💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 04004a65b0805493b16d2abca9402… captured on 2023-09-28 at 16:18:59. Gemini links have been rewritten to link to archived content

View Raw

More Information

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

Go Back

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

1 #ifdef __linux__

2 #define _GNU_SOURCE

3 #endif

4 #ifdef sun

5 #include <port.h>

6 #endif

7 #include <pthread.h>

8 #include <netinet/in.h>

9 #include <stdint.h>

10 #include <stdlib.h>

11 #include <stdio.h>

12 #include <strings.h>

13 #include <unistd.h>

14 #include <sys/socket.h>

15 #include <string.h>

16 #include <netdb.h>

17 #include <tls.h>

18 #include <poll.h>

19 #include <fcntl.h>

20 #include <errno.h>

21 #include <termbox.h>

22 #include <ctype.h>

23 #include <time.h>

24 #include "gemini.h"

25 #include "cert.h"

26 #include "wcwidth.h"

27 #include "display.h"

28 #include "input.h"

29 #include "sandbox.h"

30 #include "str.h"

31 #include "url.h"

32 #include "util.h"

33

34 #define MAX_CACHE 10

35 #define TIMEOUT 8

36 struct timespec timeout = {0, 10000000};

37

38 struct tls_config* config;

39 struct tls_config* config_empty;

40 struct gmi_client client;

41

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

43 void* gmi_request_thread(struct gmi_tab* tab);

44

45 void fatal() {

46 tb_shutdown();

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

48 exit(0);

49 }

50

51 int fatalI() {

52 fatal();

53 return -1;

54 }

55

56 void* fatalP() {

57 fatal();

58 return NULL;

59 }

60

61 void memory_failure(struct gmi_tab* tab) {

62 if (!tab) return;

63 tab->show_error = 1;

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

65 "memory allocation failure");

66 }

67

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

69 id--;

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

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

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

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

74 tab->show_error = 1;

75 client.input.mode = 0;

76 return -1;

77 }

78 gmi_cleanforward(tab);

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

80 return ret;

81 }

82

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

84 id--;

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

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

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

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

89 tab->show_error = 1;

90 client.input.mode = 0;

91 return -1;

92 }

93 struct gmi_tab* old_tab = client.tab;

94 client.tab = gmi_newtab_url(NULL);

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

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

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

98

99 return ret;

100 }

101

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

103 int url_len = strnlen(url, MAX_URL);

104

105 if (!*link) {

106 return 0;

107 }

108 if (STARTWITH(link, "about:")) {

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

110 gmi_gohome(tab, 1);

111 return 0;

112 }

113 tab->show_error = 1;

114 snprintf(tab->error, sizeof(tab->error), "Invalid url");

115 return -1;

116 }

117 if (link[0] == '/' && link[1] == '/') {

118 char buf[1024];

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

120 int len = STRNLEN(buf);

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

122 buf[len] = '/';

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

124 }

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

126 if (ret < 1) return ret;

127 return ret;

128 }

129 if (link[0] == '/') {

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

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

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

133 }

134 char urlbuf[MAX_URL];

135 size_t l = STRLCPY(urlbuf, url);

136 if (l >= sizeof(urlbuf))

137 goto nextlink_overflow;

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

139 sizeof(urlbuf) - l)

140 goto nextlink_overflow;

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

142 return ret;

143 }

144 if (strstr(link, "https://") == link ||

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

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

147 #ifndef DISABLE_XDG

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

149 #endif

150 tab->show_error = 1;

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

152 "Unable to open the link");

153 return -1;

154 }

155 if (strstr(link, "gemini://") == link) {

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

157 return ret;

158 }

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

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

161 char urlbuf[MAX_URL];

162 size_t l = STRLCPY(urlbuf, url);

163 if (l >= sizeof(urlbuf))

164 goto nextlink_overflow;

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

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

167 sizeof(urlbuf) - l);

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

169 goto nextlink_overflow;

170 l += l2;

171 }

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

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

174 goto nextlink_overflow;

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

176 return ret;

177 nextlink_overflow:

178 tab->show_error = 1;

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

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

181 return -1;

182 }

183

184 void gmi_load(struct gmi_page* page) {

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

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

187 free(page->links);

188 page->links = NULL;

189 page->links_count = 0;

190 page->lines = 0;

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

192 page->links = NULL;

193 page->links_count = 0;

194 page->lines = 0;

195 return;

196 }

197 int x = 0;

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

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

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

201 c += 2;

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

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

204 c++) ;

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

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

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

208 c--;

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

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

211 void *ptr = NULL;

212 while (!ptr) {

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

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

215 malloc(sizeof(char*));

216 if (!ptr) sleep(1);

217 }

218 page->links = ptr;

219

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

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

222 page->data[c] = save;

223 continue;

224 }

225 int len = strnlen(url, MAX_URL);

226 ptr = NULL;

227 while (!ptr) {

228 ptr = malloc(len+2);

229 if (!ptr) sleep(1);

230 }

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

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

233 for (int i = 0; page->links[page->links_count][i] &&

234 i < len; ) {

235 uint32_t ch;

236 int len;

237 len = tb_utf8_char_to_unicode(&ch,

238 &page->links[page->links_count][i]);

239 if (ch < ' ')

240 page->links[page->links_count][i] = 0;

241 i += len;

242 }

243 page->links_count++;

244 page->data[c] = save;

245 }

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

247 page->lines++;

248 x = 0;

249 continue;

250 }

251 x++;

252 }

253 }

254

255 int gmi_render(struct gmi_tab* tab) {

256 pthread_mutex_lock(&tab->render_mutex);

257 #include "img.h"

258 #ifdef TERMINAL_IMG_VIEWER

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

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

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

262 if (!ptr) {

263 tb_printf(2, -tab->scroll,

264 TB_DEFAULT, TB_DEFAULT,

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

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

267 pthread_mutex_unlock(&tab->render_mutex);

268 return 1;

269 }

270 ptr++;

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

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

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

274 }

275 tab->page.img.data =

276 stbi_load_from_memory((unsigned char*)ptr,

277 tab->page.data_len -

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

279 &tab->page.img.w,

280 &tab->page.img.h,

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

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

283 tb_printf(2, -tab->scroll,

284 TB_DEFAULT, TB_DEFAULT,

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

286 tab->page.meta);

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

288 pthread_mutex_unlock(&tab->render_mutex);

289 return 1;

290 }

291

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

293 }

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

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

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

297 client.tabs_count>1);

298 pthread_mutex_unlock(&tab->render_mutex);

299 return 1;

300 }

301 }

302 #endif

303 int text = 0;

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

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

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

307 "Unable to render format : %s",

308 tab->page.meta);

309 pthread_mutex_unlock(&tab->render_mutex);

310 return 1;

311 }

312 text = 1;

313 }

314 int line = 0;

315 int x = 0;

316 int links = 0;

317 uintattr_t color = TB_DEFAULT;

318 int start = 1;

319 int ignore = 0;

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

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

322 &client.input.field[1] : tab->search.entry;

323 int search_len = search ?

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

325 if (!search_len) search = NULL;

326 int highlight = 0;

327 int hlcolor = YELLOW;

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

329 tab->search.cursor = 0;

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

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

332 int previous_count = tab->search.count;

333 tab->search.count = 0;

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

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

336 line++;

337 int w = tb_width();

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

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

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

341 line += 1;

342 continue;

343 }

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

345 x += 8 - x%8;

346 continue;

347 }

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

349 if (!text && start &&

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

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

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

353 ignore = !ignore;

354 c += 3;

355 continue;

356 }

357 if (ignore)

358 color = ORANGE;

359

360 if (!ignore && !text) {

361 for (int i=0;

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

363 i++) {

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

365 color = RED + i;

366 break;

367 }

368 }

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

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

371 color = ITALIC|CYAN;

372 }

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

374 color = ITALIC|MAGENTA;

375 }

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

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

378 char buf[32];

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

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

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

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

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

384 links+1 ==

385 tab->selected?RED:BLUE,

386 TB_DEFAULT, buf);

387 }

388 x += len;

389 c += 2;

390

391 while (

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

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

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

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

396

397 int initial = c;

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

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

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

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

402

403 while (

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

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

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

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

408

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

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

411 c = initial;

412 x += 3;

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

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

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

416 links++;

417 }

418 }

419

420 if (search &&

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

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

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

424 (previous_count - 1)))

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

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

427 hlcolor++;

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

429 tab->search.count == 0)

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

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

432 hlcolor--;

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

434 }

435 highlight += search_len;

436 tab->search.count++;

437 }

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

439 x+4 >= w) {

440 int end = 0;

441 if (x+4 >= w)

442 end = 1;

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

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

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

446 if (i > w - 4) {

447 newline = 0;

448 break;

449 }

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

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

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

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

454 break;

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

456 }

457 if (newline) {

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

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

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

461 color = TB_DEFAULT;

462 start = 1;

463 } else c--;

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

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

466 x = 0;

467 continue;

468 } else if (end) {

469 c++;

470 x++;

471 }

472 }

473 uint32_t ch = 0;

474 int size = tb_utf8_char_to_unicode(&ch,

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

476 if (size > 0)

477 c += tb_utf8_char_to_unicode(&ch,

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

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

480

481 int wc = mk_wcwidth(ch);

482 if (wc < 0) wc = 0;

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

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

485 if (wc == 1)

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

487 ch, color,

488 highlight?hlcolor:TB_DEFAULT);

489 else

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

491 &ch, wc, color,

492 highlight?hlcolor:TB_DEFAULT);

493 }

494 if (highlight > 0)

495 highlight--;

496

497 x += wc;

498 start = 0;

499 }

500 line++;

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

502 if (h > line) {

503 pthread_mutex_unlock(&tab->render_mutex);

504 return line;

505 }

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

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

508 if (size < 1) size = 1;

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

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

511 + (client.tabs_count>1);

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

513 if (pos < 0) pos = 0;

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

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

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

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

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

519 else

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

521 }

522 pthread_mutex_unlock(&tab->render_mutex);

523 return line;

524 }

525

526 void gmi_addtohistory(struct gmi_tab* tab) {

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

528 return;

529 gmi_cleanforward(tab);

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

531 if (!link) {

532 memory_failure(tab);

533 return;

534 }

535 link->next = NULL;

536 link->prev = tab->history;

537 if (link->prev)

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

539 link->page = tab->page;

540 link->cached = 1;

541 STRLCPY(link->url, tab->url);

542 tab->history = link;

543 if (link->prev)

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

545 }

546

547 void gmi_cleanforward(struct gmi_tab* tab) {

548 if (!tab->history)

549 return;

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

551 while (link) {

552 struct gmi_link* ptr = link->next;

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

554 if (link->cached) {

555 gmi_freepage(&link->page);

556 link->cached = 0;

557 }

558 free(link);

559 link = ptr;

560 }

561 tab->history->next = NULL;

562 }

563

564 void gmi_freepage(struct gmi_page* page) {

565 if (!page) return;

566 #ifdef TERMINAL_IMG_VIEWER

567 if (page->img.data)

568 stbi_image_free(page->img.data);

569 #endif

570 free(page->data);

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

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

573 free(page->links);

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

575 }

576

577 void gmi_freetab(struct gmi_tab* tab) {

578 if (!tab) return;

579 tab->request.state = STATE_CANCEL;

580 int signal = 0xFFFFFFFF;

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

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

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

584 if (tab->history) {

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

586 while (link) {

587 struct gmi_link* ptr = link->next;

588 if (link->cached) {

589 gmi_freepage(&link->page);

590 link->cached = 0;

591 }

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

593 free(link);

594 link = ptr;

595 }

596 link = tab->history;

597 while (link) {

598 struct gmi_link* ptr = link->prev;

599 if (link->cached) {

600 gmi_freepage(&link->page);

601 link->cached = 0;

602 }

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

604 free(link);

605 link = ptr;

606 }

607 }

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

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

610 struct gmi_tab* prev = tab->prev;

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

612 pthread_mutex_destroy(&tab->render_mutex);

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

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

615 #ifdef TERMINAL_IMG_VIEWER

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

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

618 #endif

619 free(tab);

620 client.tab = prev;

621 client.tabs_count--;

622 }

623

624 char home_page[] =

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

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

627 "## Bookmarks\n\n" \

628 "%s\n" \

629 "## Keybindings\n\n" \

630 "* k - Scroll up\n" \

631 "* j - Scroll down\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

648 "## Commands\n\n" \

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

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

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

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

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

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

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

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

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

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

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

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

661

662 void gmi_newbookmarks() {

663 int len;

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

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

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

667 if (!client.bookmarks) goto fail_malloc;

668

669 len = sizeof(geminispace);

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

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

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

673

674 len = sizeof(gemigit);

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

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

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

678

679 client.bookmarks[2] = NULL;

680 return;

681 fail_malloc:

682 fatal();

683 }

684

685 int gmi_loadbookmarks() {

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

687 if (fd < 0)

688 return -1;

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

690 if (!f)

691 return -1;

692 #ifdef SANDBOX_FREEBSD

693 if (makefd_readonly(fd)) {

694 fclose(f);

695 return -1;

696 }

697 #endif

698 fseek(f, 0, SEEK_END);

699 size_t len = ftell(f);

700 fseek(f, 0, SEEK_SET);

701 char* data = malloc(len);

702 if (!data) {

703 memory_failure(client.tab);

704 fclose(f);

705 return -1;

706 }

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

708 fclose(f);

709 return -1;

710 }

711 fclose(f);

712 char* ptr = data;

713 long n = 0;

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

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

716 if (!client.bookmarks) {

717 memory_failure(client.tab);

718 return -1;

719 }

720 client.bookmarks[n] = NULL;

721 n = 0;

722 ptr = data;

723 char* str = data;

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

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

726 *ptr = '\0';

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

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

729 memory_failure(client.tab);

730 break;

731 }

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

733 n++;

734 str = ptr + 1;

735 }

736 free(data);

737 return 0;

738 }

739

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

741 int n = 0;

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

743 n = tb_utf8_char_length(str[i]);

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

745 str[i] = '\0';

746 break;

747 }

748 }

749

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

751 if (page->title_cached) return;

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

753 page->title_cached = 1;

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

755 goto use_url;

756 }

757 int start = -1;

758 int end = -1;

759 int line_start = 1;

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

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

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

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

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

765 break;

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

767 start = j;

768 break;

769 }

770 }

771 }

772 line_start = 0;

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

774 line_start = 1;

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

776 end = i;

777 break;

778 }

779 }

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

781 goto use_url;

782 size_t len = end - start + 1;

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

784 len < sizeof(page->title)?

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

786 sanitize(page->title, len);

787 return;

788 use_url:

789 if (!url) {

790 size_t len = STRLCPY(page->title, page->meta);

791 sanitize(page->title, len);

792 return;

793 }

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

795 if (!str) {

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

797 goto sanitize;

798 }

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

800 str++;

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

802 sanitize:

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

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

805 page->title[i] = 0;

806 }

807

808 int gmi_removebookmark(int index) {

809 index--;

810 if (index < 0) return -1;

811 int fail = -1;

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

813 if (i == index) {

814 free(client.bookmarks[i]);

815 fail = 0;

816 }

817 if (!fail)

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

819 }

820 return fail;

821 }

822

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

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

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

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

827 tab->show_error = 1;

828 return;

829 }

830 int title_len = 0;

831 if (!title) {

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

833 title = tab->page.title;

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

835 if (!title_len) {

836 title_len = sizeof("no title");

837 title = "no title";

838 }

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

840 long n = 0;

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

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

843 if (!ptr) {

844 memory_failure(tab);

845 return;

846 }

847 client.bookmarks = ptr;

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

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

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

851 memory_failure(tab);

852 return;

853 }

854 if (title)

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

856 else

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

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

859 gmi_savebookmarks();

860 }

861

862 int gmi_savebookmarks() {

863 #ifdef SANDBOX_SUN

864 int fd = wr_pair[1];

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

866 return -1;

867 #else

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

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

870 if (fd < 0) {

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

872 return -1;

873 }

874 #ifdef SANDBOX_FREEBSD

875 if (makefd_writeonly(fd)) {

876 close(fd);

877 return -1;

878 }

879 #endif

880 #endif

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

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

883 char c = '\n';

884 write(fd, &c, 1);

885 }

886 #ifdef SANDBOX_SUN

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

888 #else

889 close(fd);

890 #endif

891 return 0;

892 }

893

894 char* gmi_getbookmarks(int* len) {

895 char* data = NULL;

896 int n = 0;

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

898 char line[2048];

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

900 client.bookmarks[i]);

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

902 if (!ptr) {

903 *len = n;

904 return data;

905 }

906 data = ptr;

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

908 n += length-1;

909 }

910 *len = n;

911 return data;

912 }

913

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

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

916 int bm = 0;

917 char* data = gmi_getbookmarks(&bm);

918 pthread_mutex_lock(&tab->render_mutex);

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

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

921 memory_failure(tab);

922 pthread_mutex_unlock(&tab->render_mutex);

923 return;

924 }

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

926

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

928 home_page, data ? data : "");

929 free(data);

930

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

932

933 if (!add)

934 gmi_freepage(&tab->page);

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

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

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

938 tab->page.code = 20;

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

940 gmi_load(&tab->page);

941 if (add)

942 gmi_addtohistory(tab);

943 else if (tab->history) {

944 tab->history->page = tab->page;

945 tab->history->cached = 1;

946 }

947 tab->scroll = -1;

948 tab->request.data = NULL;

949 pthread_mutex_unlock(&tab->render_mutex);

950 }

951

952 struct gmi_tab* gmi_newtab() {

953 return gmi_newtab_url("about:home");

954 }

955

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

957

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

959 struct gmi_tab tmp;

960 int proto = parse_url(url,

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

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

963 &tmp.request.port);

964 switch (proto) {

965 case PROTO_GEMINI:

966 break;

967 case PROTO_HTTP:

968 case PROTO_HTTPS:

969 case PROTO_GOPHER:

970 xdg_request(url);

971 /* fallthrough */

972 default:

973 return client.tab;

974 }

975 }

976

977 client.tabs_count++;

978 if (client.tab) {

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

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

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

982 memory_failure(client.tab);

983 return client.tab;

984 }

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

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

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

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

989 if (next)

990 next->prev = client.tab;

991 } else {

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

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

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

995 }

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

997

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

999 return client.tab;

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

1001 (void *(*)(void *))gmi_request_thread,

1002 (void*)client.tab);

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

1004 if (url)

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

1006

1007 client.tab->scroll = -1;

1008 return client.tab;

1009 }

1010

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

1012 if (!tab) return -1;

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

1014 gmi_gohome(tab, add);

1015 return tab->page.data_len;

1016 }

1017 tab->show_error = 0;

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

1019 tab->selected = 0;

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

1021 int proto = parse_url(url,

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

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

1024 &tab->request.port);

1025

1026 if (proto == -2) {

1027 tab->show_error = 1;

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

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

1030 return -1;

1031 }

1032 if (proto == PROTO_FILE) {

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

1034 }

1035 if (proto != PROTO_GEMINI) {

1036 #ifndef DISABLE_XDG

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

1038 #endif

1039 tab->show_error = 1;

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

1041 "Unable to open the link");

1042 return -1;

1043 }

1044 if (tab->request.tls)

1045 tls_reset(tab->request.tls);

1046 else

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

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

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

1050 "Failed to initialize TLS");

1051 tab->show_error = 1;

1052 return -1;

1053 }

1054

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

1056 if (cert >= 0 &&

1057 tls_config_set_keypair_mem(config,

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

1059 client.certs[cert].crt_len,

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

1061 client.certs[cert].key_len))

1062 {

1063 tab->show_error = 1;

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

1065 tls_config_error(config));

1066 return -1;

1067 }

1068

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

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

1071 "Failed to configure TLS");

1072 tab->show_error = 1;

1073 return -1;

1074 }

1075 tab->request.state = STATE_REQUESTED;

1076 return 0;

1077 }

1078

1079 #ifdef __linux

1080 void dns_async(union sigval sv) {

1081 struct gmi_tab* tab = sv.sival_ptr;

1082 tab->request.resolved = 1;

1083 }

1084 #endif

1085

1086 int gmi_request_dns(struct gmi_tab* tab) {

1087 char host[1024];

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

1089 host, sizeof(host))) {

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

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

1092 tab->show_error = 1;

1093 return -1;

1094 }

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

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

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

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

1099 memory_failure(tab);

1100 return -1;

1101 }

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

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

1104 tab->request.resolved = 0;

1105 struct sigevent sevp;

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

1107 sevp.sigev_notify = SIGEV_THREAD;

1108 sevp.sigev_notify_function = dns_async;

1109 sevp.sigev_value.sival_ptr = tab;

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

1111 if (ret) {

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

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

1114 tab->show_error = 1;

1115 return -1;

1116 }

1117

1118 long start = time(NULL);

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

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

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

1122 nanosleep(&timeout, NULL);

1123 }

1124

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

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

1127 gai_cancel(tab->request.gaicb_ptr);

1128 free(tab->request.gaicb_ptr);

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

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

1131 tab->show_error = 1;

1132 return -1;

1133 }

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

1135 #else

1136 struct addrinfo hints, *result;

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

1138 hints.ai_family = AF_INET;

1139 hints.ai_socktype = SOCK_STREAM;

1140 hints.ai_flags |= AI_CANONNAME;

1141 errno = 0;

1142

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

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

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

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

1147 tab->show_error = 1;

1148 return -1;

1149 }

1150 #endif

1151

1152 struct sockaddr_in addr4;

1153 struct sockaddr_in6 addr6;

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

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

1156

1157 int error = 0;

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

1159 addr4.sin_addr =

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

1161 addr4.sin_family = AF_INET;

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

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

1164 tab->request.addr =

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

1166 }

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

1168 addr6.sin6_addr =

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

1170 addr6.sin6_family = AF_INET6;

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

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

1173 tab->request.addr =

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

1175 } else {

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

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

1178 tab->request.host);

1179 error = 1;

1180 }

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

1182 freeaddrinfo(result);

1183 #ifdef __linux__

1184 free(tab->request.gaicb_ptr);

1185 #endif

1186 if (error) {

1187 tab->request.state = STATE_CANCEL;

1188 return -1;

1189 }

1190

1191 tab->request.state = STATE_DNS;

1192 return 0;

1193 }

1194

1195 int gmi_request_connect(struct gmi_tab* tab) {

1196

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

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

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

1200 "Failed to create socket");

1201 return -1;

1202 }

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

1204 if (flags == -1 ||

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

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

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

1208 return -1;

1209 }

1210

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

1212 sizeof(struct sockaddr_in):

1213 sizeof(struct sockaddr_in6);

1214

1215 int connected = 0;

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

1217 tab->request.addr, addr_size);

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

1219 errno != EINPROGRESS && errno != EALREADY &&

1220 errno != 0):failed;

1221 while (!failed) {

1222 struct pollfd fds[2];

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

1224 fds[0].events = POLLIN;

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

1226 fds[1].events = POLLOUT;

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

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

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

1230 int value;

1231 socklen_t len = sizeof(value);

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

1233 SO_ERROR, &value, &len);

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

1235 break;

1236 }

1237

1238 if (!connected) {

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

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

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

1242 return -1;

1243 }

1244

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

1246 tab->request.host)) {

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

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

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

1250 return -1;

1251 }

1252 return 0;

1253 }

1254

1255 int gmi_request_handshake(struct gmi_tab* tab) {

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

1257 tab->request.socket,

1258 tab->request.host)) {

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

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

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

1262 return -1;

1263 }

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

1265 int ret = 0;

1266 time_t start = time(0);

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

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

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

1270 (ret < 0 &&

1271 ret != TLS_WANT_POLLIN &&

1272 ret != TLS_WANT_POLLOUT))

1273 break;

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

1275 nanosleep(&timeout, NULL);

1276 }

1277 if (ret) {

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

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

1280 tls_error(tab->request.tls),

1281 tab->request.host);

1282 return -1;

1283 }

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

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

1286 tls_peer_cert_hash(tab->request.tls),

1287 tls_peer_cert_notbefore(tab->request.tls),

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

1289 switch (ret) {

1290 case 0: // success

1291 break;

1292 case -1: // invalid certificate

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

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

1295 "(The certificate changed)",

1296 tab->request.host);

1297 return -1;

1298 case -3: // sandbox error

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

1300 "Sandbox error when verifying server certificate " \

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

1302 return -1;

1303 case -5: // expired

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

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

1306 "Expired certificate, " \

1307 "the certificate for %s has expired",

1308 tab->request.host);

1309 return -1;

1310 case -6: // certificate changed

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

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

1313 " to forget the old certificate.",

1314 tab->request.host);

1315 return -1;

1316 default: // failed to write certificate

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

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

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

1320 return -1;

1321 }

1322

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

1324 char buf[MAX_URL];

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

1326 char toascii[1024];

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

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

1329 toascii, sizeof(toascii))) {

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

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

1332 return -1;

1333 }

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

1335 char url[1024];

1336 if (len < MAX_URL)

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

1338 if (len >= MAX_URL ||

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

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

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

1342 return -1;

1343 }

1344

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

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

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

1348 return -1;

1349 }

1350

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

1352 tab->request.state = STATE_CONNECTED;

1353 return 0;

1354 }

1355

1356 int gmi_request_header(struct gmi_tab* tab) {

1357 time_t now = time(0);

1358 char buf[1024];

1359 int recv = 0;

1360 while (1) {

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

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

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

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

1365 tab->request.host);

1366 return -1;

1367 }

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

1369 sizeof(buf) - recv - 1);

1370 if (n == TLS_WANT_POLLIN) {

1371 nanosleep(&timeout, NULL);

1372 continue;

1373 }

1374 if (n < 1) {

1375 recv = -1;

1376 break;

1377 }

1378 recv += n;

1379 buf[recv] = '\0';

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

1381 }

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

1383

1384 if (recv <= 0) {

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

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

1387 recv, tab->request.host);

1388 return -1;

1389 }

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

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

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

1393 return -1;

1394 }

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

1396 if (!ptr) {

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

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

1399 tab->request.host);

1400 return -1;

1401 }

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

1403

1404 int previous_code = tab->page.code;

1405 char c = buf[2];

1406 buf[2] = '\0';

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

1408 buf[2] = c;

1409

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

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

1412 tab->redirects = 0;

1413 }

1414 switch (tab->page.code) {

1415 case 10:

1416 case 11:

1417 ptr++;

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

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

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

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

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

1423 tab->request.state = STATE_RECV_HEADER;

1424 tab->request.recv = recv;

1425 return 2; // input

1426 case 20:

1427 {

1428 #ifdef TERMINAL_IMG_VIEWER

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

1430 #endif

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

1432 if (!meta) {

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

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

1435 return -1;

1436 }

1437 *meta = '\0';

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

1439 *meta = '\r';

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

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

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

1443 #ifdef TERMINAL_IMG_VIEWER

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

1445 #endif

1446 )) {

1447 if (tab->history)

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

1449 else

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

1451 tab->request.ask = 2;

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

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

1454 tab->request.meta);

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

1456 tb_interupt();

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

1458 nanosleep(&timeout, NULL);

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

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

1461 tab->request.recv = -1;

1462 return 1; // download

1463 }

1464 }

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

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

1467 }

1468 break;

1469 case 30:

1470 case 31:

1471 tab->redirects++;

1472 if (tab->redirects > MAX_REDIRECT) {

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

1474 "Too many redirects");

1475 return -2;

1476 }

1477 snprintf(tab->error, sizeof(tab->error), "Redirect %s",

1478 tab->page.code == 30 ? "temporary" : "permanent");

1479 break;

1480 case 40:

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

1482 "Temporary failure");

1483 return -2;

1484 case 41:

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

1486 "Server unavailable");

1487 return -2;

1488 case 42:

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

1490 "CGI error");

1491 return -2;

1492 case 43:

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

1494 "Proxy error");

1495 return -2;

1496 case 44:

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

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

1499 return -2;

1500 case 50:

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

1502 "Permanent failure");

1503 return -2;

1504 case 51:

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

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

1507 return -2;

1508 case 52:

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

1510 "Resource gone");

1511 return -2;

1512 case 53:

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

1514 "Proxy request refused");

1515 return -2;

1516 case 59:

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

1518 "Bad request");

1519 return -2;

1520 case 60:

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

1522 "Client certificate required");

1523 return -2;

1524 case 61:

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

1526 "Client not authorised");

1527 return -2;

1528 case 62:

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

1530 "Client certificate not valid");

1531 return -2;

1532 default:

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

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

1535 tab->page.code = previous_code;

1536 return -2;

1537 }

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

1539 tab->request.state = STATE_RECV_HEADER;

1540 tab->request.recv = recv;

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

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

1543 memory_failure(tab);

1544 return -1;

1545 }

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

1547 return 0;

1548 }

1549

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

1551 time_t now = time(0);

1552 char buf[1024];

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

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

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

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

1557 "Server %s stopped responding",

1558 tab->request.host);

1559 return -1;

1560 }

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

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

1563 if (bytes == 0) break;

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

1565 nanosleep(&timeout, NULL);

1566 continue;

1567 }

1568 if (bytes < 1) {

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

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

1571 tab->request.host,

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

1573 return -1;

1574 }

1575 if (fd > 0) {

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

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

1578 "Failed to download file: %s",

1579 strerror(errno));

1580 return -1;

1581 }

1582 now = time(0);

1583 continue;

1584 }

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

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

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

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

1589 if (!ptr) {

1590 free(tab->request.data);

1591 tab->request.data = NULL;

1592 memory_failure(tab);

1593 return -1;

1594 }

1595 tab->request.data = ptr;

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

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

1598 tab->request.recv += bytes;

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

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

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

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

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

1604 return -1;

1605 }

1606 now = time(0);

1607 }

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

1609 tab->request.state = STATE_RECV_BODY;

1610 return 0;

1611 }

1612

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

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

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

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

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

1618 #ifndef SANDBOX_SUN

1619 int fd = getdownloadfd();

1620 if (fd < 0) {

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

1622 "Unable to open download folder");

1623 tab->show_error = 1;

1624 return -1;

1625 }

1626 #endif

1627 if (ptr)

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

1629 else {

1630 #ifdef __OpenBSD__

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

1632 #else

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

1634 #endif

1635

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

1637 }

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

1639 char c = path[i];

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

1641 !(c == '.' ||

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

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

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

1645 ))

1646 path[i] = '_';

1647 }

1648 #ifdef SANDBOX_SUN

1649 if (sandbox_download(tab, path))

1650 return -1;

1651 int dfd = wr_pair[1];

1652 sandbox_dl_length(-1);

1653 #else

1654 int dfd = openat(fd, path,

1655 O_CREAT|O_EXCL|O_WRONLY, 0600);

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

1657 char buf[1024];

1658 #ifdef __OpenBSD__

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

1660 #else

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

1662 #endif

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

1664 strlcpy(path, buf, path_len);

1665 dfd = openat(fd, path,

1666 O_CREAT|O_EXCL|O_WRONLY, 0600);

1667 if (dfd < 0) {

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

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

1670 tab->show_error = 1;

1671 return -1;

1672 }

1673 }

1674 #ifdef SANDBOX_FREEBSD

1675 if (makefd_writeonly(dfd)) {

1676 client.shutdown = 1;

1677 return -1;

1678 }

1679 #endif

1680 #endif

1681 return dfd;

1682 }

1683

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

1685

1686 #ifdef SANDBOX_SUN

1687 if (wr_pair[1] != fd)

1688 return -1;

1689 #else

1690 close(fd);

1691 #endif

1692 #ifndef DISABLE_XDG

1693 int fail = 0;

1694 if (client.xdg) {

1695 tab->request.ask = 2;

1696 snprintf(tab->request.info,

1697 sizeof(tab->request.info),

1698 "# %s download",

1699 tab->request.meta);

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

1701 tb_interupt();

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

1703 nanosleep(&timeout, NULL);

1704 if (tab->request.ask)

1705 fail = xdg_open(path);

1706 }

1707 if (fail) {

1708 tab->show_error = 1;

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

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

1711 } else

1712 #endif

1713 {

1714 tab->show_info = 1;

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

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

1717 }

1718 return fail;

1719 }

1720

1721 void* gmi_request_thread(struct gmi_tab* tab) {

1722 unsigned int signal = 0;

1723 while (!client.shutdown) {

1724 tab->selected = 0;

1725 tab->request.state = STATE_DONE;

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

1727 tb_interupt();

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

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

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

1731 tab->request.state = STATE_STARTED;

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

1733 tab->thread.add);

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

1735 ret || gmi_request_dns(tab)) {

1736 if (tab->request.tls) {

1737 tls_close(tab->request.tls);

1738 tls_free(tab->request.tls);

1739 tab->request.tls = NULL;

1740 }

1741 if (ret == -1)

1742 tab->show_error = 1;

1743 tab->request.recv = ret;

1744 continue;

1745 }

1746 if (gmi_request_connect(tab) ||

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

1748 close(tab->request.socket);

1749 if (tab->request.tls) {

1750 tls_close(tab->request.tls);

1751 tls_free(tab->request.tls);

1752 tab->request.tls = NULL;

1753 }

1754 tab->show_error = 1;

1755 tab->request.recv = -1;

1756 continue;

1757 }

1758 ret = -1;

1759 if (!gmi_request_handshake(tab) &&

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

1761 ret = gmi_request_header(tab);

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

1763 char out[1024];

1764 int fd = -1;

1765 if (tab->request.download)

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

1767 if (gmi_request_body(tab, fd)) {

1768 close(fd);

1769 tab->show_error = 1;

1770 free(tab->request.data);

1771 continue;

1772 }

1773 if (fd > 0)

1774 gmi_postdownload(tab, fd, out);

1775 }

1776 }

1777

1778 if (ret == -2) {

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

1780 if (cr) {

1781 *cr = '\0';

1782 char buf[256];

1783 snprintf(buf, sizeof(buf),

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

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

1786 STRLCPY(tab->error, buf);

1787 *cr = '\r';

1788 }

1789 }

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

1791 free(tab->request.data);

1792 tab->request.data = NULL;

1793 if (tab->history)

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

1795 else

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

1797 tab->show_error = 1;

1798 tab->request.recv = -1;

1799 tab->page.code = 20;

1800 }

1801

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

1803 close(tab->request.socket);

1804 if (tab->request.tls) {

1805 tls_close(tab->request.tls);

1806 tls_free(tab->request.tls);

1807 tab->request.tls = NULL;

1808 }

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

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

1811 free(tab->request.data);

1812 tab->request.data = NULL;

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

1814 continue;

1815 }

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

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

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

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

1820 if (!ptr) {

1821 free(tab->request.data);

1822 continue;

1823 }

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

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

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

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

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

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

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

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

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

1833 free(tab->request.data);

1834 continue;

1835 }

1836 tab->page.data_len = r;

1837 free(tab->request.data);

1838 tab->request.data = NULL;

1839 gmi_load(&tab->page);

1840 tab->history->page = tab->page;

1841 tab->history->cached = 1;

1842 tab->request.recv = r;

1843 continue;

1844 }

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

1846 tab->request.recv > 0 &&

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

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

1849 struct gmi_link* link_ptr = tab->history?

1850 tab->history->prev:NULL;

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

1852 if (!link_ptr) break;

1853 link_ptr = link_ptr->prev;

1854 }

1855 while (link_ptr) {

1856 if (link_ptr->cached) {

1857 gmi_freepage(&link_ptr->page);

1858 link_ptr->cached = 0;

1859 }

1860 link_ptr = link_ptr->prev;

1861 }

1862 pthread_mutex_lock(&tab->render_mutex);

1863 if (!tab->thread.add)

1864 gmi_freepage(&tab->page);

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

1866 tab->page.code = 20;

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

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

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

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

1871 gmi_load(&tab->page);

1872 if (tab->thread.add)

1873 gmi_addtohistory(tab);

1874 else if (tab->history) {

1875 tab->history->page = tab->page;

1876 tab->history->cached = 1;

1877 }

1878 tab->scroll = -1;

1879 pthread_mutex_unlock(&tab->render_mutex);

1880 }

1881 }

1882 return NULL;

1883 }

1884

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

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

1887 tab->request.state = STATE_CANCEL;

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

1889 nanosleep(&timeout, NULL);

1890 tab->thread.add = add;

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

1892 int signal = 0;

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

1894 sizeof(signal))

1895 return -1;

1896 return 0;

1897 }

1898

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

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

1901 if (!f) {

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

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

1904 tab->show_error = 1;

1905 return -1;

1906 }

1907 #ifdef SANDBOX_FREEBSD

1908 if (make_readonly(f)) {

1909 fclose(f);

1910 client.shutdown = 1;

1911 return -1;

1912 }

1913 #endif

1914 fseek(f, 0, SEEK_END);

1915 size_t len = ftell(f);

1916 fseek(f, 0, SEEK_SET);

1917 char* data = malloc(len);

1918 if (!data) {

1919 fclose(f);

1920 memory_failure(tab);

1921 return -1;

1922 }

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

1924 fclose(f);

1925 free(data);

1926 return -1;

1927 }

1928 fclose(f);

1929 pthread_mutex_lock(&tab->render_mutex);

1930 tab->page.code = 20;

1931 tab->page.data_len = len;

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

1933 tab->page.data = data;

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

1935 int i = strnlen(path, PATH_MAX);

1936 int gmi = 0;

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

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

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

1940 gmi = 1;

1941 if (gmi)

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

1943 else

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

1945 tab->page.no_header = 1;

1946 gmi_load(&tab->page);

1947 gmi_addtohistory(tab);

1948 pthread_mutex_unlock(&tab->render_mutex);

1949 return len;

1950 }

1951

1952 int gmi_init() {

1953 if (tls_init()) {

1954 tb_shutdown();

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

1956 return -1;

1957 }

1958

1959 config = tls_config_new();

1960 if (!config) {

1961 tb_shutdown();

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

1963 return -1;

1964 }

1965

1966 config_empty = tls_config_new();

1967 if (!config_empty) {

1968 tb_shutdown();

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

1970 return -1;

1971 }

1972

1973 tls_config_insecure_noverifycert(config);

1974 tls_config_insecure_noverifycert(config_empty);

1975 #ifndef DISABLE_XDG

1976 int xdg = client.xdg;

1977 #endif

1978 #ifdef SANDBOX_SUN

1979 char** bookmarks = client.bookmarks;

1980 #endif

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

1982 #ifdef SANDBOX_SUN

1983 client.bookmarks = bookmarks;

1984 #endif

1985 #ifndef DISABLE_XDG

1986 client.xdg = xdg;

1987 #endif

1988

1989 int fd = getconfigfd();

1990 if (fd < 0) {

1991 tb_shutdown();

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

1993 return -1;

1994 }

1995

1996 #ifndef SANDBOX_SUN

1997 if (gmi_loadbookmarks()) {

1998 gmi_newbookmarks();

1999 }

2000

2001 if (cert_load()) {

2002 tb_shutdown();

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

2004 return -1;

2005 }

2006 #endif

2007

2008 return 0;

2009 }

2010

2011 void gmi_free() {

2012 while (client.tab)

2013 gmi_freetab(client.tab);

2014 gmi_savebookmarks();

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

2016 free(client.bookmarks[i]);

2017 free(client.bookmarks);

2018 cert_free();

2019 }

2020