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 &&

181 (page->data[c] == ' ' || 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 if (strncmp(tab->page.meta, "text/gemini", 11)) {

273 if (strncmp(tab->page.meta, "text/", 5)) {

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

275 "Unable to render format : %s",

276 tab->page.meta);

277 pthread_mutex_unlock(&tab->render_mutex);

278 return 1;

279 }

280 text = 1;

281 }

282 int line = 0;

283 int x = 0;

284 int links = 0;

285 uintattr_t color = TB_DEFAULT;

286 int start = 1;

287 int ignore = 0;

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

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

290 &client.input.field[1]:

291 tab->search.entry;

292 int search_len = search?

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

294 if (!search_len) search = NULL;

295 int highlight = 0;

296 int hlcolor = YELLOW;

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

298 tab->search.cursor = 0;

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

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

301 int previous_count = tab->search.count;

302 tab->search.count = 0;

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

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

305 line++;

306 int w = tb_width();

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

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

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

310 line += 1;

311 continue;

312 }

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

314 x += 8 - x%8;

315 continue;

316 }

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

318 if (!text && start &&

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

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

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

322 ignore = !ignore;

323 c += 3;

324 continue;

325 }

326 if (ignore)

327 color = ORANGE;

328

329 if (!ignore && !text) {

330 for (int i=0;

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

332 i++) {

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

334 color = RED + i;

335 break;

336 }

337 }

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

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

340 color = ITALIC|CYAN;

341 }

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

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

344 color = ITALIC|MAGENTA;

345 }

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

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

348 char buf[32];

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

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

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

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

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

354 links+1 ==

355 tab->selected?RED:BLUE,

356 TB_DEFAULT, buf);

357 }

358 x += len;

359 c += 2;

360

361 while (

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

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

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

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

366

367 int initial = c;

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

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

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

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

372

373 while (

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

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

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

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

378

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

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

381 c = initial;

382 x += 3;

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

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

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

386 links++;

387 }

388 }

389

390 if (search &&

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

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

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

394 (previous_count - 1)))

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

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

397 hlcolor++;

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

399 tab->search.count == 0)

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

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

402 hlcolor--;

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

404 }

405 highlight += search_len;

406 tab->search.count++;

407 }

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

409 x+4 >= w) {

410 int end = 0;

411 if (x+4 >= w)

412 end = 1;

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

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

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

416 if (i > w - 4) {

417 newline = 0;

418 break;

419 }

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

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

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

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

424 break;

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

426 }

427 if (newline) {

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

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

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

431 color = TB_DEFAULT;

432 start = 1;

433 } else c--;

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

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

436 x = 0;

437 continue;

438 } else if (end) {

439 c++;

440 x++;

441 }

442 }

443 uint32_t ch = 0;

444 int size = tb_utf8_char_to_unicode(&ch,

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

446 if (size > 0)

447 c += tb_utf8_char_to_unicode(&ch,

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

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

450

451 int wc = mk_wcwidth(ch);

452 if (wc < 0) wc = 0;

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

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

455 if (wc == 1)

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

457 ch, color,

458 highlight?hlcolor:TB_DEFAULT);

459 else

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

461 &ch, wc, color,

462 highlight?hlcolor:TB_DEFAULT);

463 }

464 if (highlight > 0)

465 highlight--;

466

467 x += wc;

468 start = 0;

469 }

470 line++;

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

472 if (h > line) {

473 pthread_mutex_unlock(&tab->render_mutex);

474 return line;

475 }

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

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

478 if (size < 1) size = 1;

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

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

481 + (client.tabs_count>1);

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

483 if (pos < 0) pos = 0;

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

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

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

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

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

489 else

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

491 }

492 pthread_mutex_unlock(&tab->render_mutex);

493 return line;

494 }

495

496 void gmi_addtohistory(struct gmi_tab* tab) {

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

498 return;

499 gmi_cleanforward(tab);

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

501 if (!link) {

502 fatal();

503 return;

504 }

505 link->next = NULL;

506 link->prev = tab->history;

507 if (link->prev)

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

509 link->page = tab->page;

510 link->cached = 1;

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

512 tab->history = link;

513 if (link->prev)

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

515 }

516

517 void gmi_cleanforward(struct gmi_tab* tab) {

518 if (!tab->history)

519 return;

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

521 while (link) {

522 struct gmi_link* ptr = link->next;

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

524 if (link->cached) {

525 gmi_freepage(&link->page);

526 link->cached = 0;

527 }

528 free(link);

529 link = ptr;

530 }

531 tab->history->next = NULL;

532 }

533

534 void gmi_freepage(struct gmi_page* page) {

535 if (!page) return;

536 #ifdef TERMINAL_IMG_VIEWER

537 if (page->img.data)

538 stbi_image_free(page->img.data);

539 #endif

540 free(page->data);

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

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

543 free(page->links);

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

545 }

546

547 void gmi_freetab(struct gmi_tab* tab) {

548 if (!tab) return;

549 tab->request.state = STATE_CANCEL;

550 int signal = 0xFFFFFFFF;

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

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

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

554 if (tab->history) {

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

556 while (link) {

557 struct gmi_link* ptr = link->next;

558 if (link->cached) {

559 gmi_freepage(&link->page);

560 link->cached = 0;

561 }

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

563 free(link);

564 link = ptr;

565 }

566 link = tab->history;

567 while (link) {

568 struct gmi_link* ptr = link->prev;

569 if (link->cached) {

570 gmi_freepage(&link->page);

571 link->cached = 0;

572 }

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

574 free(link);

575 link = ptr;

576 }

577 }

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

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

580 struct gmi_tab* prev = tab->prev;

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

582 pthread_mutex_destroy(&tab->render_mutex);

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

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

585 #ifdef TERMINAL_IMG_VIEWER

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

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

588 #endif

589 free(tab);

590 client.tab = prev;

591 client.tabs_count--;

592 }

593

594 char home_page[] =

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

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

597 "## Bookmarks\n\n" \

598 "%s\n" \

599 "## Keybindings\n\n" \

600 "* k - Scroll up\n" \

601 "* j - Scroll down\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

618 "## Commands\n\n" \

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

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

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

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

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

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

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

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

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

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

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

630

631 void gmi_newbookmarks() {

632 int len;

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

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

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

636 if (!client.bookmarks) goto fail_malloc;

637

638 len = sizeof(geminispace);

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

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

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

642

643 len = sizeof(gemigit);

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

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

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

647

648 client.bookmarks[2] = NULL;

649 return;

650 fail_malloc:

651 fatal();

652 }

653

654 int gmi_loadbookmarks() {

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

656 if (fd < 0)

657 return -1;

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

659 if (!f)

660 return -1;

661 #ifdef SANDBOX_FREEBSD

662 if (makefd_readonly(fd)) {

663 fclose(f);

664 return -1;

665 }

666 #endif

667 fseek(f, 0, SEEK_END);

668 size_t len = ftell(f);

669 fseek(f, 0, SEEK_SET);

670 char* data = malloc(len);

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

672 fclose(f);

673 return -1;

674 }

675 fclose(f);

676 char* ptr = data;

677 long n = 0;

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

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

680 client.bookmarks[n] = NULL;

681 n = 0;

682 ptr = data;

683 char* str = data;

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

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

686 *ptr = '\0';

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

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

689 n++;

690 str = ptr+1;

691 }

692 free(data);

693 return 0;

694 }

695

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

697 int n = 0;

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

699 n = tb_utf8_char_length(str[i]);

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

701 str[i] = '\0';

702 break;

703 }

704 }

705

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

707 if (page->title_cached) return;

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

709 page->title_cached = 1;

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

711 goto use_url;

712 }

713 int start = -1;

714 int end = -1;

715 int line_start = 1;

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

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

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

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

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

721 break;

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

723 start = j;

724 break;

725 }

726 }

727 }

728 line_start = 0;

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

730 line_start = 1;

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

732 end = i;

733 break;

734 }

735 }

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

737 goto use_url;

738 size_t len = end - start + 1;

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

740 len < sizeof(page->title)?

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

742 sanitize(page->title, len);

743 return;

744 use_url:

745 if (!url) {

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

747 sizeof(page->title));

748 sanitize(page->title, len);

749 return;

750 }

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

752 if (!str) {

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

754 goto sanitize;

755 }

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

757 str++;

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

759 sanitize:

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

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

762 page->title[i] = 0;

763 }

764

765 int gmi_removebookmark(int index) {

766 index--;

767 if (index < 0) return -1;

768 int fail = -1;

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

770 if (i == index) {

771 free(client.bookmarks[i]);

772 fail = 0;

773 }

774 if (!fail)

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

776 }

777 return fail;

778 }

779

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

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

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

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

784 tab->show_error = 1;

785 return;

786 }

787 int title_len = 0;

788 if (!title) {

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

790 title = tab->page.title;

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

792 if (!title_len) {

793 title_len = sizeof("no title");

794 title = "no title";

795 }

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

797 long n = 0;

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

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

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

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

802 if (title)

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

804 else

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

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

807 gmi_savebookmarks();

808 }

809

810 int gmi_savebookmarks() {

811 #ifdef SANDBOX_SUN

812 int fd = wr_pair[1];

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

814 return -1;

815 #else

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

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

818 if (fd < 0) {

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

820 return -1;

821 }

822 #ifdef SANDBOX_FREEBSD

823 if (makefd_writeonly(fd)) {

824 close(fd);

825 return -1;

826 }

827 #endif

828 #endif

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

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

831 char c = '\n';

832 write(fd, &c, 1);

833 }

834 #ifdef SANDBOX_SUN

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

836 #else

837 close(fd);

838 #endif

839 return 0;

840 }

841

842 char* gmi_getbookmarks(int* len) {

843 char* data = NULL;

844 int n = 0;

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

846 char line[2048];

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

848 client.bookmarks[i]);

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

850 if (!data) return fatalP();

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

852 n += length-1;

853 }

854 *len = n;

855 return data;

856 }

857

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

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

860 int bm = 0;

861 char* data = gmi_getbookmarks(&bm);

862 pthread_mutex_lock(&tab->render_mutex);

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

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

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

866 fatal();

867 return;

868 }

869

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

871 sizeof(home_page) + bm,

872 home_page,

873 data?data:"");

874 free(data);

875

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

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

878

879 if (!add)

880 gmi_freepage(&tab->page);

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

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

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

884 tab->page.code = 20;

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

886 gmi_load(&tab->page);

887 if (add)

888 gmi_addtohistory(tab);

889 else if (tab->history) {

890 tab->history->page = tab->page;

891 tab->history->cached = 1;

892 }

893 tab->scroll = -1;

894 tab->request.data = NULL;

895 pthread_mutex_unlock(&tab->render_mutex);

896 }

897

898 struct gmi_tab* gmi_newtab() {

899 return gmi_newtab_url("about:home");

900 }

901

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

903 client.tabs_count++;

904 if (client.tab) {

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

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

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

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

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

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

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

912 if (next)

913 next->prev = client.tab;

914 } else {

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

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

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

918 }

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

920

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

922 return NULL;

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

924 (void *(*)(void *))gmi_request_thread,

925 (void*)client.tab);

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

927 if (url)

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

929

930 client.tab->scroll = -1;

931 return client.tab;

932 }

933

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

935 if (!tab) return -1;

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

937 gmi_gohome(tab, add);

938 return tab->page.data_len;

939 }

940 tab->show_error = 0;

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

942 tab->selected = 0;

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

944 int proto = parse_url(url,

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

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

947 &tab->request.port);

948

949 if (proto == -2) {

950 tab->show_error = 1;

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

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

953 return -1;

954 }

955 if (proto == PROTO_FILE) {

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

957 }

958 if (proto != PROTO_GEMINI) {

959 #ifndef DISABLE_XDG

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

961 #endif

962 tab->show_error = 1;

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

964 "Unable to open the link");

965 return -1;

966 }

967 if (tab->request.tls)

968 tls_reset(tab->request.tls);

969 else

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

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

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

973 "Failed to initialize TLS");

974 tab->show_error = 1;

975 return -1;

976 }

977

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

979 if (cert >= 0 &&

980 tls_config_set_keypair_mem(config,

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

982 client.certs[cert].crt_len,

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

984 client.certs[cert].key_len))

985 {

986 tab->show_error = 1;

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

988 tls_config_error(config));

989 return -1;

990 }

991

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

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

994 "Failed to configure TLS");

995 tab->show_error = 1;

996 return -1;

997 }

998 tab->request.state = STATE_REQUESTED;

999 return 0;

1000 }

1001

1002 #ifdef __linux

1003 void dns_async(union sigval sv) {

1004 struct gmi_tab* tab = sv.sival_ptr;

1005 tab->request.resolved = 1;

1006 }

1007 #endif

1008

1009 int gmi_request_dns(struct gmi_tab* tab) {

1010 char host[1024];

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

1012 host, sizeof(host))) {

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

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

1015 tab->show_error = 1;

1016 return -1;

1017 }

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

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

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

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

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

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

1024 tab->request.resolved = 0;

1025 struct sigevent sevp;

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

1027 sevp.sigev_notify = SIGEV_THREAD;

1028 sevp.sigev_notify_function = dns_async;

1029 sevp.sigev_value.sival_ptr = tab;

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

1031 if (ret) {

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

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

1034 tab->show_error = 1;

1035 return -1;

1036 }

1037

1038 long start = time(NULL);

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

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

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

1042 nanosleep(&timeout, NULL);

1043 }

1044

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

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

1047 gai_cancel(tab->request.gaicb_ptr);

1048 free(tab->request.gaicb_ptr);

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

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

1051 tab->show_error = 1;

1052 return -1;

1053 }

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

1055 #else

1056 struct addrinfo hints, *result;

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

1058 hints.ai_family = AF_INET;

1059 hints.ai_socktype = SOCK_STREAM;

1060 hints.ai_flags |= AI_CANONNAME;

1061 errno = 0;

1062

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

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

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

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

1067 tab->show_error = 1;

1068 return -1;

1069 }

1070 #endif

1071

1072 struct sockaddr_in addr4;

1073 struct sockaddr_in6 addr6;

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

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

1076

1077 int error = 0;

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

1079 addr4.sin_addr =

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

1081 addr4.sin_family = AF_INET;

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

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

1084 tab->request.addr =

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

1086 }

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

1088 addr6.sin6_addr =

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

1090 addr6.sin6_family = AF_INET6;

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

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

1093 tab->request.addr =

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

1095 } else {

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

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

1098 tab->request.host);

1099 error = 1;

1100 }

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

1102 freeaddrinfo(result);

1103 #ifdef __linux__

1104 free(tab->request.gaicb_ptr);

1105 #endif

1106 if (error) {

1107 tab->request.state = STATE_CANCEL;

1108 return -1;

1109 }

1110

1111 tab->request.state = STATE_DNS;

1112 return 0;

1113 }

1114

1115 int gmi_request_connect(struct gmi_tab* tab) {

1116

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

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

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

1120 "Failed to create socket");

1121 return -1;

1122 }

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

1124 if (flags == -1 ||

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

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

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

1128 return -1;

1129 }

1130

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

1132 sizeof(struct sockaddr_in):

1133 sizeof(struct sockaddr_in6);

1134

1135 int connected = 0;

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

1137 tab->request.addr, addr_size);

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

1139 errno != EINPROGRESS && errno != EALREADY &&

1140 errno != 0):failed;

1141 while (!failed) {

1142 struct pollfd fds[2];

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

1144 fds[0].events = POLLIN;

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

1146 fds[1].events = POLLOUT;

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

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

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

1150 int value;

1151 socklen_t len = sizeof(value);

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

1153 SO_ERROR, &value, &len);

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

1155 break;

1156 }

1157

1158 if (!connected) {

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

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

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

1162 return -1;

1163 }

1164

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

1166 tab->request.host)) {

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

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

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

1170 return -1;

1171 }

1172 return 0;

1173 }

1174

1175 int gmi_request_handshake(struct gmi_tab* tab) {

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

1177 tab->request.socket,

1178 tab->request.host)) {

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

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

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

1182 return -1;

1183 }

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

1185 int ret = 0;

1186 time_t start = time(0);

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

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

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

1190 (ret < 0 &&

1191 ret != TLS_WANT_POLLIN &&

1192 ret != TLS_WANT_POLLOUT))

1193 break;

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

1195 nanosleep(&timeout, NULL);

1196 }

1197 if (ret) {

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

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

1200 tls_error(tab->request.tls),

1201 tab->request.host);

1202 return -1;

1203 }

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

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

1206 tls_peer_cert_hash(tab->request.tls),

1207 tls_peer_cert_notbefore(tab->request.tls),

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

1209 switch (ret) {

1210 case 0: // success

1211 break;

1212 case -1: // invalid certificate

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

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

1215 "(The certificate changed)",

1216 tab->request.host);

1217 return -1;

1218 case -3: // sandbox error

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

1220 "Sandbox error when verifying server certificate " \

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

1222 return -1;

1223 case -5: // expired

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

1225 "Expired certificate, " \

1226 "the certificate for %s has expired",

1227 tab->request.host);

1228 return -1;

1229 case -6: // certificate changed

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

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

1232 " to forget the old certificate.",

1233 tab->request.host);

1234 return -1;

1235 default: // failed to write certificate

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

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

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

1239 return -1;

1240 }

1241

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

1243 char buf[MAX_URL];

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

1245 char toascii[1024];

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

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

1248 toascii, sizeof(toascii))) {

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

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

1251 return -1;

1252 }

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

1254 char url[1024];

1255 if (len < MAX_URL)

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

1257 if (len >= MAX_URL ||

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

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

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

1261 return -1;

1262 }

1263

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

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

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

1267 return -1;

1268 }

1269

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

1271 tab->request.state = STATE_CONNECTED;

1272 return 0;

1273 }

1274

1275 int gmi_request_header(struct gmi_tab* tab) {

1276 time_t now = time(0);

1277 char buf[1024];

1278 int recv = 0;

1279 while (1) {

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

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

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

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

1284 tab->request.host);

1285 return -1;

1286 }

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

1288 sizeof(buf) - recv - 1);

1289 if (n == TLS_WANT_POLLIN) {

1290 nanosleep(&timeout, NULL);

1291 continue;

1292 }

1293 if (n < 1) {

1294 recv = -1;

1295 break;

1296 }

1297 recv += n;

1298 buf[recv] = '\0';

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

1300 }

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

1302

1303 if (recv <= 0) {

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

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

1306 recv, tab->request.host);

1307 return -1;

1308 }

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

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

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

1312 return -1;

1313 }

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

1315 if (!ptr) {

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

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

1318 tab->request.host);

1319 return -1;

1320 }

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

1322

1323 int previous_code = tab->page.code;

1324 char c = buf[2];

1325 buf[2] = '\0';

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

1327 buf[2] = c;

1328

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

1330 switch (tab->page.code) {

1331 case 10:

1332 case 11:

1333 ptr++;

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

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

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

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

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

1339 tab->request.state = STATE_RECV_HEADER;

1340 tab->request.recv = recv;

1341 return 2; // input

1342 case 20:

1343 {

1344 #ifdef TERMINAL_IMG_VIEWER

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

1346 #endif

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

1348 if (!meta) {

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

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

1351 return -1;

1352 }

1353 *meta = '\0';

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

1355 *meta = '\r';

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

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

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

1359 #ifdef TERMINAL_IMG_VIEWER

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

1361 #endif

1362 )) {

1363 if (tab->history)

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

1365 sizeof(tab->url));

1366 else

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

1368 sizeof(tab->url));

1369 tab->request.ask = 2;

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

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

1372 tab->request.meta);

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

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

1375 tb_interupt();

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

1377 nanosleep(&timeout, NULL);

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

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

1380 tab->request.recv = -1;

1381 return 1; // download

1382 }

1383 }

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

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

1386 }

1387 break;

1388 case 30:

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

1390 "Redirect temporary");

1391 break;

1392 case 31:

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

1394 "Redirect permanent");

1395 break;

1396 case 40:

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

1398 "Temporary failure");

1399 return -2;

1400 case 41:

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

1402 "Server unavailable");

1403 return -2;

1404 case 42:

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

1406 "CGI error");

1407 return -2;

1408 case 43:

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

1410 "Proxy error");

1411 return -2;

1412 case 44:

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

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

1415 return -2;

1416 case 50:

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

1418 "Permanent failure");

1419 return -2;

1420 case 51:

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

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

1423 return -2;

1424 case 52:

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

1426 "Resource gone");

1427 return -2;

1428 case 53:

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

1430 "Proxy request refused");

1431 return -2;

1432 case 59:

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

1434 "Bad request");

1435 return -2;

1436 case 60:

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

1438 "Client certificate required");

1439 return -2;

1440 case 61:

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

1442 "Client not authorised");

1443 return -2;

1444 case 62:

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

1446 "Client certificate not valid");

1447 return -2;

1448 default:

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

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

1451 tab->page.code = previous_code;

1452 return -2;

1453 }

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

1455 tab->request.state = STATE_RECV_HEADER;

1456 tab->request.recv = recv;

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

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

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

1460 return 0;

1461 }

1462

1463 int gmi_request_body(struct gmi_tab* tab) {

1464 time_t now = time(0);

1465 char buf[1024];

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

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

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

1469 "Server %s stopped responding",

1470 tab->request.host);

1471 return -1;

1472 }

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

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

1475 if (bytes == 0) break;

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

1477 nanosleep(&timeout, NULL);

1478 continue;

1479 }

1480 if (bytes < 1) {

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

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

1483 tab->request.host,

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

1485 return -1;

1486 }

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

1488 tab->request.recv + bytes + 1);

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

1490 tab->request.data[tab->request.recv + bytes] = '\0';

1491 tab->request.recv += bytes;

1492 now = time(0);

1493 }

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

1495 tab->request.state = STATE_RECV_BODY;

1496 return 0;

1497 }

1498

1499 void* gmi_request_thread(struct gmi_tab* tab) {

1500 unsigned int signal = 0;

1501 while (!client.shutdown) {

1502 tab->selected = 0;

1503 tab->request.state = STATE_DONE;

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

1505 tb_interupt();

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

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

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

1509 tab->request.state = STATE_STARTED;

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

1511 tab->thread.add);

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

1513 ret || gmi_request_dns(tab)) {

1514 if (tab->request.tls) {

1515 tls_close(tab->request.tls);

1516 tls_free(tab->request.tls);

1517 tab->request.tls = NULL;

1518 }

1519 if (ret == -1)

1520 tab->show_error = 1;

1521 tab->request.recv = ret;

1522 continue;

1523 }

1524 if (gmi_request_connect(tab) ||

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

1526 close(tab->request.socket);

1527 if (tab->request.tls) {

1528 tls_close(tab->request.tls);

1529 tls_free(tab->request.tls);

1530 tab->request.tls = NULL;

1531 }

1532 tab->show_error = 1;

1533 tab->request.recv = -1;

1534 continue;

1535 }

1536 ret = -1;

1537 if (!gmi_request_handshake(tab) &&

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

1539 ret = gmi_request_header(tab);

1540 if (!ret || ret == 2) gmi_request_body(tab);

1541 }

1542

1543 if (ret == -2) {

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

1545 if (cr) {

1546 *cr = '\0';

1547 char buf[256];

1548 snprintf(buf, sizeof(buf),

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

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

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

1552 *cr = '\r';

1553 }

1554 }

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

1556 free(tab->request.data);

1557 tab->request.data = NULL;

1558 if (tab->history)

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

1560 sizeof(tab->url));

1561 else

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

1563 sizeof(tab->url));

1564 tab->show_error = 1;

1565 tab->request.recv = -1;

1566 tab->page.code = 20;

1567 }

1568

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

1570 close(tab->request.socket);

1571 if (tab->request.tls) {

1572 tls_close(tab->request.tls);

1573 tls_free(tab->request.tls);

1574 tab->request.tls = NULL;

1575 }

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

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

1578 free(tab->request.data);

1579 tab->request.data = NULL;

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

1581 continue;

1582 }

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

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

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

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

1587 if (!ptr) {

1588 free(tab->request.data);

1589 continue;

1590 }

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

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

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

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

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

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

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

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

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

1600 free(tab->request.data);

1601 continue;

1602 }

1603 tab->page.data_len = r;

1604 free(tab->request.data);

1605 tab->request.data = NULL;

1606 gmi_load(&tab->page);

1607 tab->history->page = tab->page;

1608 tab->history->cached = 1;

1609 tab->request.recv = r;

1610 continue;

1611 }

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

1613 tab->request.recv > 0 &&

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

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

1616 struct gmi_link* link_ptr = tab->history?

1617 tab->history->prev:NULL;

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

1619 if (!link_ptr) break;

1620 link_ptr = link_ptr->prev;

1621 }

1622 while (link_ptr) {

1623 if (link_ptr->cached) {

1624 gmi_freepage(&link_ptr->page);

1625 link_ptr->cached = 0;

1626 }

1627 link_ptr = link_ptr->prev;

1628 }

1629 pthread_mutex_lock(&tab->render_mutex);

1630 if (!tab->thread.add)

1631 gmi_freepage(&tab->page);

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

1633 tab->page.code = 20;

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

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

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

1637 sizeof(tab->url));

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

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

1640 gmi_load(&tab->page);

1641 if (tab->thread.add)

1642 gmi_addtohistory(tab);

1643 else if (tab->history) {

1644 tab->history->page = tab->page;

1645 tab->history->cached = 1;

1646 }

1647 tab->scroll = -1;

1648 pthread_mutex_unlock(&tab->render_mutex);

1649 }

1650 if (tab->request.download &&

1651 tab->request.recv > 0 &&

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

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

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

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

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

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

1658 #ifndef SANDBOX_SUN

1659 int fd = getdownloadfd();

1660 if (fd < 0) {

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

1662 "Unable to open download folder");

1663 tab->show_error = 1;

1664 goto request_end;

1665 }

1666 #endif

1667 char path[1024];

1668 if (ptr)

1669 strlcpy(path, ptr + 1, sizeof(path));

1670 else {

1671 #ifdef __OpenBSD__

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

1673 #else

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

1675 #endif

1676

1677 snprintf(path, sizeof(path),

1678 format, time(NULL));

1679 }

1680 for (unsigned int i = 0;

1681 i < sizeof(path) && path[i] && ptr; i++) {

1682 char c = path[i];

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

1684 !(c == '.' ||

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

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

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

1688 ))

1689 path[i] = '_';

1690 }

1691 #ifdef SANDBOX_SUN

1692 if (sandbox_download(tab, path))

1693 goto request_end;

1694 int dfd = wr_pair[1];

1695 #else

1696 int dfd = openat(fd, path,

1697 O_CREAT|O_EXCL|O_WRONLY, 0600);

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

1699 char buf[1024];

1700 #ifdef __OpenBSD__

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

1702 #else

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

1704 #endif

1705 snprintf(buf, sizeof(buf), format,

1706 time(NULL), path);

1707 strlcpy(path, buf, sizeof(path));

1708 dfd = openat(fd, path,

1709 O_CREAT|O_EXCL|O_WRONLY, 0600);

1710 if (dfd < 0) {

1711 snprintf(tab->error,

1712 sizeof(tab->error),

1713 "Failed to write to " \

1714 "the download folder");

1715 tab->show_error = 1;

1716 goto request_end;

1717 }

1718 }

1719 #ifdef SANDBOX_FREEBSD

1720 if (makefd_writeonly(dfd)) {

1721 client.shutdown = 1;

1722 break;

1723 }

1724 #endif

1725 #endif

1726 ptr = strchr(tab->request.data, '\n') + 1;

1727 if (!ptr) {

1728 tab->show_error = 1;

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

1730 "Server sent invalid data");

1731 goto request_end;

1732 }

1733 #ifdef SANDBOX_SUN

1734 sandbox_dl_length(tab->request.recv -

1735 (ptr-tab->request.data));

1736 #endif

1737 write(dfd, ptr,

1738 tab->request.recv - (ptr-tab->request.data));

1739 #ifndef SANDBOX_SUN

1740 close(dfd);

1741 #endif

1742 #ifndef DISABLE_XDG

1743 int fail = 0;

1744 if (client.xdg) {

1745 tab->request.ask = 2;

1746 snprintf(tab->request.info,

1747 sizeof(tab->request.info),

1748 "# %s download",

1749 tab->request.meta);

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

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

1752 tb_interupt();

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

1754 nanosleep(&timeout, NULL);

1755 if (tab->request.ask)

1756 fail = xdg_open(path);

1757 }

1758 if (fail) {

1759 tab->show_error = 1;

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

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

1762 } else

1763 #endif

1764 {

1765 tab->show_info = 1;

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

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

1768 }

1769 request_end:

1770 free(tab->request.data);

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

1772 }

1773 }

1774 return NULL;

1775 }

1776

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

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

1779 tab->request.state = STATE_CANCEL;

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

1781 nanosleep(&timeout, NULL);

1782 tab->thread.add = add;

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

1784 int signal = 0;

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

1786 sizeof(signal))

1787 return -1;

1788 return 0;

1789 }

1790

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

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

1793 if (!f) {

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

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

1796 tab->show_error = 1;

1797 return -1;

1798 }

1799 #ifdef SANDBOX_FREEBSD

1800 if (make_readonly(f)) {

1801 fclose(f);

1802 client.shutdown = 1;

1803 return -1;

1804 }

1805 #endif

1806 fseek(f, 0, SEEK_END);

1807 size_t len = ftell(f);

1808 fseek(f, 0, SEEK_SET);

1809 char* data = malloc(len);

1810 if (!data) return fatalI();

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

1812 fclose(f);

1813 free(data);

1814 return -1;

1815 }

1816 fclose(f);

1817 pthread_mutex_lock(&tab->render_mutex);

1818 tab->page.code = 20;

1819 tab->page.data_len = len;

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

1821 tab->page.data = data;

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

1823 int i = strnlen(path, 1024);

1824 int gmi = 0;

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

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

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

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

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

1830 gmi = 1;

1831 if (gmi)

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

1833 else

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

1835 tab->page.no_header = 1;

1836 gmi_load(&tab->page);

1837 gmi_addtohistory(tab);

1838 pthread_mutex_unlock(&tab->render_mutex);

1839 return len;

1840 }

1841

1842 int gmi_init() {

1843 if (tls_init()) {

1844 tb_shutdown();

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

1846 return -1;

1847 }

1848

1849 config = tls_config_new();

1850 if (!config) {

1851 tb_shutdown();

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

1853 return -1;

1854 }

1855

1856 config_empty = tls_config_new();

1857 if (!config_empty) {

1858 tb_shutdown();

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

1860 return -1;

1861 }

1862

1863 tls_config_insecure_noverifycert(config);

1864 tls_config_insecure_noverifycert(config_empty);

1865 #ifndef DISABLE_XDG

1866 int xdg = client.xdg;

1867 #endif

1868 #ifdef SANDBOX_SUN

1869 char** bookmarks = client.bookmarks;

1870 #endif

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

1872 #ifdef SANDBOX_SUN

1873 client.bookmarks = bookmarks;

1874 #endif

1875 #ifndef DISABLE_XDG

1876 client.xdg = xdg;

1877 #endif

1878

1879 int fd = getconfigfd();

1880 if (fd < 0) {

1881 tb_shutdown();

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

1883 return -1;

1884 }

1885

1886 #ifndef SANDBOX_SUN

1887 if (gmi_loadbookmarks()) {

1888 gmi_newbookmarks();

1889 }

1890

1891 if (cert_load()) {

1892 tb_shutdown();

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

1894 return -1;

1895 }

1896 #endif

1897

1898 return 0;

1899 }

1900

1901 void gmi_free() {

1902 while (client.tab)

1903 gmi_freetab(client.tab);

1904 gmi_savebookmarks();

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

1906 free(client.bookmarks[i]);

1907 free(client.bookmarks);

1908 cert_free();

1909 }

1910