0 /* See LICENSE file for copyright and license details. */

1 #ifdef __linux__

2 #define _GNU_SOURCE

3 #endif

4 #ifdef sun

5 #include <port.h>

6 #endif

7 #include <pthread.h>

8 #include <netinet/in.h>

9 #include <stdint.h>

10 #include <stdlib.h>

11 #include <stdio.h>

12 #include <strings.h>

13 #include <unistd.h>

14 #include <sys/socket.h>

15 #include <string.h>

16 #include <netdb.h>

17 #include <tls.h>

18 #include <poll.h>

19 #include <fcntl.h>

20 #include <errno.h>

21 #include <termbox.h>

22 #include <ctype.h>

23 #include <time.h>

24 #include "gemini.h"

25 #include "cert.h"

26 #include "wcwidth.h"

27 #include "display.h"

28 #include "input.h"

29 #include "sandbox.h"

30 #include "str.h"

31 #include "url.h"

32

33 #define MAX_CACHE 10

34 #define TIMEOUT 8

35 struct timespec timeout = {0, 10000000};

36

37 struct tls_config* config;

38 struct tls_config* config_empty;

39 struct gmi_client client;

40

41 void tb_colorline(int x, int y, uintattr_t color);

42 void* gmi_request_thread(struct gmi_tab* tab);

43

44 void fatal() {

45 tb_shutdown();

46 printf("Failed to allocate memory, terminating\n");

47 exit(0);

48 }

49

50 int fatalI() {

51 fatal();

52 return -1;

53 }

54

55 void* fatalP() {

56 fatal();

57 return NULL;

58 }

59

60 void memory_failure(struct gmi_tab* tab) {

61 if (!tab) return;

62 tab->show_error = 1;

63 snprintf(tab->error, sizeof(tab->error),

64 "memory allocation failure");

65 }

66

67 int gmi_goto(struct gmi_tab* tab, int id) {

68 id--;

69 struct gmi_page* page = &tab->page;

70 if (id < 0 || id >= page->links_count) {

71 snprintf(tab->error, sizeof(tab->error),

72 "Invalid link number, %d/%d", id, page->links_count);

73 tab->show_error = 1;

74 client.input.mode = 0;

75 return -1;

76 }

77 gmi_cleanforward(tab);

78 int ret = gmi_nextlink(tab, tab->url, page->links[id]);

79 return ret;

80 }

81

82 int gmi_goto_new(struct gmi_tab* tab, int id) {

83 id--;

84 struct gmi_page* page = &tab->page;

85 if (id < 0 || id >= page->links_count) {

86 snprintf(tab->error, sizeof(tab->error),

87 "Invalid link number, %d/%d", id, page->links_count);

88 tab->show_error = 1;

89 client.input.mode = 0;

90 return -1;

91 }

92 struct gmi_tab* old_tab = client.tab;

93 client.tab = gmi_newtab_url(NULL);

94 int ret = gmi_nextlink(client.tab, old_tab->url,

95 old_tab->page.links[id]);

96 return ret;

97 }

98

99 int gmi_nextlink(struct gmi_tab* tab, char* url, char* link) {

100 int url_len = strnlen(url, MAX_URL);

101 if (!strcmp(link, "about:home")) {

102 gmi_gohome(tab, 1);

103 return 0;

104 } else if (link[0] == '/' && link[1] == '/') {

105 char buf[1024];

106 strlcpy(buf, &link[2], MAX_URL - 2);

107 int len = strnlen(buf, sizeof(buf));

108 if (len < MAX_URL - 1 && buf[len - 1] != '/') {

109 buf[len] = '/';

110 buf[len + 1] = '\0';

111 }

112 int ret = gmi_request(tab, buf, 1);

113 if (ret < 1) return ret;

114 return ret;

115 } else if (link[0] == '/') {

116 if (url_len > GMI && strstr(url, "gemini://")) {

117 char* ptr = strchr(&url[GMI], '/');

118 if (ptr) *ptr = '\0';

119 }

120 char urlbuf[MAX_URL];

121 size_t l = strlcpy(urlbuf, url, sizeof(urlbuf));

122 if (l >= sizeof(urlbuf))

123 goto nextlink_overflow;

124 if (strlcpy(urlbuf + l, link, sizeof(urlbuf) - l) >=

125 sizeof(urlbuf) - l)

126 goto nextlink_overflow;

127 int ret = gmi_request(tab, urlbuf, 1);

128 return ret;

129 } else if (strstr(link, "https://") == link ||

130 strstr(link, "http://") == link ||

131 strstr(link, "gopher://") == link) {

132 #ifndef DISABLE_XDG

133 if (client.xdg && !xdg_open(link)) return -1;

134 #endif

135 tab->show_error = 1;

136 snprintf(tab->error, sizeof(tab->error),

137 "Unable to open the link");

138 return -1;

139 } else if (strstr(link, "gemini://") == link) {

140 int ret = gmi_request(tab, link, 1);

141 return ret;

142 } else {

143 char* ptr = strrchr(&url[GMI], '/');

144 if (ptr) *ptr = '\0';

145 char urlbuf[MAX_URL];

146 size_t l = strlcpy(urlbuf, url, sizeof(urlbuf));

147 if (l >= sizeof(urlbuf))

148 goto nextlink_overflow;

149 if (urlbuf[l-1] != '/') {

150 size_t l2 = strlcpy(urlbuf + l, "/",

151 sizeof(urlbuf) - l);

152 if (l2 >= sizeof(urlbuf) - l)

153 goto nextlink_overflow;

154 l += l2;

155 }

156 size_t l2 = strlcpy(urlbuf + l, link, sizeof(urlbuf) - l);

157 if (l2 >= sizeof(urlbuf) - l)

158 goto nextlink_overflow;

159 int ret = gmi_request(tab, urlbuf, 1);

160 return ret;

161 }

162 nextlink_overflow:

163 tab->show_error = 1;

164 snprintf(tab->error, sizeof(tab->error),

165 "Link too long, above 1024 characters");

166 return -1;

167 }

168

169 void gmi_load(struct gmi_page* page) {

170 for (int i=0; i < page->links_count; i++)

171 free(page->links[i]);

172 free(page->links);

173 page->links = NULL;

174 page->links_count = 0;

175 page->lines = 0;

176 if (strncmp(page->meta, "text/gemini", sizeof("text/gemini") - 1)) {

177 page->links = NULL;

178 page->links_count = 0;

179 page->lines = 0;

180 return;

181 }

182 int x = 0;

183 for (int c = 0; c < page->data_len; c++) {

184 if (x == 0 && page->data[c] == '=' &&

185 page->data[c + 1] == '>') {

186 c += 2;

187 for (; c < page->data_len && (page->data[c] == ' ' ||

188 page->data[c] == '\t');

189 c++) ;

190 char* url = (char*)&page->data[c];

191 c += parse_link(&page->data[c], page->data_len - c);

192 if (page->data[c - 1] == 127)

193 c--;

194 char save = page->data[c];

195 page->data[c] = '\0';

196 void *ptr = NULL;

197 while (!ptr) {

198 ptr = page->links ? realloc(page->links,

199 sizeof(char*) * (page->links_count+1)):

200 malloc(sizeof(char*));

201 if (!ptr) sleep(1);

202 }

203 page->links = ptr;

204

205 if (url[0] == '\0') {

206 page->links[page->links_count] = NULL;

207 page->data[c] = save;

208 continue;

209 }

210 int len = strnlen(url, MAX_URL);

211 ptr = NULL;

212 while (!ptr) {

213 ptr = malloc(len+2);

214 if (!ptr) sleep(1);

215 }

216 page->links[page->links_count] = ptr;

217 memcpy(page->links[page->links_count], url, len+1);

218 page->links_count++;

219 page->data[c] = save;

220 }

221 if (page->data[c] == '\n') {

222 page->lines++;

223 x = 0;

224 continue;

225 }

226 x++;

227 }

228 }

229

230 int gmi_render(struct gmi_tab* tab) {

231 pthread_mutex_lock(&tab->render_mutex);

232 #include "img.h"

233 #ifdef TERMINAL_IMG_VIEWER

234 if (strncmp(tab->page.meta, "image/", 6) == 0) {

235 if (!tab->page.img.tried) {

236 char* ptr = strchr(tab->page.data, '\n');

237 if (!ptr) {

238 tb_printf(2, -tab->scroll,

239 TB_DEFAULT, TB_DEFAULT,

240 "Invalid data: new line not found");

241 tab->page.img.tried = 1;

242 pthread_mutex_unlock(&tab->render_mutex);

243 return 1;

244 }

245 ptr++;

246 if (tab->page.img.data) {

247 stbi_image_free(tab->page.img.data);

248 tab->page.img.data = NULL;

249 }

250 tab->page.img.data =

251 stbi_load_from_memory((unsigned char*)ptr,

252 tab->page.data_len -

253 (int)(ptr-tab->page.data),

254 &tab->page.img.w,

255 &tab->page.img.h,

256 &tab->page.img.channels, 3);

257 if (!tab->page.img.data) {

258 tb_printf(2, -tab->scroll,

259 TB_DEFAULT, TB_DEFAULT,

260 "Failed to decode image: %s;",

261 tab->page.meta);

262 tab->page.img.tried = 1;

263 pthread_mutex_unlock(&tab->render_mutex);

264 return 1;

265 }

266

267 tab->page.img.tried = 1;

268 }

269 if (tab->page.img.data) {

270 img_display(tab->page.img.data,

271 tab->page.img.w, tab->page.img.h,

272 client.tabs_count>1);

273 pthread_mutex_unlock(&tab->render_mutex);

274 return 1;

275 }

276 }

277 #endif

278 int text = 0;

279 const char gmi[] = "text/gemini";

280 const char txt[] = "text/";

281 if (strncmp(tab->page.meta, gmi, sizeof(gmi) - 1)) {

282 if (strncmp(tab->page.meta, txt, sizeof(txt) - 1)) {

283 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,

284 "Unable to render format : %s",

285 tab->page.meta);

286 pthread_mutex_unlock(&tab->render_mutex);

287 return 1;

288 }

289 text = 1;

290 }

291 int line = 0;

292 int x = 0;

293 int links = 0;

294 uintattr_t color = TB_DEFAULT;

295 int start = 1;

296 int ignore = 0;

297 int h = tb_height() - 2 - (client.tabs_count>1);

298 char* search = client.input.field[0] == '/'?

299 &client.input.field[1]:

300 tab->search.entry;

301 int search_len = search?

302 strnlen(search, sizeof(client.input.field) - 1):0;

303 if (!search_len) search = NULL;

304 int highlight = 0;

305 int hlcolor = YELLOW;

306 if (tab->search.cursor >= tab->search.count)

307 tab->search.cursor = 0;

308 else if (tab->search.cursor < 0)

309 tab->search.cursor = tab->search.count - 1;

310 int previous_count = tab->search.count;

311 tab->search.count = 0;

312 char* ptr = tab->page.no_header?NULL:strstr(tab->page.data, "\r\n");

313 if (ptr && ptr > strstr(tab->page.data, "\n")) ptr = NULL;

314 line++;

315 int w = tb_width();

316 for (int c = ptr?ptr-tab->page.data+2:0; c < tab->page.data_len; c++) {

317 if (x == 0 && tab->page.data[c] == '\n') {

318 if (c+1 != tab->page.data_len)

319 line += 1;

320 continue;

321 }

322 if (tab->page.data[c] == '\t') {

323 x += 8 - x%8;

324 continue;

325 }

326 if (tab->page.data[c] == '\r') continue;

327 if (!text && start &&

328 tab->page.data[c] == '`' &&

329 tab->page.data[c + 1] == '`' &&

330 tab->page.data[c + 2] == '`') {

331 ignore = !ignore;

332 c += 3;

333 continue;

334 }

335 if (ignore)

336 color = ORANGE;

337

338 if (!ignore && !text) {

339 for (int i=0;

340 start && tab->page.data[c + i] == '#' && i<3;

341 i++) {

342 if (tab->page.data[c + i + 1] != '#') {

343 color = RED + i;

344 break;

345 }

346 }

347 if (start && tab->page.data[c] == '*' &&

348 tab->page.data[c + 1] == ' ') {

349 color = ITALIC|CYAN;

350 }

351 if (start && tab->page.data[c] == '>') {

352 color = ITALIC|MAGENTA;

353 }

354 if (start && tab->page.data[c] == '=' &&

355 tab->page.data[c + 1] == '>') {

356 char buf[32];

357 int len = snprintf(buf, sizeof(buf),

358 "[%d]", links + 1);

359 if (line-1>=(tab->scroll>=0?tab->scroll:0) &&

360 line-tab->scroll <= tb_height()-2) {

361 tb_print(x+2, line-1-tab->scroll,

362 links+1 ==

363 tab->selected?RED:BLUE,

364 TB_DEFAULT, buf);

365 }

366 x += len;

367 c += 2;

368

369 while (

370 (tab->page.data[c]==' ' ||

371 tab->page.data[c]=='\t') &&

372 tab->page.data[c]!='\n' &&

373 tab->page.data[c]!='\0') c++;

374

375 int initial = c;

376 while (tab->page.data[c]!=' ' &&

377 tab->page.data[c]!='\t' &&

378 tab->page.data[c]!='\n' &&

379 tab->page.data[c]!='\0') c++;

380

381 while (

382 (tab->page.data[c]==' ' ||

383 tab->page.data[c]=='\t') &&

384 tab->page.data[c]!='\n' &&

385 tab->page.data[c]!='\0') c++;

386

387 if (tab->page.data[c]=='\n' ||

388 tab->page.data[c]=='\0')

389 c = initial;

390 x += 3;

391 if ((links + 1) / 10) x--;

392 if ((links + 1) / 100) x--;

393 if ((links + 1) / 1000) x--;

394 links++;

395 }

396 }

397

398 if (search &&

399 !strncasecmp(&tab->page.data[c], search, search_len)) {

400 if (tab->search.count == (tab->search.cursor?

401 (tab->search.cursor - 1):

402 (previous_count - 1)))

403 tab->search.pos[0] = line;

404 if (tab->search.count == tab->search.cursor)

405 hlcolor++;

406 if (tab->search.cursor == previous_count - 1 &&

407 tab->search.count == 0)

408 tab->search.pos[1] = line;

409 if (tab->search.count == tab->search.cursor + 1) {

410 hlcolor--;

411 tab->search.pos[1] = line;

412 }

413 highlight += search_len;

414 tab->search.count++;

415 }

416 if (tab->page.data[c] == '\n' || tab->page.data[c] == ' ' ||

417 x+4 >= w) {

418 int end = 0;

419 if (x+4 >= w)

420 end = 1;

421 int newline = (tab->page.data[c] == '\n' || x+4 >= w);

422 for (int i = 1; ; i++) {

423 if (x + i == w - 1) break;

424 if (i > w - 4) {

425 newline = 0;

426 break;

427 }

428 if (c + i >= tab->page.data_len ||

429 tab->page.data[c+i] == ' ' ||

430 tab->page.data[c+i] == '\n' ||

431 tab->page.data[c+i] == '\0')

432 break;

433 if (w - 4 <= x + i) newline = 1;

434 }

435 if (newline) {

436 if (c != tab->page.data_len)

437 line += 1 + (x + 1)/w;

438 if (tab->page.data[c] == '\n') {

439 color = TB_DEFAULT;

440 start = 1;

441 } else c--;

442 if (c + 1 >= tab->page.data_len) continue;

443 if (tab->page.data[c + 1] == ' ') c++;

444 x = 0;

445 continue;

446 } else if (end) {

447 c++;

448 x++;

449 }

450 }

451 uint32_t ch = 0;

452 int size = tb_utf8_char_to_unicode(&ch,

453 &tab->page.data[c]) - 1;

454 if (size > 0)

455 c += tb_utf8_char_to_unicode(&ch,

456 &tab->page.data[c]) - 1;

457 else if (ch < 32) ch = '?';

458

459 int wc = mk_wcwidth(ch);

460 if (wc < 0) wc = 0;

461 if (line - 1 >= tab->scroll &&

462 (line - tab->scroll <= tb_height() - 2) && ch != '\t') {

463 if (wc == 1)

464 tb_set_cell(x + 2, line - 1 - tab->scroll,

465 ch, color,

466 highlight?hlcolor:TB_DEFAULT);

467 else

468 tb_set_cell_ex(x + 2, line - 1 - tab->scroll,

469 &ch, wc, color,

470 highlight?hlcolor:TB_DEFAULT);

471 }

472 if (highlight > 0)

473 highlight--;

474

475 x += wc;

476 start = 0;

477 }

478 line++;

479 h += (client.tabs_count > 1);

480 if (h > line) {

481 pthread_mutex_unlock(&tab->render_mutex);

482 return line;

483 }

484 int size = (h == line?(h - 1):(h / (line - h)));

485 if (size == h) size--;

486 if (size < 1) size = 1;

487 int H = (line - h + 1 + (client.tabs_count > 1));

488 int pos = (tab->scroll + (client.tabs_count < 1)) * h / (H?H:1)

489 + (client.tabs_count>1);

490 if (pos >= h) pos = h - size;

491 if (pos < 0) pos = 0;

492 if (tab->scroll+h == line) pos = h - size;

493 for (int y = (client.tabs_count > 1);

494 y < h + (client.tabs_count > 1); y++) {

495 if (y >= pos && y < pos + size)

496 tb_set_cell(w - 1, y, ' ', TB_DEFAULT, CYAN);

497 else

498 tb_set_cell(w - 1, y, ' ', TB_DEFAULT, BLACK);

499 }

500 pthread_mutex_unlock(&tab->render_mutex);

501 return line;

502 }

503

504 void gmi_addtohistory(struct gmi_tab* tab) {

505 if (!(!tab->history || (tab->history && !tab->history->next)))

506 return;

507 gmi_cleanforward(tab);

508 struct gmi_link* link = malloc(sizeof(struct gmi_link));

509 if (!link) {

510 memory_failure(tab);

511 return;

512 }

513 link->next = NULL;

514 link->prev = tab->history;

515 if (link->prev)

516 link->prev->scroll = tab->scroll;

517 link->page = tab->page;

518 link->cached = 1;

519 strlcpy(link->url, tab->url, sizeof(link->url));

520 tab->history = link;

521 if (link->prev)

522 link->prev->next = tab->history;

523 }

524

525 void gmi_cleanforward(struct gmi_tab* tab) {

526 if (!tab->history)

527 return;

528 struct gmi_link* link = tab->history->next;

529 while (link) {

530 struct gmi_link* ptr = link->next;

531 bzero(link->url, sizeof(link->url));

532 if (link->cached) {

533 gmi_freepage(&link->page);

534 link->cached = 0;

535 }

536 free(link);

537 link = ptr;

538 }

539 tab->history->next = NULL;

540 }

541

542 void gmi_freepage(struct gmi_page* page) {

543 if (!page) return;

544 #ifdef TERMINAL_IMG_VIEWER

545 if (page->img.data)

546 stbi_image_free(page->img.data);

547 #endif

548 free(page->data);

549 for (int i=0; i < page->links_count; i++)

550 free(page->links[i]);

551 free(page->links);

552 bzero(page, sizeof(struct gmi_page));

553 }

554

555 void gmi_freetab(struct gmi_tab* tab) {

556 if (!tab) return;

557 tab->request.state = STATE_CANCEL;

558 int signal = 0xFFFFFFFF;

559 send(tab->thread.pair[1], &signal, sizeof(signal), 0);

560 close(tab->thread.pair[0]);

561 close(tab->thread.pair[1]);

562 if (tab->history) {

563 struct gmi_link* link = tab->history->next;

564 while (link) {

565 struct gmi_link* ptr = link->next;

566 if (link->cached) {

567 gmi_freepage(&link->page);

568 link->cached = 0;

569 }

570 bzero(link->url, sizeof(link->url));

571 free(link);

572 link = ptr;

573 }

574 link = tab->history;

575 while (link) {

576 struct gmi_link* ptr = link->prev;

577 if (link->cached) {

578 gmi_freepage(&link->page);

579 link->cached = 0;

580 }

581 bzero(link->url, sizeof(link->url));

582 free(link);

583 link = ptr;

584 }

585 }

586 if (tab->prev) tab->prev->next = tab->next;

587 if (tab->next) tab->next->prev = tab->prev;

588 struct gmi_tab* prev = tab->prev;

589 if (!prev) prev = tab->next;

590 pthread_mutex_destroy(&tab->render_mutex);

591 if ((signed)tab->thread.started)

592 pthread_join(tab->thread.thread, NULL);

593 #ifdef TERMINAL_IMG_VIEWER

594 if (tab->page.img.data)

595 free(tab->page.img.data);

596 #endif

597 free(tab);

598 client.tab = prev;

599 client.tabs_count--;

600 }

601

602 char home_page[] =

603 "20 text/gemini\r\n# Vgmi - " VERSION "\n\n" \

604 "A Gemini client written in C with vim-like keybindings\n\n" \

605 "## Bookmarks\n\n" \

606 "%s\n" \

607 "## Keybindings\n\n" \

608 "* k - Scroll up\n" \

609 "* j - Scroll down\n" \

610 "* gT - Switch to the previous tab\n" \

611 "* gt - Switch to the next tab\n" \

612 "* H - Go back in the history\n" \

613 "* L - Go forward in the history\n" \

614 "* gg - Go at the top of the page\n" \

615 "* G - Go at the bottom of the page\n" \

616 "* / - Open search mode\n" \

617 "* : - Open input mode\n" \

618 "* u - Open input mode with the current url\n" \

619 "* f - Show the history\n" \

620 "* r - Reload the page\n" \

621 "* [number]Tab - Select a link\n" \

622 "* Tab - Follow the selected link\n" \

623 "* Shift+Tab - Open the selected link in a new tab\n" \

624 "* Del - Delete the selected link from the bookmarks\n" \

625 "\nYou can prefix a movement key with a number to repeat it.\n\n" \

626 "## Commands\n\n" \

627 "* :q - Close the current tab\n" \

628 "* :qa - Close all tabs, exit the program\n" \

629 "* :o [url] - Open an url\n" \

630 "* :s [search] - Search the Geminispace using geminispace.info\n" \

631 "* :nt [url] - Open a new tab, the url is optional\n" \

632 "* :add [name] - Add the current url to the bookmarks, the name is optional\n"\

633 "* :[number] - Scroll to the line number\n" \

634 "* :gencert - Generate a certificate for the current capsule\n" \

635 "* :forget <host> - Forget the certificate for an host\n" \

636 "* :ignore <host> - Ignore expiration for the host certificate\n" \

637 "* :download [name] - Download the current page, the name is optional\n"

638 "* :exec - Open the last downloaded file";

639

640 void gmi_newbookmarks() {

641 int len;

642 const char geminispace[] = "gemini://geminispace.info Geminispace";

643 const char gemigit[] = "gemini://gemini.rmf-dev.com Gemigit";

644 client.bookmarks = malloc(sizeof(char*) * 3);

645 if (!client.bookmarks) goto fail_malloc;

646

647 len = sizeof(geminispace);

648 client.bookmarks[0] = malloc(len);

649 if (!client.bookmarks[0]) goto fail_malloc;

650 strlcpy(client.bookmarks[0], geminispace, len);

651

652 len = sizeof(gemigit);

653 client.bookmarks[1] = malloc(len);

654 if (!client.bookmarks[1]) goto fail_malloc;

655 strlcpy(client.bookmarks[1], gemigit, len);

656

657 client.bookmarks[2] = NULL;

658 return;

659 fail_malloc:

660 fatal();

661 }

662

663 int gmi_loadbookmarks() {

664 int fd = openat(config_fd, "bookmarks.txt", O_RDONLY);

665 if (fd < 0)

666 return -1;

667 FILE* f = fdopen(fd, "rb");

668 if (!f)

669 return -1;

670 #ifdef SANDBOX_FREEBSD

671 if (makefd_readonly(fd)) {

672 fclose(f);

673 return -1;

674 }

675 #endif

676 fseek(f, 0, SEEK_END);

677 size_t len = ftell(f);

678 fseek(f, 0, SEEK_SET);

679 char* data = malloc(len);

680 if (!data) {

681 memory_failure(client.tab);

682 fclose(f);

683 return -1;

684 }

685 if (len != fread(data, 1, len, f)) {

686 fclose(f);

687 return -1;

688 }

689 fclose(f);

690 char* ptr = data;

691 long n = 0;

692 while (++ptr && ptr < data+len) if (*ptr == '\n') n++;

693 client.bookmarks = malloc(sizeof(char*) * (n + 1));

694 if (!client.bookmarks) {

695 memory_failure(client.tab);

696 return -1;

697 }

698 client.bookmarks[n] = NULL;

699 n = 0;

700 ptr = data;

701 char* str = data;

702 while (++ptr && ptr < data+len) {

703 if (*ptr != '\n') continue;

704 *ptr = '\0';

705 client.bookmarks[n] = malloc(ptr-str+1);

706 if (!client.bookmarks[n]) {

707 memory_failure(client.tab);

708 break;

709 }

710 strlcpy(client.bookmarks[n], str, ptr-str+1);

711 n++;

712 str = ptr+1;

713 }

714 free(data);

715 return 0;

716 }

717

718 void sanitize(char* str, size_t len) {

719 int n = 0;

720 for (size_t i = 0; i < len; i += n) {

721 n = tb_utf8_char_length(str[i]);

722 if (n > 1 || str[i] >= 32) continue;

723 str[i] = '\0';

724 break;

725 }

726 }

727

728 void gmi_gettitle(struct gmi_page* page, const char* url) {

729 if (page->title_cached) return;

730 page->title[0] = '\0';

731 page->title_cached = 1;

732 if (strncmp(page->meta, "text/gemini", sizeof("text/gemini") - 1)) {

733 goto use_url;

734 }

735 int start = -1;

736 int end = -1;

737 int line_start = 1;

738 for (int i = 0; i < page->data_len; i++) {

739 if (line_start && start == -1 && page->data[i] == '#') {

740 for (int j = i+1; j < page->data_len; j++) {

741 if (j && page->data[j-1] == '#' &&

742 page->data[j] == '#')

743 break;

744 if (page->data[j] != ' ') {

745 start = j;

746 break;

747 }

748 }

749 }

750 line_start = 0;

751 if (page->data[i] == '\n')

752 line_start = 1;

753 if (start != -1 && page->data[i] == '\n') {

754 end = i;

755 break;

756 }

757 }

758 if (start == -1 || end == -1)

759 goto use_url;

760 size_t len = end - start + 1;

761 len = strlcpy(page->title, &page->data[start],

762 len < sizeof(page->title)?

763 len:sizeof(page->title));

764 sanitize(page->title, len);

765 return;

766 use_url:

767 if (!url) {

768 size_t len = strlcpy(page->title, page->meta,

769 sizeof(page->title));

770 sanitize(page->title, len);

771 return;

772 }

773 char* str = strrchr(url, '/');

774 if (!str) {

775 len = strlcpy(page->title, url, sizeof(page->title));

776 goto sanitize;

777 }

778 if (str[1] != '\0')

779 str++;

780 len = strlcpy(page->title, str, sizeof(page->title));

781 sanitize:

782 for (size_t i = 0; i < len; i++)

783 if (page->title[i] == '?')

784 page->title[i] = 0;

785 }

786

787 int gmi_removebookmark(int index) {

788 index--;

789 if (index < 0) return -1;

790 int fail = -1;

791 for (int i = 0; client.bookmarks[i]; i++) {

792 if (i == index) {

793 free(client.bookmarks[i]);

794 fail = 0;

795 }

796 if (!fail)

797 client.bookmarks[i] = client.bookmarks[i+1];

798 }

799 return fail;

800 }

801

802 void gmi_addbookmark(struct gmi_tab* tab, char* url, char* title) {

803 if (!strcmp(url, "about:home")) {

804 snprintf(tab->error, sizeof(tab->error),

805 "Cannot add the new tab page to the bookmarks");

806 tab->show_error = 1;

807 return;

808 }

809 int title_len = 0;

810 if (!title) {

811 gmi_gettitle(&tab->page, tab->url);

812 title = tab->page.title;

813 title_len = strnlen(tab->page.title, sizeof(tab->page.title));

814 if (!title_len) {

815 title_len = sizeof("no title");

816 title = "no title";

817 }

818 } else title_len = strnlen(title, 128);

819 long n = 0;

820 while (client.bookmarks[n]) n++;

821 void* ptr = realloc(client.bookmarks, sizeof(char*) * (n + 2));

822 if (!ptr) {

823 memory_failure(tab);

824 return;

825 }

826 client.bookmarks = ptr;

827 int len = strnlen(url, MAX_URL) + title_len + 2;

828 client.bookmarks[n] = malloc(len);

829 if (!client.bookmarks[n]) {

830 memory_failure(tab);

831 return;

832 }

833 if (title)

834 snprintf(client.bookmarks[n], len, "%s %s", url, title);

835 else

836 snprintf(client.bookmarks[n], len, "%s", url);

837 client.bookmarks[n + 1] = NULL;

838 gmi_savebookmarks();

839 }

840

841 int gmi_savebookmarks() {

842 #ifdef SANDBOX_SUN

843 int fd = wr_pair[1];

844 if (send(fd, &WR_BOOKMARKS, sizeof(SBC), 0) != sizeof(SBC))

845 return -1;

846 #else

847 int fd = openat(config_fd, "bookmarks.txt",

848 O_CREAT|O_WRONLY|O_CLOEXEC|O_TRUNC, 0600);

849 if (fd < 0) {

850 printf("Failed to write bookmarks, %s\n", strerror(errno));

851 return -1;

852 }

853 #ifdef SANDBOX_FREEBSD

854 if (makefd_writeonly(fd)) {

855 close(fd);

856 return -1;

857 }

858 #endif

859 #endif

860 for (int i = 0; client.bookmarks[i]; i++) {

861 write(fd, client.bookmarks[i], strlen(client.bookmarks[i]));

862 char c = '\n';

863 write(fd, &c, 1);

864 }

865 #ifdef SANDBOX_SUN

866 send(fd, &WR_END, sizeof(SBC), 0);

867 #else

868 close(fd);

869 #endif

870 return 0;

871 }

872

873 char* gmi_getbookmarks(int* len) {

874 char* data = NULL;

875 int n = 0;

876 for (int i = 0; client.bookmarks[i]; i++) {

877 char line[2048];

878 long length = snprintf(line, sizeof(line), "=>%s\n ",

879 client.bookmarks[i]);

880 void *ptr = realloc(data, n+length+1);

881 if (!ptr) {

882 *len = n;

883 return data;

884 }

885 data = ptr;

886 strlcpy(&data[n], line, length);

887 n += length-1;

888 }

889 *len = n;

890 return data;

891 }

892

893 void gmi_gohome(struct gmi_tab* tab, int add) {

894 strlcpy(tab->url, "about:home", sizeof(tab->url));

895 int bm = 0;

896 char* data = gmi_getbookmarks(&bm);

897 pthread_mutex_lock(&tab->render_mutex);

898 tab->request.data = malloc(sizeof(home_page) + bm);

899 if (!tab->request.data) {

900 memory_failure(tab);

901 pthread_mutex_unlock(&tab->render_mutex);

902 return;

903 }

904 bzero(tab->request.data, sizeof(home_page) + bm);

905

906 tab->request.recv = snprintf(tab->request.data, sizeof(home_page) + bm,

907 home_page, data ? data : "");

908 free(data);

909

910 strlcpy(tab->request.meta, "text/gemini", sizeof(tab->request.meta));

911

912 if (!add)

913 gmi_freepage(&tab->page);

914 bzero(&tab->page, sizeof(struct gmi_page));

915 tab->page.data = tab->request.data;

916 tab->page.data_len = tab->request.recv;

917 tab->page.code = 20;

918 strlcpy(tab->page.meta, tab->request.meta, sizeof(tab->page.meta));

919 gmi_load(&tab->page);

920 if (add)

921 gmi_addtohistory(tab);

922 else if (tab->history) {

923 tab->history->page = tab->page;

924 tab->history->cached = 1;

925 }

926 tab->scroll = -1;

927 tab->request.data = NULL;

928 pthread_mutex_unlock(&tab->render_mutex);

929 }

930

931 struct gmi_tab* gmi_newtab() {

932 return gmi_newtab_url("about:home");

933 }

934

935 struct gmi_tab* gmi_newtab_url(const char* url) {

936 client.tabs_count++;

937 if (client.tab) {

938 struct gmi_tab* next = client.tab->next;

939 client.tab->next = malloc(sizeof(struct gmi_tab));

940 if (!client.tab->next) {

941 memory_failure(client.tab);

942 return client.tab;

943 }

944 bzero(client.tab->next, sizeof(struct gmi_tab));

945 client.tab->next->next = next;

946 client.tab->next->prev = client.tab;

947 client.tab = client.tab->next;

948 if (next)

949 next->prev = client.tab;

950 } else {

951 client.tab = malloc(sizeof(struct gmi_tab));

952 if (!client.tab) return fatalP();

953 bzero(client.tab, sizeof(struct gmi_tab));

954 }

955 pthread_mutex_init(&client.tab->render_mutex, NULL);

956

957 if (socketpair(AF_UNIX, SOCK_STREAM, 0, client.tab->thread.pair))

958 return client.tab;

959 pthread_create(&client.tab->thread.thread, NULL,

960 (void *(*)(void *))gmi_request_thread,

961 (void*)client.tab);

962 client.tab->thread.started = 1;

963 if (url)

964 gmi_request(client.tab, url, 1);

965

966 client.tab->scroll = -1;

967 return client.tab;

968 }

969

970 int gmi_request_init(struct gmi_tab* tab, const char* url, int add) {

971 if (!tab) return -1;

972 if (!strcmp(url, "about:home")) {

973 gmi_gohome(tab, add);

974 return tab->page.data_len;

975 }

976 tab->show_error = 0;

977 if (tab->history) tab->history->scroll = tab->scroll;

978 tab->selected = 0;

979 tab->request.host[0] = '\0';

980 int proto = parse_url(url,

981 tab->request.host, sizeof(tab->request.host),

982 tab->request.url, sizeof(tab->request.url),

983 &tab->request.port);

984

985 if (proto == -2) {

986 tab->show_error = 1;

987 snprintf(tab->error, sizeof(tab->error),

988 "Link too long, above 1024 characters");

989 return -1;

990 }

991 if (proto == PROTO_FILE) {

992 return gmi_loadfile(tab, &tab->request.url[P_FILE]);

993 }

994 if (proto != PROTO_GEMINI) {

995 #ifndef DISABLE_XDG

996 if (client.xdg && !xdg_open((char*)url)) return 1;

997 #endif

998 tab->show_error = 1;

999 snprintf(tab->error, sizeof(tab->error),

1000 "Unable to open the link");

1001 return -1;

1002 }

1003 if (tab->request.tls)

1004 tls_reset(tab->request.tls);

1005 else

1006 tab->request.tls = tls_client();

1007 if (!tab->request.tls) {

1008 snprintf(tab->error, sizeof(tab->error),

1009 "Failed to initialize TLS");

1010 tab->show_error = 1;

1011 return -1;

1012 }

1013

1014 int cert = cert_getcert(tab->request.host, 0);

1015 if (cert >= 0 &&

1016 tls_config_set_keypair_mem(config,

1017 (unsigned char*)client.certs[cert].crt,

1018 client.certs[cert].crt_len,

1019 (unsigned char*)client.certs[cert].key,

1020 client.certs[cert].key_len))

1021 {

1022 tab->show_error = 1;

1023 snprintf(tab->error, sizeof(tab->error), "tls error: %s",

1024 tls_config_error(config));

1025 return -1;

1026 }

1027

1028 if (tls_configure(tab->request.tls, cert>=0?config:config_empty)) {

1029 snprintf(tab->error, sizeof(tab->error),

1030 "Failed to configure TLS");

1031 tab->show_error = 1;

1032 return -1;

1033 }

1034 tab->request.state = STATE_REQUESTED;

1035 return 0;

1036 }

1037

1038 #ifdef __linux

1039 void dns_async(union sigval sv) {

1040 struct gmi_tab* tab = sv.sival_ptr;

1041 tab->request.resolved = 1;

1042 }

1043 #endif

1044

1045 int gmi_request_dns(struct gmi_tab* tab) {

1046 char host[1024];

1047 if (idn_to_ascii(tab->request.host, sizeof(tab->request.host),

1048 host, sizeof(host))) {

1049 snprintf(tab->error, sizeof(tab->error),

1050 "Invalid domain name: %s", tab->request.host);

1051 tab->show_error = 1;

1052 return -1;

1053 }

1054 strlcpy(tab->request.host, host, sizeof(tab->request.host));

1055 #if defined(__linux__) && !defined(__MUSL__)

1056 tab->request.gaicb_ptr = malloc(sizeof(struct gaicb));

1057 if (!tab->request.gaicb_ptr) {

1058 memory_failure(tab);

1059 return -1;

1060 }

1061 bzero(tab->request.gaicb_ptr, sizeof(struct gaicb));

1062 tab->request.gaicb_ptr->ar_name = tab->request.host;

1063 tab->request.resolved = 0;

1064 struct sigevent sevp;

1065 bzero(&sevp, sizeof(sevp));

1066 sevp.sigev_notify = SIGEV_THREAD;

1067 sevp.sigev_notify_function = dns_async;

1068 sevp.sigev_value.sival_ptr = tab;

1069 int ret = getaddrinfo_a(GAI_NOWAIT, &tab->request.gaicb_ptr, 1, &sevp);

1070 if (ret) {

1071 snprintf(tab->error, sizeof(tab->error),

1072 "Unable request domain name: %s", tab->request.host);

1073 tab->show_error = 1;

1074 return -1;

1075 }

1076

1077 long start = time(NULL);

1078 for (int i=0; !tab->request.resolved; i++) {

1079 if (tab->request.state == STATE_CANCEL) break;

1080 if (time(NULL) - start > TIMEOUT) break;

1081 nanosleep(&timeout, NULL);

1082 }

1083

1084 if (tab->request.resolved != 1 ||

1085 tab->request.gaicb_ptr->ar_result == NULL) {

1086 gai_cancel(tab->request.gaicb_ptr);

1087 free(tab->request.gaicb_ptr);

1088 snprintf(tab->error, sizeof(tab->error),

1089 "Unknown domain name: %s", tab->request.host);

1090 tab->show_error = 1;

1091 return -1;

1092 }

1093 struct addrinfo *result = tab->request.gaicb_ptr->ar_result;

1094 #else

1095 struct addrinfo hints, *result;

1096 bzero(&hints, sizeof(hints));

1097 hints.ai_family = AF_INET;

1098 hints.ai_socktype = SOCK_STREAM;

1099 hints.ai_flags |= AI_CANONNAME;

1100 errno = 0;

1101

1102 if ((getaddrinfo(tab->request.host, NULL, &hints, &result))) {

1103 snprintf(tab->error, sizeof(tab->error),

1104 "Unknown domain name: %s, %s",

1105 tab->request.host, strerror(errno));

1106 tab->show_error = 1;

1107 return -1;

1108 }

1109 #endif

1110

1111 struct sockaddr_in addr4;

1112 struct sockaddr_in6 addr6;

1113 bzero(&addr4, sizeof(addr4));

1114 bzero(&addr6, sizeof(addr6));

1115

1116 int error = 0;

1117 if (result->ai_family == AF_INET) {

1118 addr4.sin_addr =

1119 ((struct sockaddr_in*)result->ai_addr)->sin_addr;

1120 addr4.sin_family = AF_INET;

1121 addr4.sin_port = htons(tab->request.port);

1122 tab->request._addr.addr4 = addr4;

1123 tab->request.addr =

1124 (struct sockaddr*)&tab->request._addr.addr4;

1125 }

1126 else if (result->ai_family == AF_INET6) {

1127 addr6.sin6_addr =

1128 ((struct sockaddr_in6*)result->ai_addr)->sin6_addr;

1129 addr6.sin6_family = AF_INET6;

1130 addr6.sin6_port = htons(tab->request.port);

1131 tab->request._addr.addr6 = addr6;

1132 tab->request.addr =

1133 (struct sockaddr*)&tab->request._addr.addr6;

1134 } else {

1135 snprintf(tab->error, sizeof(tab->error),

1136 "Unexpected error, invalid address family %s",

1137 tab->request.host);

1138 error = 1;

1139 }

1140 tab->request.family = result->ai_family;

1141 freeaddrinfo(result);

1142 #ifdef __linux__

1143 free(tab->request.gaicb_ptr);

1144 #endif

1145 if (error) {

1146 tab->request.state = STATE_CANCEL;

1147 return -1;

1148 }

1149

1150 tab->request.state = STATE_DNS;

1151 return 0;

1152 }

1153

1154 int gmi_request_connect(struct gmi_tab* tab) {

1155

1156 tab->request.socket = socket(tab->request.family, SOCK_STREAM, 0);

1157 if (tab->request.socket == -1) {

1158 snprintf(tab->error, sizeof(tab->error),

1159 "Failed to create socket");

1160 return -1;

1161 }

1162 int flags = fcntl(tab->request.socket, F_GETFL);

1163 if (flags == -1 ||

1164 fcntl(tab->request.socket, F_SETFL, flags|O_NONBLOCK) == -1) {

1165 snprintf(tab->error, sizeof(tab->error),

1166 "Failed to create non-blocking socket");

1167 return -1;

1168 }

1169

1170 int addr_size = (tab->request.family == AF_INET)?

1171 sizeof(struct sockaddr_in):

1172 sizeof(struct sockaddr_in6);

1173

1174 int connected = 0;

1175 int failed = connect(tab->request.socket,

1176 tab->request.addr, addr_size);

1177 failed = failed?(errno != EAGAIN && errno != EWOULDBLOCK &&

1178 errno != EINPROGRESS && errno != EALREADY &&

1179 errno != 0):failed;

1180 while (!failed) {

1181 struct pollfd fds[2];

1182 fds[0].fd = tab->thread.pair[0];

1183 fds[0].events = POLLIN;

1184 fds[1].fd = tab->request.socket;

1185 fds[1].events = POLLOUT;

1186 int count = poll(fds, 2, TIMEOUT * 1000);

1187 if (count < 1 || fds[1].revents != POLLOUT ||

1188 tab->request.state == STATE_CANCEL) break;

1189 int value;

1190 socklen_t len = sizeof(value);

1191 int ret = getsockopt(tab->request.socket, SOL_SOCKET,

1192 SO_ERROR, &value, &len);

1193 connected = (value == 0 && ret == 0);

1194 break;

1195 }

1196

1197 if (!connected) {

1198 snprintf(tab->error, sizeof(tab->error),

1199 "Connection to %s timed out : %s",

1200 tab->request.host, strerror(errno));

1201 return -1;

1202 }

1203

1204 if (tls_connect_socket(tab->request.tls, tab->request.socket,

1205 tab->request.host)) {

1206 snprintf(tab->error, sizeof(tab->error),

1207 "Unable to connect to: %s : %s",

1208 tab->request.host, tls_error(tab->request.tls));

1209 return -1;

1210 }

1211 return 0;

1212 }

1213

1214 int gmi_request_handshake(struct gmi_tab* tab) {

1215 if (tls_connect_socket(tab->request.tls,

1216 tab->request.socket,

1217 tab->request.host)) {

1218 snprintf(tab->error, sizeof(tab->error),

1219 "Unable to connect to: %s : %s",

1220 tab->request.host, tls_error(tab->request.tls));

1221 return -1;

1222 }

1223 if (tab->request.state == STATE_CANCEL) return -1;

1224 int ret = 0;

1225 time_t start = time(0);

1226 while ((ret = tls_handshake(tab->request.tls))) {

1227 if (time(NULL) - start > TIMEOUT ||

1228 tab->request.state == STATE_CANCEL ||

1229 (ret < 0 &&

1230 ret != TLS_WANT_POLLIN &&

1231 ret != TLS_WANT_POLLOUT))

1232 break;

1233 if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)

1234 nanosleep(&timeout, NULL);

1235 }

1236 if (ret) {

1237 snprintf(tab->error, sizeof(tab->error),

1238 "Failed to handshake: %s (%s)",

1239 tls_error(tab->request.tls),

1240 tab->request.host);

1241 return -1;

1242 }

1243 if (tab->request.state == STATE_CANCEL) return -1;

1244 ret = cert_verify(tab->request.host,

1245 tls_peer_cert_hash(tab->request.tls),

1246 tls_peer_cert_notbefore(tab->request.tls),

1247 tls_peer_cert_notafter(tab->request.tls));

1248 switch (ret) {

1249 case 0: // success

1250 break;

1251 case -1: // invalid certificate

1252 snprintf(tab->error, sizeof(tab->error),

1253 "Failed to verify server certificate for %s" \

1254 "(The certificate changed)",

1255 tab->request.host);

1256 return -1;

1257 case -3: // sandbox error

1258 snprintf(tab->error, sizeof(tab->error),

1259 "Sandbox error when verifying server certificate " \

1260 "for %s", tab->request.host);

1261 return -1;

1262 case -5: // expired

1263 if (cert_should_ignore(tab->request.host)) break;

1264 snprintf(tab->error, sizeof(tab->error),

1265 "Expired certificate, " \

1266 "the certificate for %s has expired",

1267 tab->request.host);

1268 return -1;

1269 case -6: // certificate changed

1270 snprintf(tab->error, sizeof(tab->error),

1271 "Invalid certificate, enter \":forget %s\"" \

1272 " to forget the old certificate.",

1273 tab->request.host);

1274 return -1;

1275 default: // failed to write certificate

1276 snprintf(tab->error, sizeof(tab->error),

1277 "Failed to write %s certificate information : %s [%d]",

1278 tab->request.host, strerror(errno), ret);

1279 return -1;

1280 }

1281

1282 if (tab->request.state == STATE_CANCEL) return -1;

1283 char buf[MAX_URL];

1284 strlcpy(buf, "gemini://", GMI + 1);

1285 char toascii[1024];

1286 if (idn_to_ascii(&tab->request.url[GMI],

1287 sizeof(tab->request.url) - GMI,

1288 toascii, sizeof(toascii))) {

1289 snprintf(tab->error, sizeof(tab->error),

1290 "Failed to parse url: %s", &tab->request.url[GMI]);

1291 return -1;

1292 }

1293 ssize_t len = strlcpy(&buf[GMI], toascii, sizeof(buf) - GMI) + GMI;

1294 char url[1024];

1295 if (len < MAX_URL)

1296 len = parse_query(buf, MAX_URL, url, MAX_URL);

1297 if (len >= MAX_URL ||

1298 (len += strlcpy(&url[len], "\r\n", MAX_URL - len)) >= MAX_URL) {

1299 snprintf(tab->error, sizeof(tab->error),

1300 "Url too long: %s", url);

1301 return -1;

1302 }

1303

1304 if (tls_write(tab->request.tls, url, len) < len) {

1305 snprintf(tab->error, sizeof(tab->error),

1306 "Failed to send data to: %s", tab->request.host);

1307 return -1;

1308 }

1309

1310 if (tab->request.state == STATE_CANCEL) return -1;

1311 tab->request.state = STATE_CONNECTED;

1312 return 0;

1313 }

1314

1315 int gmi_request_header(struct gmi_tab* tab) {

1316 time_t now = time(0);

1317 char buf[1024];

1318 int recv = 0;

1319 while (1) {

1320 if (tab->request.state == STATE_CANCEL) break;

1321 if (time(0) - now >= TIMEOUT) {

1322 snprintf(tab->error, sizeof(tab->error),

1323 "Connection to %s timed out (sent no data)",

1324 tab->request.host);

1325 return -1;

1326 }

1327 int n = tls_read(tab->request.tls, &buf[recv],

1328 sizeof(buf) - recv - 1);

1329 if (n == TLS_WANT_POLLIN) {

1330 nanosleep(&timeout, NULL);

1331 continue;

1332 }

1333 if (n < 1) {

1334 recv = -1;

1335 break;

1336 }

1337 recv += n;

1338 buf[recv] = '\0';

1339 if (strstr(buf, "\r\n")) break;

1340 }

1341 if (tab->request.state == STATE_CANCEL) return -1;

1342

1343 if (recv <= 0) {

1344 snprintf(tab->error, sizeof(tab->error),

1345 "[%d] Invalid data from: %s",

1346 recv, tab->request.host);

1347 return -1;

1348 }

1349 if (!strstr(buf, "\r\n")) {

1350 snprintf(tab->error, sizeof(tab->error),

1351 "Invalid data from: %s (no CRLF)", tab->request.host);

1352 return -1;

1353 }

1354 char* ptr = strchr(buf, ' ');

1355 if (!ptr) {

1356 snprintf(tab->error, sizeof(tab->error),

1357 "Invalid data from: %s (no metadata)",

1358 tab->request.host);

1359 return -1;

1360 }

1361 strlcpy(tab->request.error, ptr +1, sizeof(tab->request.error));

1362

1363 int previous_code = tab->page.code;

1364 char c = buf[2];

1365 buf[2] = '\0';

1366 tab->page.code = atoi(buf);

1367 buf[2] = c;

1368

1369 if (tab->request.state == STATE_CANCEL) return -1;

1370 switch (tab->page.code) {

1371 case 10:

1372 case 11:

1373 ptr++;

1374 strlcpy(client.input.label, ptr, sizeof(client.input.label));

1375 ptr = strchr(client.input.label, '\n');

1376 if (ptr) *ptr = '\0';

1377 ptr = strchr(client.input.label, '\r');

1378 if (ptr) *ptr = '\0';

1379 tab->request.state = STATE_RECV_HEADER;

1380 tab->request.recv = recv;

1381 return 2; // input

1382 case 20:

1383 {

1384 #ifdef TERMINAL_IMG_VIEWER

1385 tab->page.img.tried = 0;

1386 #endif

1387 char* meta = strchr(++ptr, '\r');

1388 if (!meta) {

1389 snprintf(tab->error, sizeof(tab->error),

1390 "Invalid data from: %s", tab->request.host);

1391 return -1;

1392 }

1393 *meta = '\0';

1394 strlcpy(tab->request.meta, ptr, MAX_META);

1395 *meta = '\r';

1396 if ((strcasestr(tab->request.meta, "charset=") &&

1397 !strcasestr(tab->request.meta, "charset=utf-8"))

1398 || ((strncmp(tab->request.meta, "text/", 5))

1399 #ifdef TERMINAL_IMG_VIEWER

1400 && (strncmp(tab->request.meta, "image/", 6))

1401 #endif

1402 )) {

1403 if (tab->history)

1404 strlcpy(tab->url, tab->history->url,

1405 sizeof(tab->url));

1406 else

1407 strlcpy(tab->url, "about:home",

1408 sizeof(tab->url));

1409 tab->request.ask = 2;

1410 snprintf(tab->request.info, sizeof(tab->request.info),

1411 "# Non-renderable meta-data : %s",

1412 tab->request.meta);

1413 strlcpy(tab->request.action, "download",

1414 sizeof(tab->request.action));

1415 tb_interupt();

1416 while (tab->request.ask == 2)

1417 nanosleep(&timeout, NULL);

1418 tab->request.download = tab->request.ask;

1419 if (!tab->request.download) {

1420 tab->request.recv = -1;

1421 return 1; // download

1422 }

1423 }

1424 char* ptr_meta = strchr(tab->page.meta, ';');

1425 if (ptr_meta) *ptr_meta = '\0';

1426 }

1427 break;

1428 case 30:

1429 snprintf(tab->error, sizeof(tab->error),

1430 "Redirect temporary");

1431 break;

1432 case 31:

1433 snprintf(tab->error, sizeof(tab->error),

1434 "Redirect permanent");

1435 break;

1436 case 40:

1437 snprintf(tab->error, sizeof(tab->error),

1438 "Temporary failure");

1439 return -2;

1440 case 41:

1441 snprintf(tab->error, sizeof(tab->error),

1442 "Server unavailable");

1443 return -2;

1444 case 42:

1445 snprintf(tab->error, sizeof(tab->error),

1446 "CGI error");

1447 return -2;

1448 case 43:

1449 snprintf(tab->error, sizeof(tab->error),

1450 "Proxy error");

1451 return -2;

1452 case 44:

1453 snprintf(tab->error, sizeof(tab->error),

1454 "Slow down: server above rate limit");

1455 return -2;

1456 case 50:

1457 snprintf(tab->error, sizeof(tab->error),

1458 "Permanent failure");

1459 return -2;

1460 case 51:

1461 snprintf(tab->error, sizeof(tab->error),

1462 "Not found %s", tab->request.url);

1463 return -2;

1464 case 52:

1465 snprintf(tab->error, sizeof(tab->error),

1466 "Resource gone");

1467 return -2;

1468 case 53:

1469 snprintf(tab->error, sizeof(tab->error),

1470 "Proxy request refused");

1471 return -2;

1472 case 59:

1473 snprintf(tab->error, sizeof(tab->error),

1474 "Bad request");

1475 return -2;

1476 case 60:

1477 snprintf(tab->error, sizeof(tab->error),

1478 "Client certificate required");

1479 return -2;

1480 case 61:

1481 snprintf(tab->error, sizeof(tab->error),

1482 "Client not authorised");

1483 return -2;

1484 case 62:

1485 snprintf(tab->error, sizeof(tab->error),

1486 "Client certificate not valid");

1487 return -2;

1488 default:

1489 snprintf(tab->error, sizeof(tab->error),

1490 "Unknown status code: %d", tab->page.code);

1491 tab->page.code = previous_code;

1492 return -2;

1493 }

1494 if (tab->request.state == STATE_CANCEL) return -1;

1495 tab->request.state = STATE_RECV_HEADER;

1496 tab->request.recv = recv;

1497 tab->request.data = malloc(tab->request.recv + 1);

1498 if (!tab->request.data) {

1499 memory_failure(tab);

1500 return -1;

1501 }

1502 memcpy(tab->request.data, buf, recv);

1503 return 0;

1504 }

1505

1506 int gmi_request_body(struct gmi_tab* tab, int fd) {

1507 time_t now = time(0);

1508 char buf[1024];

1509 int isText = !strncmp(tab->request.meta, "text/", sizeof("text/") - 1);

1510 while (tab->request.state != STATE_CANCEL) {

1511 if (time(0) - now >= TIMEOUT) {

1512 snprintf(tab->error, sizeof(tab->error),

1513 "Server %s stopped responding",

1514 tab->request.host);

1515 return -1;

1516 }

1517 int bytes = tls_read(tab->request.tls, buf, sizeof(buf));

1518 if (tab->request.state == STATE_CANCEL) return -1;

1519 if (bytes == 0) break;

1520 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) {

1521 nanosleep(&timeout, NULL);

1522 continue;

1523 }

1524 if (bytes < 1) {

1525 snprintf(tab->error, sizeof(tab->error),

1526 "Invalid data from %s: %s",

1527 tab->request.host,

1528 tls_error(tab->request.tls));

1529 return -1;

1530 }

1531 if (fd > 0) {

1532 if (write(fd, buf, bytes) != bytes) {

1533 snprintf(tab->error, sizeof(tab->error),

1534 "Failed to download file: %s",

1535 strerror(errno));

1536 return -1;

1537 }

1538 now = time(0);

1539 continue;

1540 }

1541 /* allocate addional memory in case of reading an incomplete

1542 * unicode character at the end of the page */

1543 void *ptr = realloc(tab->request.data,

1544 tab->request.recv + bytes + 32);

1545 if (!ptr) {

1546 free(tab->request.data);

1547 tab->request.data = NULL;

1548 memory_failure(tab);

1549 return -1;

1550 }

1551 tab->request.data = ptr;

1552 memcpy(&tab->request.data[tab->request.recv], buf, bytes);

1553 memset(&tab->request.data[tab->request.recv + bytes], 0, 32);

1554 tab->request.recv += bytes;

1555 /* prevent the server from filling up memory */

1556 if ((isText && tab->request.recv > MAX_TEXT_SIZE) ||

1557 (!isText && tab->request.recv > MAX_IMAGE_SIZE)) {

1558 snprintf(tab->error, sizeof(tab->error),

1559 "The server sent a too large response");

1560 return -1;

1561 }

1562 now = time(0);

1563 }

1564 if (tab->request.state == STATE_CANCEL) return -1;

1565 tab->request.state = STATE_RECV_BODY;

1566 return 0;

1567 }

1568

1569 int gmi_getfd(struct gmi_tab* tab, char* path, size_t path_len) {

1570 int len = strnlen(tab->request.url,

1571 sizeof(tab->request.url));

1572 if (tab->request.url[len-1] == '/')

1573 tab->request.url[len-1] = '\0';

1574 char* ptr = strrchr(tab->request.url, '/');

1575 #ifndef SANDBOX_SUN

1576 int fd = getdownloadfd();

1577 if (fd < 0) {

1578 snprintf(tab->error, sizeof(tab->error),

1579 "Unable to open download folder");

1580 tab->show_error = 1;

1581 return -1;

1582 }

1583 #endif

1584 if (ptr)

1585 strlcpy(path, ptr + 1, path_len);

1586 else {

1587 #ifdef __OpenBSD__

1588 char format[] = "output_%lld.dat";

1589 #else

1590 char format[] = "output_%ld.dat";

1591 #endif

1592

1593 snprintf(path, path_len, format, time(NULL));

1594 }

1595 for (unsigned int i = 0; i < path_len && path[i] && ptr; i++) {

1596 char c = path[i];

1597 if ((path[i] == '.' && path[i+1] == '.') ||

1598 !(c == '.' ||

1599 (c >= 'A' && c <= 'Z') ||

1600 (c >= 'a' && c <= 'z') ||

1601 (c >= '0' && c <= '9')

1602 ))

1603 path[i] = '_';

1604 }

1605 #ifdef SANDBOX_SUN

1606 if (sandbox_download(tab, path))

1607 return -1;

1608 int dfd = wr_pair[1];

1609 sandbox_dl_length(-1);

1610 #else

1611 int dfd = openat(fd, path,

1612 O_CREAT|O_EXCL|O_WRONLY, 0600);

1613 if (dfd < 0 && errno == EEXIST) {

1614 char buf[1024];

1615 #ifdef __OpenBSD__

1616 char format[] = "%lld_%s";

1617 #else

1618 char format[] = "%ld_%s";

1619 #endif

1620 snprintf(buf, sizeof(buf), format, time(NULL), path);

1621 strlcpy(path, buf, path_len);

1622 dfd = openat(fd, path,

1623 O_CREAT|O_EXCL|O_WRONLY, 0600);

1624 if (dfd < 0) {

1625 snprintf(tab->error, sizeof(tab->error),

1626 "Failed to write to the download folder");

1627 tab->show_error = 1;

1628 return -1;

1629 }

1630 }

1631 #ifdef SANDBOX_FREEBSD

1632 if (makefd_writeonly(dfd)) {

1633 client.shutdown = 1;

1634 return -1;

1635 }

1636 #endif

1637 #endif

1638 return dfd;

1639 }

1640

1641 int gmi_postdownload(struct gmi_tab* tab, int fd, const char* path) {

1642

1643 #ifdef SANDBOX_SUN

1644 if (wr_pair[1] != fd)

1645 return -1;

1646 #else

1647 close(fd);

1648 #endif

1649 #ifndef DISABLE_XDG

1650 int fail = 0;

1651 if (client.xdg) {

1652 tab->request.ask = 2;

1653 snprintf(tab->request.info,

1654 sizeof(tab->request.info),

1655 "# %s download",

1656 tab->request.meta);

1657 strlcpy(tab->request.action, "open",

1658 sizeof(tab->request.action));

1659 tb_interupt();

1660 while (tab->request.ask == 2)

1661 nanosleep(&timeout, NULL);

1662 if (tab->request.ask)

1663 fail = xdg_open(path);

1664 }

1665 if (fail) {

1666 tab->show_error = 1;

1667 snprintf(tab->error, sizeof(tab->info),

1668 "Failed to open %s", path);

1669 } else

1670 #endif

1671 {

1672 tab->show_info = 1;

1673 snprintf(tab->info, sizeof(tab->info),

1674 "File downloaded to %s", path);

1675 }

1676 return fail;

1677 }

1678

1679 void* gmi_request_thread(struct gmi_tab* tab) {

1680 unsigned int signal = 0;

1681 while (!client.shutdown) {

1682 tab->selected = 0;

1683 tab->request.state = STATE_DONE;

1684 if (tab->page.code != 30 && tab->page.code != 31)

1685 tb_interupt();

1686 if (recv(tab->thread.pair[0], &signal, 4, 0) != 4 ||

1687 client.shutdown || signal == 0xFFFFFFFF) break;

1688 bzero(&tab->request, sizeof(tab->request));

1689 tab->request.state = STATE_STARTED;

1690 int ret = gmi_request_init(tab, tab->thread.url,

1691 tab->thread.add);

1692 if (tab->request.state == STATE_CANCEL ||

1693 ret || gmi_request_dns(tab)) {

1694 if (tab->request.tls) {

1695 tls_close(tab->request.tls);

1696 tls_free(tab->request.tls);

1697 tab->request.tls = NULL;

1698 }

1699 if (ret == -1)

1700 tab->show_error = 1;

1701 tab->request.recv = ret;

1702 continue;

1703 }

1704 if (gmi_request_connect(tab) ||

1705 tab->request.state == STATE_CANCEL) {

1706 close(tab->request.socket);

1707 if (tab->request.tls) {

1708 tls_close(tab->request.tls);

1709 tls_free(tab->request.tls);

1710 tab->request.tls = NULL;

1711 }

1712 tab->show_error = 1;

1713 tab->request.recv = -1;

1714 continue;

1715 }

1716 ret = -1;

1717 if (!gmi_request_handshake(tab) &&

1718 tab->request.state != STATE_CANCEL) {

1719 ret = gmi_request_header(tab);

1720 if (!ret || ret == 2) {

1721 char out[1024];

1722 int fd = -1;

1723 if (tab->request.download)

1724 fd = gmi_getfd(tab, out, sizeof(out));

1725 if (gmi_request_body(tab, fd)) {

1726 close(fd);

1727 tab->show_error = 1;

1728 free(tab->request.data);

1729 continue;

1730 }

1731 if (fd > 0)

1732 gmi_postdownload(tab, fd, out);

1733 }

1734 }

1735

1736 if (ret == -2) {

1737 char* cr = strchr(tab->request.error, '\r');

1738 if (cr) {

1739 *cr = '\0';

1740 char buf[256];

1741 snprintf(buf, sizeof(buf),

1742 "%s (%d : %s)", tab->request.error,

1743 tab->page.code, tab->error);

1744 strlcpy(tab->error, buf, sizeof(tab->error));

1745 *cr = '\r';

1746 }

1747 }

1748 if (ret == -1 || ret == -2) {

1749 free(tab->request.data);

1750 tab->request.data = NULL;

1751 if (tab->history)

1752 strlcpy(tab->url, tab->history->url,

1753 sizeof(tab->url));

1754 else

1755 strlcpy(tab->url, "about:home",

1756 sizeof(tab->url));

1757 tab->show_error = 1;

1758 tab->request.recv = -1;

1759 tab->page.code = 20;

1760 }

1761

1762 if (tab->request.socket != -1)

1763 close(tab->request.socket);

1764 if (tab->request.tls) {

1765 tls_close(tab->request.tls);

1766 tls_free(tab->request.tls);

1767 tab->request.tls = NULL;

1768 }

1769 if (tab->request.recv > 0 &&

1770 (tab->page.code == 11 || tab->page.code == 10)) {

1771 free(tab->request.data);

1772 tab->request.data = NULL;

1773 strlcpy(tab->url, tab->request.url, sizeof(tab->url));

1774 continue;

1775 }

1776 if (tab->request.recv > 0 &&

1777 (tab->page.code == 31 || tab->page.code == 30)) {

1778 tab->request.data[tab->request.recv] = '\0';

1779 char* ptr = strchr(tab->request.data, ' ');

1780 if (!ptr) {

1781 free(tab->request.data);

1782 continue;

1783 }

1784 char* ln = strchr(ptr + 1, ' ');

1785 if (ln) *ln = '\0';

1786 ln = strchr(ptr, '\n');

1787 if (ln) *ln = '\0';

1788 ln = strchr(ptr, '\r');

1789 if (ln) *ln = '\0';

1790 strlcpy(tab->url, tab->request.url, sizeof(tab->url));

1791 int r = gmi_nextlink(tab, tab->url, ptr + 1);

1792 if (r < 1 || tab->page.code != 20) {

1793 free(tab->request.data);

1794 continue;

1795 }

1796 tab->page.data_len = r;

1797 free(tab->request.data);

1798 tab->request.data = NULL;

1799 gmi_load(&tab->page);

1800 tab->history->page = tab->page;

1801 tab->history->cached = 1;

1802 tab->request.recv = r;

1803 continue;

1804 }

1805 if (!tab->request.download &&

1806 tab->request.recv > 0 &&

1807 tab->page.code == 20) {

1808 tab->search.entry[0] = '\0';

1809 struct gmi_link* link_ptr = tab->history?

1810 tab->history->prev:NULL;

1811 for (int i = 0; i < MAX_CACHE; i++) {

1812 if (!link_ptr) break;

1813 link_ptr = link_ptr->prev;

1814 }

1815 while (link_ptr) {

1816 if (link_ptr->cached) {

1817 gmi_freepage(&link_ptr->page);

1818 link_ptr->cached = 0;

1819 }

1820 link_ptr = link_ptr->prev;

1821 }

1822 pthread_mutex_lock(&tab->render_mutex);

1823 if (!tab->thread.add)

1824 gmi_freepage(&tab->page);

1825 bzero(&tab->page, sizeof(struct gmi_page));

1826 tab->page.code = 20;

1827 strlcpy(tab->page.meta, tab->request.meta,

1828 sizeof(tab->page.meta));

1829 strlcpy(tab->url, tab->request.url,

1830 sizeof(tab->url));

1831 tab->page.data_len = tab->request.recv;

1832 tab->page.data = tab->request.data;

1833 gmi_load(&tab->page);

1834 if (tab->thread.add)

1835 gmi_addtohistory(tab);

1836 else if (tab->history) {

1837 tab->history->page = tab->page;

1838 tab->history->cached = 1;

1839 }

1840 tab->scroll = -1;

1841 pthread_mutex_unlock(&tab->render_mutex);

1842 }

1843 }

1844 return NULL;

1845 }

1846

1847 int gmi_request(struct gmi_tab *tab, const char *url, int add) {

1848 if (tab->request.state != STATE_DONE)

1849 tab->request.state = STATE_CANCEL;

1850 for (int i = 0; i < 5 && tab->request.state == STATE_CANCEL; i++)

1851 nanosleep(&timeout, NULL);

1852 tab->thread.add = add;

1853 strlcpy(tab->thread.url, url, sizeof(tab->thread.url));

1854 int signal = 0;

1855 if (send(tab->thread.pair[1], &signal, sizeof(signal), 0) !=

1856 sizeof(signal))

1857 return -1;

1858 return 0;

1859 }

1860

1861 int gmi_loadfile(struct gmi_tab* tab, char* path) {

1862 FILE* f = fopen(path, "rb");

1863 if (!f) {

1864 snprintf(tab->error, sizeof(tab->error),

1865 "Failed to open %s", path);

1866 tab->show_error = 1;

1867 return -1;

1868 }

1869 #ifdef SANDBOX_FREEBSD

1870 if (make_readonly(f)) {

1871 fclose(f);

1872 client.shutdown = 1;

1873 return -1;

1874 }

1875 #endif

1876 fseek(f, 0, SEEK_END);

1877 size_t len = ftell(f);

1878 fseek(f, 0, SEEK_SET);

1879 char* data = malloc(len);

1880 if (!data) {

1881 fclose(f);

1882 memory_failure(tab);

1883 return -1;

1884 }

1885 if (len != fread(data, 1, len, f)) {

1886 fclose(f);

1887 free(data);

1888 return -1;

1889 }

1890 fclose(f);

1891 pthread_mutex_lock(&tab->render_mutex);

1892 tab->page.code = 20;

1893 tab->page.data_len = len;

1894 if (tab->page.data) free(tab->page.data);

1895 tab->page.data = data;

1896 snprintf(tab->url, sizeof(tab->url), "file://%s/", path);

1897 int i = strnlen(path, 1024);

1898 int gmi = 0;

1899 if (i > 4 && path[i - 1] == '/') i--;

1900 if (i > 4 && path[i - 4] == '.' && path[i - 3] == 'g' &&

1901 path[i - 2] == 'm' && path[i - 1] == 'i')

1902 gmi = 1;

1903 if (gmi)

1904 strlcpy(tab->page.meta, "text/gemini", sizeof(tab->page.meta));

1905 else

1906 strlcpy(tab->page.meta, "text/text", sizeof(tab->page.meta));

1907 tab->page.no_header = 1;

1908 gmi_load(&tab->page);

1909 gmi_addtohistory(tab);

1910 pthread_mutex_unlock(&tab->render_mutex);

1911 return len;

1912 }

1913

1914 int gmi_init() {

1915 if (tls_init()) {

1916 tb_shutdown();

1917 printf("Failed to initialize TLS\n");

1918 return -1;

1919 }

1920

1921 config = tls_config_new();

1922 if (!config) {

1923 tb_shutdown();

1924 printf("Failed to initialize TLS config\n");

1925 return -1;

1926 }

1927

1928 config_empty = tls_config_new();

1929 if (!config_empty) {

1930 tb_shutdown();

1931 printf("Failed to initialize TLS config\n");

1932 return -1;

1933 }

1934

1935 tls_config_insecure_noverifycert(config);

1936 tls_config_insecure_noverifycert(config_empty);

1937 #ifndef DISABLE_XDG

1938 int xdg = client.xdg;

1939 #endif

1940 #ifdef SANDBOX_SUN

1941 char** bookmarks = client.bookmarks;

1942 #endif

1943 bzero(&client, sizeof(client));

1944 #ifdef SANDBOX_SUN

1945 client.bookmarks = bookmarks;

1946 #endif

1947 #ifndef DISABLE_XDG

1948 client.xdg = xdg;

1949 #endif

1950

1951 int fd = getconfigfd();

1952 if (fd < 0) {

1953 tb_shutdown();

1954 printf("Failed to open config folder\n");

1955 return -1;

1956 }

1957

1958 #ifndef SANDBOX_SUN

1959 if (gmi_loadbookmarks()) {

1960 gmi_newbookmarks();

1961 }

1962

1963 if (cert_load()) {

1964 tb_shutdown();

1965 printf("Failed to load known hosts\n");

1966 return -1;

1967 }

1968 #endif

1969

1970 return 0;

1971 }

1972

1973 void gmi_free() {

1974 while (client.tab)

1975 gmi_freetab(client.tab);

1976 gmi_savebookmarks();

1977 for (int i = 0; client.bookmarks[i]; i++)

1978 free(client.bookmarks[i]);

1979 free(client.bookmarks);

1980 cert_free();

1981 }

1982