💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 61c21b12a91ef11dd1e9b00356530… captured on 2023-04-19 at 23:02:17. Gemini links have been rewritten to link to archived content

View Raw

More Information

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

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 int gmi_goto(struct gmi_tab* tab, int id) {

61 id--;

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

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

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

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

66 tab->show_error = 1;

67 client.input.mode = 0;

68 return -1;

69 }

70 gmi_cleanforward(tab);

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

72 return ret;

73 }

74

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

76 id--;

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

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

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

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

81 tab->show_error = 1;

82 client.input.mode = 0;

83 return -1;

84 }

85 struct gmi_tab* old_tab = client.tab;

86 client.tab = gmi_newtab_url(NULL);

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

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

89 return ret;

90 }

91

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

93 int url_len = strnlen(url, MAX_URL);

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

95 gmi_gohome(tab, 1);

96 return 0;

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

98 char buf[1024];

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

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

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

102 buf[len] = '/';

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

104 }

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

106 if (ret < 1) return ret;

107 return ret;

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

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

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

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

112 }

113 char urlbuf[MAX_URL];

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

115 if (l >= sizeof(urlbuf))

116 goto nextlink_overflow;

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

118 sizeof(urlbuf) - l)

119 goto nextlink_overflow;

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

121 return ret;

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

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

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

125 #ifndef DISABLE_XDG

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

127 #endif

128 tab->show_error = 1;

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

130 "Unable to open the link");

131 return -1;

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

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

134 return ret;

135 } else {

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

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

138 char urlbuf[MAX_URL];

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

140 if (l >= sizeof(urlbuf))

141 goto nextlink_overflow;

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

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

144 sizeof(urlbuf) - l);

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

146 goto nextlink_overflow;

147 l += l2;

148 }

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

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

151 goto nextlink_overflow;

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

153 return ret;

154 }

155 nextlink_overflow:

156 tab->show_error = 1;

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

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

159 return -1;

160 }

161

162 void gmi_load(struct gmi_page* page) {

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

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

165 free(page->links);

166 page->links = NULL;

167 page->links_count = 0;

168 page->lines = 0;

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

170 page->links = NULL;

171 page->links_count = 0;

172 page->lines = 0;

173 return;

174 }

175 int x = 0;

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

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

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

179 c += 2;

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

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

182 c++) ;

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

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

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

186 c--;

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

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

189 if (page->links)

190 page->links = realloc(page->links,

191 sizeof(char*) *

192 (page->links_count+1));

193 else

194 page->links = malloc(sizeof(char*));

195 if (!page->links) {

196 fatal();

197 return;

198 }

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

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

201 page->data[c] = save;

202 continue;

203 }

204 int len = strnlen(url, MAX_URL);

205 page->links[page->links_count] = malloc(len+2);

206 if (!page->links[page->links_count]) {

207 fatal();

208 return;

209 }

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

211 page->links_count++;

212 page->data[c] = save;

213 }

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

215 page->lines++;

216 x = 0;

217 continue;

218 }

219 x++;

220 }

221 }

222

223 int gmi_render(struct gmi_tab* tab) {

224 pthread_mutex_lock(&tab->render_mutex);

225 #include "img.h"

226 #ifdef TERMINAL_IMG_VIEWER

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

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

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

230 if (!ptr) {

231 tb_printf(2, -tab->scroll,

232 TB_DEFAULT, TB_DEFAULT,

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

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

235 pthread_mutex_unlock(&tab->render_mutex);

236 return 1;

237 }

238 ptr++;

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

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

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

242 }

243 tab->page.img.data =

244 stbi_load_from_memory((unsigned char*)ptr,

245 tab->page.data_len -

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

247 &tab->page.img.w,

248 &tab->page.img.h,

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

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

251 tb_printf(2, -tab->scroll,

252 TB_DEFAULT, TB_DEFAULT,

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

254 tab->page.meta);

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

256 pthread_mutex_unlock(&tab->render_mutex);

257 return 1;

258 }

259

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

261 }

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

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

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

265 client.tabs_count>1);

266 pthread_mutex_unlock(&tab->render_mutex);

267 return 1;

268 }

269 }

270 #endif

271 int text = 0;

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

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

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

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

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

277 "Unable to render format : %s",

278 tab->page.meta);

279 pthread_mutex_unlock(&tab->render_mutex);

280 return 1;

281 }

282 text = 1;

283 }

284 int line = 0;

285 int x = 0;

286 int links = 0;

287 uintattr_t color = TB_DEFAULT;

288 int start = 1;

289 int ignore = 0;

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

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

292 &client.input.field[1]:

293 tab->search.entry;

294 int search_len = search?

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

296 if (!search_len) search = NULL;

297 int highlight = 0;

298 int hlcolor = YELLOW;

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

300 tab->search.cursor = 0;

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

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

303 int previous_count = tab->search.count;

304 tab->search.count = 0;

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

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

307 line++;

308 int w = tb_width();

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

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

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

312 line += 1;

313 continue;

314 }

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

316 x += 8 - x%8;

317 continue;

318 }

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

320 if (!text && start &&

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

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

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

324 ignore = !ignore;

325 c += 3;

326 continue;

327 }

328 if (ignore)

329 color = ORANGE;

330

331 if (!ignore && !text) {

332 for (int i=0;

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

334 i++) {

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

336 color = RED + i;

337 break;

338 }

339 }

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

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

342 color = ITALIC|CYAN;

343 }

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

345 color = ITALIC|MAGENTA;

346 }

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

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

349 char buf[32];

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

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

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

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

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

355 links+1 ==

356 tab->selected?RED:BLUE,

357 TB_DEFAULT, buf);

358 }

359 x += len;

360 c += 2;

361

362 while (

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

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

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

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

367

368 int initial = c;

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

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

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

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

373

374 while (

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

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

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

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

379

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

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

382 c = initial;

383 x += 3;

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

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

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

387 links++;

388 }

389 }

390

391 if (search &&

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

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

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

395 (previous_count - 1)))

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

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

398 hlcolor++;

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

400 tab->search.count == 0)

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

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

403 hlcolor--;

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

405 }

406 highlight += search_len;

407 tab->search.count++;

408 }

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

410 x+4 >= w) {

411 int end = 0;

412 if (x+4 >= w)

413 end = 1;

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

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

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

417 if (i > w - 4) {

418 newline = 0;

419 break;

420 }

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

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

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

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

425 break;

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

427 }

428 if (newline) {

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

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

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

432 color = TB_DEFAULT;

433 start = 1;

434 } else c--;

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

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

437 x = 0;

438 continue;

439 } else if (end) {

440 c++;

441 x++;

442 }

443 }

444 uint32_t ch = 0;

445 int size = tb_utf8_char_to_unicode(&ch,

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

447 if (size > 0)

448 c += tb_utf8_char_to_unicode(&ch,

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

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

451

452 int wc = mk_wcwidth(ch);

453 if (wc < 0) wc = 0;

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

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

456 if (wc == 1)

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

458 ch, color,

459 highlight?hlcolor:TB_DEFAULT);

460 else

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

462 &ch, wc, color,

463 highlight?hlcolor:TB_DEFAULT);

464 }

465 if (highlight > 0)

466 highlight--;

467

468 x += wc;

469 start = 0;

470 }

471 line++;

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

473 if (h > line) {

474 pthread_mutex_unlock(&tab->render_mutex);

475 return line;

476 }

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

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

479 if (size < 1) size = 1;

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

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

482 + (client.tabs_count>1);

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

484 if (pos < 0) pos = 0;

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

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

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

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

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

490 else

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

492 }

493 pthread_mutex_unlock(&tab->render_mutex);

494 return line;

495 }

496

497 void gmi_addtohistory(struct gmi_tab* tab) {

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

499 return;

500 gmi_cleanforward(tab);

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

502 if (!link) {

503 fatal();

504 return;

505 }

506 link->next = NULL;

507 link->prev = tab->history;

508 if (link->prev)

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

510 link->page = tab->page;

511 link->cached = 1;

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

513 tab->history = link;

514 if (link->prev)

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

516 }

517

518 void gmi_cleanforward(struct gmi_tab* tab) {

519 if (!tab->history)

520 return;

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

522 while (link) {

523 struct gmi_link* ptr = link->next;

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

525 if (link->cached) {

526 gmi_freepage(&link->page);

527 link->cached = 0;

528 }

529 free(link);

530 link = ptr;

531 }

532 tab->history->next = NULL;

533 }

534

535 void gmi_freepage(struct gmi_page* page) {

536 if (!page) return;

537 #ifdef TERMINAL_IMG_VIEWER

538 if (page->img.data)

539 stbi_image_free(page->img.data);

540 #endif

541 free(page->data);

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

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

544 free(page->links);

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

546 }

547

548 void gmi_freetab(struct gmi_tab* tab) {

549 if (!tab) return;

550 tab->request.state = STATE_CANCEL;

551 int signal = 0xFFFFFFFF;

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

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

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

555 if (tab->history) {

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

557 while (link) {

558 struct gmi_link* ptr = link->next;

559 if (link->cached) {

560 gmi_freepage(&link->page);

561 link->cached = 0;

562 }

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

564 free(link);

565 link = ptr;

566 }

567 link = tab->history;

568 while (link) {

569 struct gmi_link* ptr = link->prev;

570 if (link->cached) {

571 gmi_freepage(&link->page);

572 link->cached = 0;

573 }

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

575 free(link);

576 link = ptr;

577 }

578 }

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

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

581 struct gmi_tab* prev = tab->prev;

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

583 pthread_mutex_destroy(&tab->render_mutex);

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

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

586 #ifdef TERMINAL_IMG_VIEWER

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

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

589 #endif

590 free(tab);

591 client.tab = prev;

592 client.tabs_count--;

593 }

594

595 char home_page[] =

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

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

598 "## Bookmarks\n\n" \

599 "%s\n" \

600 "## Keybindings\n\n" \

601 "* k - Scroll up\n" \

602 "* j - Scroll down\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

619 "## Commands\n\n" \

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

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

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

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

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

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

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

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

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

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

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

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

632

633 void gmi_newbookmarks() {

634 int len;

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

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

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

638 if (!client.bookmarks) goto fail_malloc;

639

640 len = sizeof(geminispace);

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

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

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

644

645 len = sizeof(gemigit);

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

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

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

649

650 client.bookmarks[2] = NULL;

651 return;

652 fail_malloc:

653 fatal();

654 }

655

656 int gmi_loadbookmarks() {

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

658 if (fd < 0)

659 return -1;

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

661 if (!f)

662 return -1;

663 #ifdef SANDBOX_FREEBSD

664 if (makefd_readonly(fd)) {

665 fclose(f);

666 return -1;

667 }

668 #endif

669 fseek(f, 0, SEEK_END);

670 size_t len = ftell(f);

671 fseek(f, 0, SEEK_SET);

672 char* data = malloc(len);

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

674 fclose(f);

675 return -1;

676 }

677 fclose(f);

678 char* ptr = data;

679 long n = 0;

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

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

682 client.bookmarks[n] = NULL;

683 n = 0;

684 ptr = data;

685 char* str = data;

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

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

688 *ptr = '\0';

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

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

691 n++;

692 str = ptr+1;

693 }

694 free(data);

695 return 0;

696 }

697

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

699 int n = 0;

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

701 n = tb_utf8_char_length(str[i]);

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

703 str[i] = '\0';

704 break;

705 }

706 }

707

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

709 if (page->title_cached) return;

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

711 page->title_cached = 1;

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

713 goto use_url;

714 }

715 int start = -1;

716 int end = -1;

717 int line_start = 1;

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

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

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

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

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

723 break;

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

725 start = j;

726 break;

727 }

728 }

729 }

730 line_start = 0;

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

732 line_start = 1;

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

734 end = i;

735 break;

736 }

737 }

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

739 goto use_url;

740 size_t len = end - start + 1;

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

742 len < sizeof(page->title)?

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

744 sanitize(page->title, len);

745 return;

746 use_url:

747 if (!url) {

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

749 sizeof(page->title));

750 sanitize(page->title, len);

751 return;

752 }

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

754 if (!str) {

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

756 goto sanitize;

757 }

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

759 str++;

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

761 sanitize:

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

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

764 page->title[i] = 0;

765 }

766

767 int gmi_removebookmark(int index) {

768 index--;

769 if (index < 0) return -1;

770 int fail = -1;

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

772 if (i == index) {

773 free(client.bookmarks[i]);

774 fail = 0;

775 }

776 if (!fail)

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

778 }

779 return fail;

780 }

781

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

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

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

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

786 tab->show_error = 1;

787 return;

788 }

789 int title_len = 0;

790 if (!title) {

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

792 title = tab->page.title;

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

794 if (!title_len) {

795 title_len = sizeof("no title");

796 title = "no title";

797 }

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

799 long n = 0;

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

801 client.bookmarks = realloc(client.bookmarks, sizeof(char*) * (n + 2));

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

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

804 if (title)

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

806 else

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

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

809 gmi_savebookmarks();

810 }

811

812 int gmi_savebookmarks() {

813 #ifdef SANDBOX_SUN

814 int fd = wr_pair[1];

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

816 return -1;

817 #else

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

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

820 if (fd < 0) {

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

822 return -1;

823 }

824 #ifdef SANDBOX_FREEBSD

825 if (makefd_writeonly(fd)) {

826 close(fd);

827 return -1;

828 }

829 #endif

830 #endif

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

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

833 char c = '\n';

834 write(fd, &c, 1);

835 }

836 #ifdef SANDBOX_SUN

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

838 #else

839 close(fd);

840 #endif

841 return 0;

842 }

843

844 char* gmi_getbookmarks(int* len) {

845 char* data = NULL;

846 int n = 0;

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

848 char line[2048];

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

850 client.bookmarks[i]);

851 data = realloc(data, n+length+1);

852 if (!data) return fatalP();

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

854 n += length-1;

855 }

856 *len = n;

857 return data;

858 }

859

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

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

862 int bm = 0;

863 char* data = gmi_getbookmarks(&bm);

864 pthread_mutex_lock(&tab->render_mutex);

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

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

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

868 fatal();

869 return;

870 }

871

872 tab->request.recv = snprintf(tab->request.data,

873 sizeof(home_page) + bm,

874 home_page,

875 data?data:"");

876 free(data);

877

878 strlcpy(tab->request.meta, "text/gemini",

879 sizeof(tab->request.meta));

880

881 if (!add)

882 gmi_freepage(&tab->page);

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

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

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

886 tab->page.code = 20;

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

888 gmi_load(&tab->page);

889 if (add)

890 gmi_addtohistory(tab);

891 else if (tab->history) {

892 tab->history->page = tab->page;

893 tab->history->cached = 1;

894 }

895 tab->scroll = -1;

896 tab->request.data = NULL;

897 pthread_mutex_unlock(&tab->render_mutex);

898 }

899

900 struct gmi_tab* gmi_newtab() {

901 return gmi_newtab_url("about:home");

902 }

903

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

905 client.tabs_count++;

906 if (client.tab) {

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

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

909 if (!client.tab->next) return fatalP();

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

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

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

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

914 if (next)

915 next->prev = client.tab;

916 } else {

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

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

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

920 }

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

922

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

924 return NULL;

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

926 (void *(*)(void *))gmi_request_thread,

927 (void*)client.tab);

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

929 if (url)

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

931

932 client.tab->scroll = -1;

933 return client.tab;

934 }

935

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

937 if (!tab) return -1;

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

939 gmi_gohome(tab, add);

940 return tab->page.data_len;

941 }

942 tab->show_error = 0;

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

944 tab->selected = 0;

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

946 int proto = parse_url(url,

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

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

949 &tab->request.port);

950

951 if (proto == -2) {

952 tab->show_error = 1;

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

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

955 return -1;

956 }

957 if (proto == PROTO_FILE) {

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

959 }

960 if (proto != PROTO_GEMINI) {

961 #ifndef DISABLE_XDG

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

963 #endif

964 tab->show_error = 1;

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

966 "Unable to open the link");

967 return -1;

968 }

969 if (tab->request.tls)

970 tls_reset(tab->request.tls);

971 else

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

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

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

975 "Failed to initialize TLS");

976 tab->show_error = 1;

977 return -1;

978 }

979

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

981 if (cert >= 0 &&

982 tls_config_set_keypair_mem(config,

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

984 client.certs[cert].crt_len,

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

986 client.certs[cert].key_len))

987 {

988 tab->show_error = 1;

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

990 tls_config_error(config));

991 return -1;

992 }

993

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

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

996 "Failed to configure TLS");

997 tab->show_error = 1;

998 return -1;

999 }

1000 tab->request.state = STATE_REQUESTED;

1001 return 0;

1002 }

1003

1004 #ifdef __linux

1005 void dns_async(union sigval sv) {

1006 struct gmi_tab* tab = sv.sival_ptr;

1007 tab->request.resolved = 1;

1008 }

1009 #endif

1010

1011 int gmi_request_dns(struct gmi_tab* tab) {

1012 char host[1024];

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

1014 host, sizeof(host))) {

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

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

1017 tab->show_error = 1;

1018 return -1;

1019 }

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

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

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

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

1024 if (!tab->request.gaicb_ptr) return fatalI();

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

1026 tab->request.resolved = 0;

1027 struct sigevent sevp;

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

1029 sevp.sigev_notify = SIGEV_THREAD;

1030 sevp.sigev_notify_function = dns_async;

1031 sevp.sigev_value.sival_ptr = tab;

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

1033 if (ret) {

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

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

1036 tab->show_error = 1;

1037 return -1;

1038 }

1039

1040 long start = time(NULL);

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

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

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

1044 nanosleep(&timeout, NULL);

1045 }

1046

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

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

1049 gai_cancel(tab->request.gaicb_ptr);

1050 free(tab->request.gaicb_ptr);

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

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

1053 tab->show_error = 1;

1054 return -1;

1055 }

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

1057 #else

1058 struct addrinfo hints, *result;

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

1060 hints.ai_family = AF_INET;

1061 hints.ai_socktype = SOCK_STREAM;

1062 hints.ai_flags |= AI_CANONNAME;

1063 errno = 0;

1064

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

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

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

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

1069 tab->show_error = 1;

1070 return -1;

1071 }

1072 #endif

1073

1074 struct sockaddr_in addr4;

1075 struct sockaddr_in6 addr6;

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

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

1078

1079 int error = 0;

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

1081 addr4.sin_addr =

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

1083 addr4.sin_family = AF_INET;

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

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

1086 tab->request.addr =

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

1088 }

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

1090 addr6.sin6_addr =

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

1092 addr6.sin6_family = AF_INET6;

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

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

1095 tab->request.addr =

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

1097 } else {

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

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

1100 tab->request.host);

1101 error = 1;

1102 }

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

1104 freeaddrinfo(result);

1105 #ifdef __linux__

1106 free(tab->request.gaicb_ptr);

1107 #endif

1108 if (error) {

1109 tab->request.state = STATE_CANCEL;

1110 return -1;

1111 }

1112

1113 tab->request.state = STATE_DNS;

1114 return 0;

1115 }

1116

1117 int gmi_request_connect(struct gmi_tab* tab) {

1118

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

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

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

1122 "Failed to create socket");

1123 return -1;

1124 }

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

1126 if (flags == -1 ||

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

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

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

1130 return -1;

1131 }

1132

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

1134 sizeof(struct sockaddr_in):

1135 sizeof(struct sockaddr_in6);

1136

1137 int connected = 0;

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

1139 tab->request.addr, addr_size);

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

1141 errno != EINPROGRESS && errno != EALREADY &&

1142 errno != 0):failed;

1143 while (!failed) {

1144 struct pollfd fds[2];

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

1146 fds[0].events = POLLIN;

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

1148 fds[1].events = POLLOUT;

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

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

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

1152 int value;

1153 socklen_t len = sizeof(value);

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

1155 SO_ERROR, &value, &len);

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

1157 break;

1158 }

1159

1160 if (!connected) {

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

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

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

1164 return -1;

1165 }

1166

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

1168 tab->request.host)) {

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

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

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

1172 return -1;

1173 }

1174 return 0;

1175 }

1176

1177 int gmi_request_handshake(struct gmi_tab* tab) {

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

1179 tab->request.socket,

1180 tab->request.host)) {

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

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

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

1184 return -1;

1185 }

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

1187 int ret = 0;

1188 time_t start = time(0);

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

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

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

1192 (ret < 0 &&

1193 ret != TLS_WANT_POLLIN &&

1194 ret != TLS_WANT_POLLOUT))

1195 break;

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

1197 nanosleep(&timeout, NULL);

1198 }

1199 if (ret) {

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

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

1202 tls_error(tab->request.tls),

1203 tab->request.host);

1204 return -1;

1205 }

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

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

1208 tls_peer_cert_hash(tab->request.tls),

1209 tls_peer_cert_notbefore(tab->request.tls),

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

1211 switch (ret) {

1212 case 0: // success

1213 break;

1214 case -1: // invalid certificate

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

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

1217 "(The certificate changed)",

1218 tab->request.host);

1219 return -1;

1220 case -3: // sandbox error

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

1222 "Sandbox error when verifying server certificate " \

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

1224 return -1;

1225 case -5: // expired

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

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

1228 "Expired certificate, " \

1229 "the certificate for %s has expired",

1230 tab->request.host);

1231 return -1;

1232 case -6: // certificate changed

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

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

1235 " to forget the old certificate.",

1236 tab->request.host);

1237 return -1;

1238 default: // failed to write certificate

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

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

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

1242 return -1;

1243 }

1244

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

1246 char buf[MAX_URL];

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

1248 char toascii[1024];

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

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

1251 toascii, sizeof(toascii))) {

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

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

1254 return -1;

1255 }

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

1257 char url[1024];

1258 if (len < MAX_URL)

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

1260 if (len >= MAX_URL ||

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

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

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

1264 return -1;

1265 }

1266

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

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

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

1270 return -1;

1271 }

1272

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

1274 tab->request.state = STATE_CONNECTED;

1275 return 0;

1276 }

1277

1278 int gmi_request_header(struct gmi_tab* tab) {

1279 time_t now = time(0);

1280 char buf[1024];

1281 int recv = 0;

1282 while (1) {

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

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

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

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

1287 tab->request.host);

1288 return -1;

1289 }

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

1291 sizeof(buf) - recv - 1);

1292 if (n == TLS_WANT_POLLIN) {

1293 nanosleep(&timeout, NULL);

1294 continue;

1295 }

1296 if (n < 1) {

1297 recv = -1;

1298 break;

1299 }

1300 recv += n;

1301 buf[recv] = '\0';

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

1303 }

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

1305

1306 if (recv <= 0) {

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

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

1309 recv, tab->request.host);

1310 return -1;

1311 }

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

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

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

1315 return -1;

1316 }

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

1318 if (!ptr) {

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

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

1321 tab->request.host);

1322 return -1;

1323 }

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

1325

1326 int previous_code = tab->page.code;

1327 char c = buf[2];

1328 buf[2] = '\0';

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

1330 buf[2] = c;

1331

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

1333 switch (tab->page.code) {

1334 case 10:

1335 case 11:

1336 ptr++;

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

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

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

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

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

1342 tab->request.state = STATE_RECV_HEADER;

1343 tab->request.recv = recv;

1344 return 2; // input

1345 case 20:

1346 {

1347 #ifdef TERMINAL_IMG_VIEWER

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

1349 #endif

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

1351 if (!meta) {

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

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

1354 return -1;

1355 }

1356 *meta = '\0';

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

1358 *meta = '\r';

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

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

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

1362 #ifdef TERMINAL_IMG_VIEWER

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

1364 #endif

1365 )) {

1366 if (tab->history)

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

1368 sizeof(tab->url));

1369 else

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

1371 sizeof(tab->url));

1372 tab->request.ask = 2;

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

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

1375 tab->request.meta);

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

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

1378 tb_interupt();

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

1380 nanosleep(&timeout, NULL);

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

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

1383 tab->request.recv = -1;

1384 return 1; // download

1385 }

1386 }

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

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

1389 }

1390 break;

1391 case 30:

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

1393 "Redirect temporary");

1394 break;

1395 case 31:

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

1397 "Redirect permanent");

1398 break;

1399 case 40:

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

1401 "Temporary failure");

1402 return -2;

1403 case 41:

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

1405 "Server unavailable");

1406 return -2;

1407 case 42:

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

1409 "CGI error");

1410 return -2;

1411 case 43:

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

1413 "Proxy error");

1414 return -2;

1415 case 44:

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

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

1418 return -2;

1419 case 50:

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

1421 "Permanent failure");

1422 return -2;

1423 case 51:

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

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

1426 return -2;

1427 case 52:

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

1429 "Resource gone");

1430 return -2;

1431 case 53:

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

1433 "Proxy request refused");

1434 return -2;

1435 case 59:

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

1437 "Bad request");

1438 return -2;

1439 case 60:

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

1441 "Client certificate required");

1442 return -2;

1443 case 61:

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

1445 "Client not authorised");

1446 return -2;

1447 case 62:

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

1449 "Client certificate not valid");

1450 return -2;

1451 default:

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

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

1454 tab->page.code = previous_code;

1455 return -2;

1456 }

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

1458 tab->request.state = STATE_RECV_HEADER;

1459 tab->request.recv = recv;

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

1461 if (!tab->request.data) return fatalI();

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

1463 return 0;

1464 }

1465

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

1467 time_t now = time(0);

1468 char buf[1024];

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

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

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

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

1473 "Server %s stopped responding",

1474 tab->request.host);

1475 return -1;

1476 }

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

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

1479 if (bytes == 0) break;

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

1481 nanosleep(&timeout, NULL);

1482 continue;

1483 }

1484 if (bytes < 1) {

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

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

1487 tab->request.host,

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

1489 return -1;

1490 }

1491 if (fd > 0) {

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

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

1494 "Failed to download file: %s",

1495 strerror(errno));

1496 return -1;

1497 }

1498 now = time(0);

1499 continue;

1500 }

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

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

1503 tab->request.data = realloc(tab->request.data,

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

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

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

1507 tab->request.recv += bytes;

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

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

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

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

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

1513 return -1;

1514 }

1515 now = time(0);

1516 }

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

1518 tab->request.state = STATE_RECV_BODY;

1519 return 0;

1520 }

1521

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

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

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

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

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

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

1528 #ifndef SANDBOX_SUN

1529 int fd = getdownloadfd();

1530 if (fd < 0) {

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

1532 "Unable to open download folder");

1533 tab->show_error = 1;

1534 return -1;

1535 }

1536 #endif

1537 if (ptr)

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

1539 else {

1540 #ifdef __OpenBSD__

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

1542 #else

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

1544 #endif

1545

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

1547 }

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

1549 char c = path[i];

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

1551 !(c == '.' ||

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

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

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

1555 ))

1556 path[i] = '_';

1557 }

1558 #ifdef SANDBOX_SUN

1559 if (sandbox_download(tab, path))

1560 return -1;

1561 int dfd = wr_pair[1];

1562 sandbox_dl_length(-1);

1563 #else

1564 int dfd = openat(fd, path,

1565 O_CREAT|O_EXCL|O_WRONLY, 0600);

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

1567 char buf[1024];

1568 #ifdef __OpenBSD__

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

1570 #else

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

1572 #endif

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

1574 strlcpy(path, buf, path_len);

1575 dfd = openat(fd, path,

1576 O_CREAT|O_EXCL|O_WRONLY, 0600);

1577 if (dfd < 0) {

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

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

1580 tab->show_error = 1;

1581 return -1;

1582 }

1583 }

1584 #ifdef SANDBOX_FREEBSD

1585 if (makefd_writeonly(dfd)) {

1586 client.shutdown = 1;

1587 break;

1588 }

1589 #endif

1590 #endif

1591 return dfd;

1592 }

1593

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

1595

1596 #ifdef SANDBOX_SUN

1597 if (wr_pair[1] != fd)

1598 return -1;

1599 #else

1600 close(fd);

1601 #endif

1602 #ifndef DISABLE_XDG

1603 int fail = 0;

1604 if (client.xdg) {

1605 tab->request.ask = 2;

1606 snprintf(tab->request.info,

1607 sizeof(tab->request.info),

1608 "# %s download",

1609 tab->request.meta);

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

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

1612 tb_interupt();

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

1614 nanosleep(&timeout, NULL);

1615 if (tab->request.ask)

1616 fail = xdg_open(path);

1617 }

1618 if (fail) {

1619 tab->show_error = 1;

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

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

1622 } else

1623 #endif

1624 {

1625 tab->show_info = 1;

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

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

1628 }

1629 return fail;

1630 }

1631

1632 void* gmi_request_thread(struct gmi_tab* tab) {

1633 unsigned int signal = 0;

1634 while (!client.shutdown) {

1635 tab->selected = 0;

1636 tab->request.state = STATE_DONE;

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

1638 tb_interupt();

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

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

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

1642 tab->request.state = STATE_STARTED;

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

1644 tab->thread.add);

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

1646 ret || gmi_request_dns(tab)) {

1647 if (tab->request.tls) {

1648 tls_close(tab->request.tls);

1649 tls_free(tab->request.tls);

1650 tab->request.tls = NULL;

1651 }

1652 if (ret == -1)

1653 tab->show_error = 1;

1654 tab->request.recv = ret;

1655 continue;

1656 }

1657 if (gmi_request_connect(tab) ||

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

1659 close(tab->request.socket);

1660 if (tab->request.tls) {

1661 tls_close(tab->request.tls);

1662 tls_free(tab->request.tls);

1663 tab->request.tls = NULL;

1664 }

1665 tab->show_error = 1;

1666 tab->request.recv = -1;

1667 continue;

1668 }

1669 ret = -1;

1670 if (!gmi_request_handshake(tab) &&

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

1672 ret = gmi_request_header(tab);

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

1674 char out[1024];

1675 int fd = -1;

1676 if (tab->request.download)

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

1678 if (gmi_request_body(tab, fd)) {

1679 close(fd);

1680 tab->show_error = 1;

1681 free(tab->request.data);

1682 continue;

1683 }

1684 if (fd > 0)

1685 gmi_postdownload(tab, fd, out);

1686 }

1687 }

1688

1689 if (ret == -2) {

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

1691 if (cr) {

1692 *cr = '\0';

1693 char buf[256];

1694 snprintf(buf, sizeof(buf),

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

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

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

1698 *cr = '\r';

1699 }

1700 }

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

1702 free(tab->request.data);

1703 tab->request.data = NULL;

1704 if (tab->history)

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

1706 sizeof(tab->url));

1707 else

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

1709 sizeof(tab->url));

1710 tab->show_error = 1;

1711 tab->request.recv = -1;

1712 tab->page.code = 20;

1713 }

1714

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

1716 close(tab->request.socket);

1717 if (tab->request.tls) {

1718 tls_close(tab->request.tls);

1719 tls_free(tab->request.tls);

1720 tab->request.tls = NULL;

1721 }

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

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

1724 free(tab->request.data);

1725 tab->request.data = NULL;

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

1727 continue;

1728 }

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

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

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

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

1733 if (!ptr) {

1734 free(tab->request.data);

1735 continue;

1736 }

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

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

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

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

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

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

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

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

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

1746 free(tab->request.data);

1747 continue;

1748 }

1749 tab->page.data_len = r;

1750 free(tab->request.data);

1751 tab->request.data = NULL;

1752 gmi_load(&tab->page);

1753 tab->history->page = tab->page;

1754 tab->history->cached = 1;

1755 tab->request.recv = r;

1756 continue;

1757 }

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

1759 tab->request.recv > 0 &&

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

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

1762 struct gmi_link* link_ptr = tab->history?

1763 tab->history->prev:NULL;

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

1765 if (!link_ptr) break;

1766 link_ptr = link_ptr->prev;

1767 }

1768 while (link_ptr) {

1769 if (link_ptr->cached) {

1770 gmi_freepage(&link_ptr->page);

1771 link_ptr->cached = 0;

1772 }

1773 link_ptr = link_ptr->prev;

1774 }

1775 pthread_mutex_lock(&tab->render_mutex);

1776 if (!tab->thread.add)

1777 gmi_freepage(&tab->page);

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

1779 tab->page.code = 20;

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

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

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

1783 sizeof(tab->url));

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

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

1786 gmi_load(&tab->page);

1787 if (tab->thread.add)

1788 gmi_addtohistory(tab);

1789 else if (tab->history) {

1790 tab->history->page = tab->page;

1791 tab->history->cached = 1;

1792 }

1793 tab->scroll = -1;

1794 pthread_mutex_unlock(&tab->render_mutex);

1795 }

1796 }

1797 return NULL;

1798 }

1799

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

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

1802 tab->request.state = STATE_CANCEL;

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

1804 nanosleep(&timeout, NULL);

1805 tab->thread.add = add;

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

1807 int signal = 0;

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

1809 sizeof(signal))

1810 return -1;

1811 return 0;

1812 }

1813

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

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

1816 if (!f) {

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

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

1819 tab->show_error = 1;

1820 return -1;

1821 }

1822 #ifdef SANDBOX_FREEBSD

1823 if (make_readonly(f)) {

1824 fclose(f);

1825 client.shutdown = 1;

1826 return -1;

1827 }

1828 #endif

1829 fseek(f, 0, SEEK_END);

1830 size_t len = ftell(f);

1831 fseek(f, 0, SEEK_SET);

1832 char* data = malloc(len);

1833 if (!data) return fatalI();

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

1835 fclose(f);

1836 free(data);

1837 return -1;

1838 }

1839 fclose(f);

1840 pthread_mutex_lock(&tab->render_mutex);

1841 tab->page.code = 20;

1842 tab->page.data_len = len;

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

1844 tab->page.data = data;

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

1846 int i = strnlen(path, 1024);

1847 int gmi = 0;

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

1849 if (i > 4 && path[i - 4] == '.' &&

1850 path[i - 3] == 'g' &&

1851 path[i - 2] == 'm' &&

1852 path[i - 1] == 'i')

1853 gmi = 1;

1854 if (gmi)

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

1856 else

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

1858 tab->page.no_header = 1;

1859 gmi_load(&tab->page);

1860 gmi_addtohistory(tab);

1861 pthread_mutex_unlock(&tab->render_mutex);

1862 return len;

1863 }

1864

1865 int gmi_init() {

1866 if (tls_init()) {

1867 tb_shutdown();

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

1869 return -1;

1870 }

1871

1872 config = tls_config_new();

1873 if (!config) {

1874 tb_shutdown();

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

1876 return -1;

1877 }

1878

1879 config_empty = tls_config_new();

1880 if (!config_empty) {

1881 tb_shutdown();

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

1883 return -1;

1884 }

1885

1886 tls_config_insecure_noverifycert(config);

1887 tls_config_insecure_noverifycert(config_empty);

1888 #ifndef DISABLE_XDG

1889 int xdg = client.xdg;

1890 #endif

1891 #ifdef SANDBOX_SUN

1892 char** bookmarks = client.bookmarks;

1893 #endif

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

1895 #ifdef SANDBOX_SUN

1896 client.bookmarks = bookmarks;

1897 #endif

1898 #ifndef DISABLE_XDG

1899 client.xdg = xdg;

1900 #endif

1901

1902 int fd = getconfigfd();

1903 if (fd < 0) {

1904 tb_shutdown();

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

1906 return -1;

1907 }

1908

1909 #ifndef SANDBOX_SUN

1910 if (gmi_loadbookmarks()) {

1911 gmi_newbookmarks();

1912 }

1913

1914 if (cert_load()) {

1915 tb_shutdown();

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

1917 return -1;

1918 }

1919 #endif

1920

1921 return 0;

1922 }

1923

1924 void gmi_free() {

1925 while (client.tab)

1926 gmi_freetab(client.tab);

1927 gmi_savebookmarks();

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

1929 free(client.bookmarks[i]);

1930 free(client.bookmarks);

1931 cert_free();

1932 }

1933