💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 8e11c777cf661d37c99885ddde015… captured on 2023-09-08 at 16:22:47. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

Go Back

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 #include "util.h"

33

34 #define MAX_CACHE 10

35 #define TIMEOUT 8

36 struct timespec timeout = {0, 10000000};

37

38 struct tls_config* config;

39 struct tls_config* config_empty;

40 struct gmi_client client;

41

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

43 void* gmi_request_thread(struct gmi_tab* tab);

44

45 void fatal() {

46 tb_shutdown();

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

48 exit(0);

49 }

50

51 int fatalI() {

52 fatal();

53 return -1;

54 }

55

56 void* fatalP() {

57 fatal();

58 return NULL;

59 }

60

61 void memory_failure(struct gmi_tab* tab) {

62 if (!tab) return;

63 tab->show_error = 1;

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

65 "memory allocation failure");

66 }

67

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

69 id--;

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

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

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

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

74 tab->show_error = 1;

75 client.input.mode = 0;

76 return -1;

77 }

78 gmi_cleanforward(tab);

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

80 return ret;

81 }

82

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

84 id--;

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

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

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

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

89 tab->show_error = 1;

90 client.input.mode = 0;

91 return -1;

92 }

93 struct gmi_tab* old_tab = client.tab;

94 client.tab = gmi_newtab_url(NULL);

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

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

97 if (ret) gmi_freetab(client.tab);

98

99 return ret;

100 }

101

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

103 int url_len = strnlen(url, MAX_URL);

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

105 gmi_gohome(tab, 1);

106 return 0;

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

108 char buf[1024];

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

110 int len = STRNLEN(buf);

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

112 buf[len] = '/';

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

114 }

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

116 if (ret < 1) return ret;

117 return ret;

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

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

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

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

122 }

123 char urlbuf[MAX_URL];

124 size_t l = STRLCPY(urlbuf, url);

125 if (l >= sizeof(urlbuf))

126 goto nextlink_overflow;

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

128 sizeof(urlbuf) - l)

129 goto nextlink_overflow;

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

131 return ret;

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

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

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

135 #ifndef DISABLE_XDG

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

137 #endif

138 tab->show_error = 1;

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

140 "Unable to open the link");

141 return -1;

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

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

144 return ret;

145 } else {

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

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

148 char urlbuf[MAX_URL];

149 size_t l = STRLCPY(urlbuf, url);

150 if (l >= sizeof(urlbuf))

151 goto nextlink_overflow;

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

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

154 sizeof(urlbuf) - l);

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

156 goto nextlink_overflow;

157 l += l2;

158 }

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

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

161 goto nextlink_overflow;

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

163 return ret;

164 }

165 nextlink_overflow:

166 tab->show_error = 1;

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

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

169 return -1;

170 }

171

172 void gmi_load(struct gmi_page* page) {

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

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

175 free(page->links);

176 page->links = NULL;

177 page->links_count = 0;

178 page->lines = 0;

179 if (!STARTWITH(page->meta, "text/gemini")) {

180 page->links = NULL;

181 page->links_count = 0;

182 page->lines = 0;

183 return;

184 }

185 int x = 0;

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

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

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

189 c += 2;

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

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

192 c++) ;

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

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

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

196 c--;

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

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

199 void *ptr = NULL;

200 while (!ptr) {

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

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

203 malloc(sizeof(char*));

204 if (!ptr) sleep(1);

205 }

206 page->links = ptr;

207

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

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

210 page->data[c] = save;

211 continue;

212 }

213 int len = strnlen(url, MAX_URL);

214 ptr = NULL;

215 while (!ptr) {

216 ptr = malloc(len+2);

217 if (!ptr) sleep(1);

218 }

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

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

221 page->links_count++;

222 page->data[c] = save;

223 }

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

225 page->lines++;

226 x = 0;

227 continue;

228 }

229 x++;

230 }

231 }

232

233 int gmi_render(struct gmi_tab* tab) {

234 pthread_mutex_lock(&tab->render_mutex);

235 #include "img.h"

236 #ifdef TERMINAL_IMG_VIEWER

237 if (STARTWITH(tab->page.meta, "image/")) {

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

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

240 if (!ptr) {

241 tb_printf(2, -tab->scroll,

242 TB_DEFAULT, TB_DEFAULT,

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

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

245 pthread_mutex_unlock(&tab->render_mutex);

246 return 1;

247 }

248 ptr++;

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

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

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

252 }

253 tab->page.img.data =

254 stbi_load_from_memory((unsigned char*)ptr,

255 tab->page.data_len -

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

257 &tab->page.img.w,

258 &tab->page.img.h,

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

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

261 tb_printf(2, -tab->scroll,

262 TB_DEFAULT, TB_DEFAULT,

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

264 tab->page.meta);

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

266 pthread_mutex_unlock(&tab->render_mutex);

267 return 1;

268 }

269

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

271 }

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

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

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

275 client.tabs_count>1);

276 pthread_mutex_unlock(&tab->render_mutex);

277 return 1;

278 }

279 }

280 #endif

281 int text = 0;

282 if (!STARTWITH(tab->page.meta, "text/gemini")) {

283 if (!STARTWITH(tab->page.meta, "text/")) {

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

285 "Unable to render format : %s",

286 tab->page.meta);

287 pthread_mutex_unlock(&tab->render_mutex);

288 return 1;

289 }

290 text = 1;

291 }

292 int line = 0;

293 int x = 0;

294 int links = 0;

295 uintattr_t color = TB_DEFAULT;

296 int start = 1;

297 int ignore = 0;

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

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

300 &client.input.field[1] : 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);

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 (!STARTWITH(page->meta, "text/gemini")) {

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 sanitize(page->title, len);

770 return;

771 }

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

773 if (!str) {

774 len = STRLCPY(page->title, url);

775 goto sanitize;

776 }

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

778 str++;

779 len = STRLCPY(page->title, str);

780 sanitize:

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

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

783 page->title[i] = 0;

784 }

785

786 int gmi_removebookmark(int index) {

787 index--;

788 if (index < 0) return -1;

789 int fail = -1;

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

791 if (i == index) {

792 free(client.bookmarks[i]);

793 fail = 0;

794 }

795 if (!fail)

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

797 }

798 return fail;

799 }

800

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

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

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

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

805 tab->show_error = 1;

806 return;

807 }

808 int title_len = 0;

809 if (!title) {

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

811 title = tab->page.title;

812 title_len = STRNLEN(tab->page.title);

813 if (!title_len) {

814 title_len = sizeof("no title");

815 title = "no title";

816 }

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

818 long n = 0;

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

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

821 if (!ptr) {

822 memory_failure(tab);

823 return;

824 }

825 client.bookmarks = ptr;

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

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

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

829 memory_failure(tab);

830 return;

831 }

832 if (title)

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

834 else

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

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

837 gmi_savebookmarks();

838 }

839

840 int gmi_savebookmarks() {

841 #ifdef SANDBOX_SUN

842 int fd = wr_pair[1];

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

844 return -1;

845 #else

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

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

848 if (fd < 0) {

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

850 return -1;

851 }

852 #ifdef SANDBOX_FREEBSD

853 if (makefd_writeonly(fd)) {

854 close(fd);

855 return -1;

856 }

857 #endif

858 #endif

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

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

861 char c = '\n';

862 write(fd, &c, 1);

863 }

864 #ifdef SANDBOX_SUN

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

866 #else

867 close(fd);

868 #endif

869 return 0;

870 }

871

872 char* gmi_getbookmarks(int* len) {

873 char* data = NULL;

874 int n = 0;

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

876 char line[2048];

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

878 client.bookmarks[i]);

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

880 if (!ptr) {

881 *len = n;

882 return data;

883 }

884 data = ptr;

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

886 n += length-1;

887 }

888 *len = n;

889 return data;

890 }

891

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

893 STRLCPY(tab->url, "about:home");

894 int bm = 0;

895 char* data = gmi_getbookmarks(&bm);

896 pthread_mutex_lock(&tab->render_mutex);

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

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

899 memory_failure(tab);

900 pthread_mutex_unlock(&tab->render_mutex);

901 return;

902 }

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

904

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

906 home_page, data ? data : "");

907 free(data);

908

909 STRLCPY(tab->request.meta, "text/gemini");

910

911 if (!add)

912 gmi_freepage(&tab->page);

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

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

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

916 tab->page.code = 20;

917 STRLCPY(tab->page.meta, tab->request.meta);

918 gmi_load(&tab->page);

919 if (add)

920 gmi_addtohistory(tab);

921 else if (tab->history) {

922 tab->history->page = tab->page;

923 tab->history->cached = 1;

924 }

925 tab->scroll = -1;

926 tab->request.data = NULL;

927 pthread_mutex_unlock(&tab->render_mutex);

928 }

929

930 struct gmi_tab* gmi_newtab() {

931 return gmi_newtab_url("about:home");

932 }

933

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

935

936 if (url && strcmp(url, "about:home")) {

937 struct gmi_tab tmp;

938 int proto = parse_url(url,

939 tmp.request.host, sizeof(tmp.request.host),

940 tmp.request.url, sizeof(tmp.request.url),

941 &tmp.request.port);

942 switch (proto) {

943 case PROTO_GEMINI:

944 break;

945 case PROTO_HTTP:

946 case PROTO_HTTPS:

947 case PROTO_GOPHER:

948 xdg_request(url);

949 /* fallthrough */

950 default:

951 return client.tab;

952 }

953 }

954

955 client.tabs_count++;

956 if (client.tab) {

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

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

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

960 memory_failure(client.tab);

961 return client.tab;

962 }

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

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

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

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

967 if (next)

968 next->prev = client.tab;

969 } else {

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

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

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

973 }

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

975

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

977 return client.tab;

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

979 (void *(*)(void *))gmi_request_thread,

980 (void*)client.tab);

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

982 if (url)

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

984

985 client.tab->scroll = -1;

986 return client.tab;

987 }

988

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

990 if (!tab) return -1;

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

992 gmi_gohome(tab, add);

993 return tab->page.data_len;

994 }

995 tab->show_error = 0;

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

997 tab->selected = 0;

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

999 int proto = parse_url(url,

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

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

1002 &tab->request.port);

1003

1004 if (proto == -2) {

1005 tab->show_error = 1;

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

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

1008 return -1;

1009 }

1010 if (proto == PROTO_FILE) {

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

1012 }

1013 if (proto != PROTO_GEMINI) {

1014 #ifndef DISABLE_XDG

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

1016 #endif

1017 tab->show_error = 1;

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

1019 "Unable to open the link");

1020 return -1;

1021 }

1022 if (tab->request.tls)

1023 tls_reset(tab->request.tls);

1024 else

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

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

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

1028 "Failed to initialize TLS");

1029 tab->show_error = 1;

1030 return -1;

1031 }

1032

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

1034 if (cert >= 0 &&

1035 tls_config_set_keypair_mem(config,

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

1037 client.certs[cert].crt_len,

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

1039 client.certs[cert].key_len))

1040 {

1041 tab->show_error = 1;

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

1043 tls_config_error(config));

1044 return -1;

1045 }

1046

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

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

1049 "Failed to configure TLS");

1050 tab->show_error = 1;

1051 return -1;

1052 }

1053 tab->request.state = STATE_REQUESTED;

1054 return 0;

1055 }

1056

1057 #ifdef __linux

1058 void dns_async(union sigval sv) {

1059 struct gmi_tab* tab = sv.sival_ptr;

1060 tab->request.resolved = 1;

1061 }

1062 #endif

1063

1064 int gmi_request_dns(struct gmi_tab* tab) {

1065 char host[1024];

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

1067 host, sizeof(host))) {

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

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

1070 tab->show_error = 1;

1071 return -1;

1072 }

1073 STRLCPY(tab->request.host, host);

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

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

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

1077 memory_failure(tab);

1078 return -1;

1079 }

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

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

1082 tab->request.resolved = 0;

1083 struct sigevent sevp;

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

1085 sevp.sigev_notify = SIGEV_THREAD;

1086 sevp.sigev_notify_function = dns_async;

1087 sevp.sigev_value.sival_ptr = tab;

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

1089 if (ret) {

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

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

1092 tab->show_error = 1;

1093 return -1;

1094 }

1095

1096 long start = time(NULL);

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

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

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

1100 nanosleep(&timeout, NULL);

1101 }

1102

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

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

1105 gai_cancel(tab->request.gaicb_ptr);

1106 free(tab->request.gaicb_ptr);

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

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

1109 tab->show_error = 1;

1110 return -1;

1111 }

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

1113 #else

1114 struct addrinfo hints, *result;

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

1116 hints.ai_family = AF_INET;

1117 hints.ai_socktype = SOCK_STREAM;

1118 hints.ai_flags |= AI_CANONNAME;

1119 errno = 0;

1120

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

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

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

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

1125 tab->show_error = 1;

1126 return -1;

1127 }

1128 #endif

1129

1130 struct sockaddr_in addr4;

1131 struct sockaddr_in6 addr6;

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

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

1134

1135 int error = 0;

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

1137 addr4.sin_addr =

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

1139 addr4.sin_family = AF_INET;

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

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

1142 tab->request.addr =

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

1144 }

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

1146 addr6.sin6_addr =

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

1148 addr6.sin6_family = AF_INET6;

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

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

1151 tab->request.addr =

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

1153 } else {

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

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

1156 tab->request.host);

1157 error = 1;

1158 }

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

1160 freeaddrinfo(result);

1161 #ifdef __linux__

1162 free(tab->request.gaicb_ptr);

1163 #endif

1164 if (error) {

1165 tab->request.state = STATE_CANCEL;

1166 return -1;

1167 }

1168

1169 tab->request.state = STATE_DNS;

1170 return 0;

1171 }

1172

1173 int gmi_request_connect(struct gmi_tab* tab) {

1174

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

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

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

1178 "Failed to create socket");

1179 return -1;

1180 }

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

1182 if (flags == -1 ||

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

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

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

1186 return -1;

1187 }

1188

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

1190 sizeof(struct sockaddr_in):

1191 sizeof(struct sockaddr_in6);

1192

1193 int connected = 0;

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

1195 tab->request.addr, addr_size);

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

1197 errno != EINPROGRESS && errno != EALREADY &&

1198 errno != 0):failed;

1199 while (!failed) {

1200 struct pollfd fds[2];

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

1202 fds[0].events = POLLIN;

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

1204 fds[1].events = POLLOUT;

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

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

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

1208 int value;

1209 socklen_t len = sizeof(value);

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

1211 SO_ERROR, &value, &len);

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

1213 break;

1214 }

1215

1216 if (!connected) {

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

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

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

1220 return -1;

1221 }

1222

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

1224 tab->request.host)) {

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

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

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

1228 return -1;

1229 }

1230 return 0;

1231 }

1232

1233 int gmi_request_handshake(struct gmi_tab* tab) {

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

1235 tab->request.socket,

1236 tab->request.host)) {

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

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

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

1240 return -1;

1241 }

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

1243 int ret = 0;

1244 time_t start = time(0);

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

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

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

1248 (ret < 0 &&

1249 ret != TLS_WANT_POLLIN &&

1250 ret != TLS_WANT_POLLOUT))

1251 break;

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

1253 nanosleep(&timeout, NULL);

1254 }

1255 if (ret) {

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

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

1258 tls_error(tab->request.tls),

1259 tab->request.host);

1260 return -1;

1261 }

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

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

1264 tls_peer_cert_hash(tab->request.tls),

1265 tls_peer_cert_notbefore(tab->request.tls),

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

1267 switch (ret) {

1268 case 0: // success

1269 break;

1270 case -1: // invalid certificate

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

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

1273 "(The certificate changed)",

1274 tab->request.host);

1275 return -1;

1276 case -3: // sandbox error

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

1278 "Sandbox error when verifying server certificate " \

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

1280 return -1;

1281 case -5: // expired

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

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

1284 "Expired certificate, " \

1285 "the certificate for %s has expired",

1286 tab->request.host);

1287 return -1;

1288 case -6: // certificate changed

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

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

1291 " to forget the old certificate.",

1292 tab->request.host);

1293 return -1;

1294 default: // failed to write certificate

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

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

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

1298 return -1;

1299 }

1300

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

1302 char buf[MAX_URL];

1303 STRLCPY(buf, "gemini://");

1304 char toascii[1024];

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

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

1307 toascii, sizeof(toascii))) {

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

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

1310 return -1;

1311 }

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

1313 char url[1024];

1314 if (len < MAX_URL)

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

1316 if (len >= MAX_URL ||

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

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

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

1320 return -1;

1321 }

1322

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

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

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

1326 return -1;

1327 }

1328

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

1330 tab->request.state = STATE_CONNECTED;

1331 return 0;

1332 }

1333

1334 int gmi_request_header(struct gmi_tab* tab) {

1335 time_t now = time(0);

1336 char buf[1024];

1337 int recv = 0;

1338 while (1) {

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

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

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

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

1343 tab->request.host);

1344 return -1;

1345 }

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

1347 sizeof(buf) - recv - 1);

1348 if (n == TLS_WANT_POLLIN) {

1349 nanosleep(&timeout, NULL);

1350 continue;

1351 }

1352 if (n < 1) {

1353 recv = -1;

1354 break;

1355 }

1356 recv += n;

1357 buf[recv] = '\0';

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

1359 }

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

1361

1362 if (recv <= 0) {

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

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

1365 recv, tab->request.host);

1366 return -1;

1367 }

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

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

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

1371 return -1;

1372 }

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

1374 if (!ptr) {

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

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

1377 tab->request.host);

1378 return -1;

1379 }

1380 STRLCPY(tab->request.error, ptr + 1);

1381

1382 int previous_code = tab->page.code;

1383 char c = buf[2];

1384 buf[2] = '\0';

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

1386 buf[2] = c;

1387

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

1389 switch (tab->page.code) {

1390 case 10:

1391 case 11:

1392 ptr++;

1393 STRLCPY(client.input.label, ptr);

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

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

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

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

1398 tab->request.state = STATE_RECV_HEADER;

1399 tab->request.recv = recv;

1400 return 2; // input

1401 case 20:

1402 {

1403 #ifdef TERMINAL_IMG_VIEWER

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

1405 #endif

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

1407 if (!meta) {

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

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

1410 return -1;

1411 }

1412 *meta = '\0';

1413 STRLCPY(tab->request.meta, ptr);

1414 *meta = '\r';

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

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

1417 || ((!STARTWITH(tab->request.meta, "text/"))

1418 #ifdef TERMINAL_IMG_VIEWER

1419 && (!STARTWITH(tab->request.meta, "image/"))

1420 #endif

1421 )) {

1422 if (tab->history)

1423 STRLCPY(tab->url, tab->history->url);

1424 else

1425 STRLCPY(tab->url, "about:home");

1426 tab->request.ask = 2;

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

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

1429 tab->request.meta);

1430 STRLCPY(tab->request.action, "download");

1431 tb_interupt();

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

1433 nanosleep(&timeout, NULL);

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

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

1436 tab->request.recv = -1;

1437 return 1; // download

1438 }

1439 }

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

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

1442 }

1443 break;

1444 case 30:

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

1446 "Redirect temporary");

1447 break;

1448 case 31:

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

1450 "Redirect permanent");

1451 break;

1452 case 40:

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

1454 "Temporary failure");

1455 return -2;

1456 case 41:

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

1458 "Server unavailable");

1459 return -2;

1460 case 42:

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

1462 "CGI error");

1463 return -2;

1464 case 43:

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

1466 "Proxy error");

1467 return -2;

1468 case 44:

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

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

1471 return -2;

1472 case 50:

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

1474 "Permanent failure");

1475 return -2;

1476 case 51:

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

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

1479 return -2;

1480 case 52:

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

1482 "Resource gone");

1483 return -2;

1484 case 53:

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

1486 "Proxy request refused");

1487 return -2;

1488 case 59:

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

1490 "Bad request");

1491 return -2;

1492 case 60:

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

1494 "Client certificate required");

1495 return -2;

1496 case 61:

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

1498 "Client not authorised");

1499 return -2;

1500 case 62:

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

1502 "Client certificate not valid");

1503 return -2;

1504 default:

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

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

1507 tab->page.code = previous_code;

1508 return -2;

1509 }

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

1511 tab->request.state = STATE_RECV_HEADER;

1512 tab->request.recv = recv;

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

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

1515 memory_failure(tab);

1516 return -1;

1517 }

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

1519 return 0;

1520 }

1521

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

1523 time_t now = time(0);

1524 char buf[1024];

1525 int isText = STARTWITH(tab->request.meta, "text/");

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

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

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

1529 "Server %s stopped responding",

1530 tab->request.host);

1531 return -1;

1532 }

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

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

1535 if (bytes == 0) break;

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

1537 nanosleep(&timeout, NULL);

1538 continue;

1539 }

1540 if (bytes < 1) {

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

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

1543 tab->request.host,

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

1545 return -1;

1546 }

1547 if (fd > 0) {

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

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

1550 "Failed to download file: %s",

1551 strerror(errno));

1552 return -1;

1553 }

1554 now = time(0);

1555 continue;

1556 }

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

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

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

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

1561 if (!ptr) {

1562 free(tab->request.data);

1563 tab->request.data = NULL;

1564 memory_failure(tab);

1565 return -1;

1566 }

1567 tab->request.data = ptr;

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

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

1570 tab->request.recv += bytes;

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

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

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

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

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

1576 return -1;

1577 }

1578 now = time(0);

1579 }

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

1581 tab->request.state = STATE_RECV_BODY;

1582 return 0;

1583 }

1584

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

1586 int len = STRNLEN(tab->request.url);

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

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

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

1590 #ifndef SANDBOX_SUN

1591 int fd = getdownloadfd();

1592 if (fd < 0) {

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

1594 "Unable to open download folder");

1595 tab->show_error = 1;

1596 return -1;

1597 }

1598 #endif

1599 if (ptr)

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

1601 else {

1602 #ifdef __OpenBSD__

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

1604 #else

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

1606 #endif

1607

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

1609 }

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

1611 char c = path[i];

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

1613 !(c == '.' ||

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

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

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

1617 ))

1618 path[i] = '_';

1619 }

1620 #ifdef SANDBOX_SUN

1621 if (sandbox_download(tab, path))

1622 return -1;

1623 int dfd = wr_pair[1];

1624 sandbox_dl_length(-1);

1625 #else

1626 int dfd = openat(fd, path,

1627 O_CREAT|O_EXCL|O_WRONLY, 0600);

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

1629 char buf[1024];

1630 #ifdef __OpenBSD__

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

1632 #else

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

1634 #endif

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

1636 strlcpy(path, buf, path_len);

1637 dfd = openat(fd, path,

1638 O_CREAT|O_EXCL|O_WRONLY, 0600);

1639 if (dfd < 0) {

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

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

1642 tab->show_error = 1;

1643 return -1;

1644 }

1645 }

1646 #ifdef SANDBOX_FREEBSD

1647 if (makefd_writeonly(dfd)) {

1648 client.shutdown = 1;

1649 return -1;

1650 }

1651 #endif

1652 #endif

1653 return dfd;

1654 }

1655

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

1657

1658 #ifdef SANDBOX_SUN

1659 if (wr_pair[1] != fd)

1660 return -1;

1661 #else

1662 close(fd);

1663 #endif

1664 #ifndef DISABLE_XDG

1665 int fail = 0;

1666 if (client.xdg) {

1667 tab->request.ask = 2;

1668 snprintf(tab->request.info,

1669 sizeof(tab->request.info),

1670 "# %s download",

1671 tab->request.meta);

1672 STRLCPY(tab->request.action, "open");

1673 tb_interupt();

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

1675 nanosleep(&timeout, NULL);

1676 if (tab->request.ask)

1677 fail = xdg_open(path);

1678 }

1679 if (fail) {

1680 tab->show_error = 1;

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

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

1683 } else

1684 #endif

1685 {

1686 tab->show_info = 1;

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

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

1689 }

1690 return fail;

1691 }

1692

1693 void* gmi_request_thread(struct gmi_tab* tab) {

1694 unsigned int signal = 0;

1695 while (!client.shutdown) {

1696 tab->selected = 0;

1697 tab->request.state = STATE_DONE;

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

1699 tb_interupt();

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

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

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

1703 tab->request.state = STATE_STARTED;

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

1705 tab->thread.add);

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

1707 ret || gmi_request_dns(tab)) {

1708 if (tab->request.tls) {

1709 tls_close(tab->request.tls);

1710 tls_free(tab->request.tls);

1711 tab->request.tls = NULL;

1712 }

1713 if (ret == -1)

1714 tab->show_error = 1;

1715 tab->request.recv = ret;

1716 continue;

1717 }

1718 if (gmi_request_connect(tab) ||

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

1720 close(tab->request.socket);

1721 if (tab->request.tls) {

1722 tls_close(tab->request.tls);

1723 tls_free(tab->request.tls);

1724 tab->request.tls = NULL;

1725 }

1726 tab->show_error = 1;

1727 tab->request.recv = -1;

1728 continue;

1729 }

1730 ret = -1;

1731 if (!gmi_request_handshake(tab) &&

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

1733 ret = gmi_request_header(tab);

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

1735 char out[1024];

1736 int fd = -1;

1737 if (tab->request.download)

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

1739 if (gmi_request_body(tab, fd)) {

1740 close(fd);

1741 tab->show_error = 1;

1742 free(tab->request.data);

1743 continue;

1744 }

1745 if (fd > 0)

1746 gmi_postdownload(tab, fd, out);

1747 }

1748 }

1749

1750 if (ret == -2) {

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

1752 if (cr) {

1753 *cr = '\0';

1754 char buf[256];

1755 snprintf(buf, sizeof(buf),

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

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

1758 STRLCPY(tab->error, buf);

1759 *cr = '\r';

1760 }

1761 }

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

1763 free(tab->request.data);

1764 tab->request.data = NULL;

1765 if (tab->history)

1766 STRLCPY(tab->url, tab->history->url);

1767 else

1768 STRLCPY(tab->url, "about:home");

1769 tab->show_error = 1;

1770 tab->request.recv = -1;

1771 tab->page.code = 20;

1772 }

1773

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

1775 close(tab->request.socket);

1776 if (tab->request.tls) {

1777 tls_close(tab->request.tls);

1778 tls_free(tab->request.tls);

1779 tab->request.tls = NULL;

1780 }

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

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

1783 free(tab->request.data);

1784 tab->request.data = NULL;

1785 STRLCPY(tab->url, tab->request.url);

1786 continue;

1787 }

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

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

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

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

1792 if (!ptr) {

1793 free(tab->request.data);

1794 continue;

1795 }

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

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

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

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

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

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

1802 STRLCPY(tab->url, tab->request.url);

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

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

1805 free(tab->request.data);

1806 continue;

1807 }

1808 tab->page.data_len = r;

1809 free(tab->request.data);

1810 tab->request.data = NULL;

1811 gmi_load(&tab->page);

1812 tab->history->page = tab->page;

1813 tab->history->cached = 1;

1814 tab->request.recv = r;

1815 continue;

1816 }

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

1818 tab->request.recv > 0 &&

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

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

1821 struct gmi_link* link_ptr = tab->history?

1822 tab->history->prev:NULL;

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

1824 if (!link_ptr) break;

1825 link_ptr = link_ptr->prev;

1826 }

1827 while (link_ptr) {

1828 if (link_ptr->cached) {

1829 gmi_freepage(&link_ptr->page);

1830 link_ptr->cached = 0;

1831 }

1832 link_ptr = link_ptr->prev;

1833 }

1834 pthread_mutex_lock(&tab->render_mutex);

1835 if (!tab->thread.add)

1836 gmi_freepage(&tab->page);

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

1838 tab->page.code = 20;

1839 STRLCPY(tab->page.meta, tab->request.meta);

1840 STRLCPY(tab->url, tab->request.url);

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

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

1843 gmi_load(&tab->page);

1844 if (tab->thread.add)

1845 gmi_addtohistory(tab);

1846 else if (tab->history) {

1847 tab->history->page = tab->page;

1848 tab->history->cached = 1;

1849 }

1850 tab->scroll = -1;

1851 pthread_mutex_unlock(&tab->render_mutex);

1852 }

1853 }

1854 return NULL;

1855 }

1856

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

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

1859 tab->request.state = STATE_CANCEL;

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

1861 nanosleep(&timeout, NULL);

1862 tab->thread.add = add;

1863 STRLCPY(tab->thread.url, url);

1864 int signal = 0;

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

1866 sizeof(signal))

1867 return -1;

1868 return 0;

1869 }

1870

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

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

1873 if (!f) {

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

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

1876 tab->show_error = 1;

1877 return -1;

1878 }

1879 #ifdef SANDBOX_FREEBSD

1880 if (make_readonly(f)) {

1881 fclose(f);

1882 client.shutdown = 1;

1883 return -1;

1884 }

1885 #endif

1886 fseek(f, 0, SEEK_END);

1887 size_t len = ftell(f);

1888 fseek(f, 0, SEEK_SET);

1889 char* data = malloc(len);

1890 if (!data) {

1891 fclose(f);

1892 memory_failure(tab);

1893 return -1;

1894 }

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

1896 fclose(f);

1897 free(data);

1898 return -1;

1899 }

1900 fclose(f);

1901 pthread_mutex_lock(&tab->render_mutex);

1902 tab->page.code = 20;

1903 tab->page.data_len = len;

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

1905 tab->page.data = data;

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

1907 int i = strnlen(path, PATH_MAX);

1908 int gmi = 0;

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

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

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

1912 gmi = 1;

1913 if (gmi)

1914 STRLCPY(tab->page.meta, "text/gemini");

1915 else

1916 STRLCPY(tab->page.meta, "text/text");

1917 tab->page.no_header = 1;

1918 gmi_load(&tab->page);

1919 gmi_addtohistory(tab);

1920 pthread_mutex_unlock(&tab->render_mutex);

1921 return len;

1922 }

1923

1924 int gmi_init() {

1925 if (tls_init()) {

1926 tb_shutdown();

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

1928 return -1;

1929 }

1930

1931 config = tls_config_new();

1932 if (!config) {

1933 tb_shutdown();

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

1935 return -1;

1936 }

1937

1938 config_empty = tls_config_new();

1939 if (!config_empty) {

1940 tb_shutdown();

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

1942 return -1;

1943 }

1944

1945 tls_config_insecure_noverifycert(config);

1946 tls_config_insecure_noverifycert(config_empty);

1947 #ifndef DISABLE_XDG

1948 int xdg = client.xdg;

1949 #endif

1950 #ifdef SANDBOX_SUN

1951 char** bookmarks = client.bookmarks;

1952 #endif

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

1954 #ifdef SANDBOX_SUN

1955 client.bookmarks = bookmarks;

1956 #endif

1957 #ifndef DISABLE_XDG

1958 client.xdg = xdg;

1959 #endif

1960

1961 int fd = getconfigfd();

1962 if (fd < 0) {

1963 tb_shutdown();

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

1965 return -1;

1966 }

1967

1968 #ifndef SANDBOX_SUN

1969 if (gmi_loadbookmarks()) {

1970 gmi_newbookmarks();

1971 }

1972

1973 if (cert_load()) {

1974 tb_shutdown();

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

1976 return -1;

1977 }

1978 #endif

1979

1980 return 0;

1981 }

1982

1983 void gmi_free() {

1984 while (client.tab)

1985 gmi_freetab(client.tab);

1986 gmi_savebookmarks();

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

1988 free(client.bookmarks[i]);

1989 free(client.bookmarks);

1990 cert_free();

1991 }

1992