💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 31131603309e137f2f5ea3654ad70… captured on 2023-11-14 at 08:24:08. 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]!='\n' &&

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

407

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

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

410 c = initial;

411 x += 3;

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

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

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

415 links++;

416 }

417 }

418

419 if (search &&

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

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

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

423 (previous_count - 1)))

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

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

426 hlcolor++;

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

428 tab->search.count == 0)

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

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

431 hlcolor--;

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

433 }

434 highlight += search_len;

435 tab->search.count++;

436 }

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

438 x+4 >= w) {

439 int end = 0;

440 if (x+4 >= w)

441 end = 1;

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

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

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

445 if (i > w - 4) {

446 newline = 0;

447 break;

448 }

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

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

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

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

453 break;

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

455 }

456 if (newline) {

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

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

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

460 color = TB_DEFAULT;

461 start = 1;

462 } else c--;

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

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

465 x = 0;

466 continue;

467 } else if (end) {

468 c++;

469 x++;

470 }

471 }

472 uint32_t ch = 0;

473 int size = tb_utf8_char_to_unicode(&ch,

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

475 if (size > 0) c += size;

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

477

478 /* ignore utf-8 BOM */

479 if (ch == 0xfeff) continue;

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" \

626 "# Vgmi - " VERSION "\n\n" \

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

628 "## Bookmarks\n\n" \

629 "%s\n" \

630 "## Keybindings\n\n" \

631 "* k - Scroll up\n" \

632 "* j - Scroll down\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

649 "## Commands\n\n" \

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

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

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

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

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

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

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

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

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

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

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

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

662

663 void gmi_newbookmarks() {

664 int len;

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

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

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

668 if (!client.bookmarks) goto fail_malloc;

669

670 len = sizeof(geminispace);

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

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

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

674

675 len = sizeof(gemigit);

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

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

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

679

680 client.bookmarks[2] = NULL;

681 return;

682 fail_malloc:

683 fatal();

684 }

685

686 int gmi_loadbookmarks() {

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

688 if (fd < 0)

689 return -1;

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

691 if (!f)

692 return -1;

693 #ifdef SANDBOX_FREEBSD

694 if (makefd_readonly(fd)) {

695 fclose(f);

696 return -1;

697 }

698 #endif

699 fseek(f, 0, SEEK_END);

700 size_t len = ftell(f);

701 fseek(f, 0, SEEK_SET);

702 char* data = malloc(len);

703 if (!data) {

704 memory_failure(client.tab);

705 fclose(f);

706 return -1;

707 }

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

709 fclose(f);

710 return -1;

711 }

712 fclose(f);

713 char* ptr = data;

714 long n = 0;

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

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

717 if (!client.bookmarks) {

718 memory_failure(client.tab);

719 return -1;

720 }

721 client.bookmarks[n] = NULL;

722 n = 0;

723 ptr = data;

724 char* str = data;

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

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

727 *ptr = '\0';

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

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

730 memory_failure(client.tab);

731 break;

732 }

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

734 n++;

735 str = ptr + 1;

736 }

737 free(data);

738 return 0;

739 }

740

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

742 int n = 0;

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

744 n = tb_utf8_char_length(str[i]);

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

746 str[i] = '\0';

747 break;

748 }

749 }

750

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

752 if (page->title_cached) return;

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

754 page->title_cached = 1;

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

756 goto use_url;

757 }

758 int start = -1;

759 int end = -1;

760 int line_start = 1;

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

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

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

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

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

766 break;

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

768 start = j;

769 break;

770 }

771 }

772 }

773 line_start = 0;

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

775 line_start = 1;

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

777 end = i;

778 break;

779 }

780 }

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

782 goto use_url;

783 size_t len = end - start + 1;

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

785 len < sizeof(page->title)?

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

787 sanitize(page->title, len);

788 return;

789 use_url:

790 if (!url) {

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

792 sanitize(page->title, len);

793 return;

794 }

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

796 if (!str) {

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

798 goto sanitize;

799 }

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

801 str++;

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

803 sanitize:

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

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

806 page->title[i] = 0;

807 }

808

809 int gmi_removebookmark(int index) {

810 index--;

811 if (index < 0) return -1;

812 int fail = -1;

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

814 if (i == index) {

815 free(client.bookmarks[i]);

816 fail = 0;

817 }

818 if (!fail)

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

820 }

821 return fail;

822 }

823

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

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

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

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

828 tab->show_error = 1;

829 return;

830 }

831 int title_len = 0;

832 if (!title) {

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

834 title = tab->page.title;

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

836 if (!title_len) {

837 title_len = sizeof("no title");

838 title = "no title";

839 }

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

841 long n = 0;

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

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

844 if (!ptr) {

845 memory_failure(tab);

846 return;

847 }

848 client.bookmarks = ptr;

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

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

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

852 memory_failure(tab);

853 return;

854 }

855 if (title)

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

857 else

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

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

860 gmi_savebookmarks();

861 }

862

863 int gmi_savebookmarks() {

864 #ifdef SANDBOX_SUN

865 int fd = wr_pair[1];

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

867 return -1;

868 #else

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

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

871 if (fd < 0) {

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

873 return -1;

874 }

875 #ifdef SANDBOX_FREEBSD

876 if (makefd_writeonly(fd)) {

877 close(fd);

878 return -1;

879 }

880 #endif

881 #endif

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

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

884 char c = '\n';

885 write(fd, &c, 1);

886 }

887 #ifdef SANDBOX_SUN

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

889 #else

890 close(fd);

891 #endif

892 return 0;

893 }

894

895 char* gmi_getbookmarks(int* len) {

896 char* data = NULL;

897 int n = 0;

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

899 char line[2048];

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

901 client.bookmarks[i]);

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

903 if (!ptr) {

904 *len = n;

905 return data;

906 }

907 data = ptr;

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

909 n += length-1;

910 }

911 *len = n;

912 return data;

913 }

914

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

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

917 int bm = 0;

918 char* data = gmi_getbookmarks(&bm);

919 pthread_mutex_lock(&tab->render_mutex);

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

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

922 memory_failure(tab);

923 pthread_mutex_unlock(&tab->render_mutex);

924 return;

925 }

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

927

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

929 home_page, data ? data : "");

930 free(data);

931

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

933

934 if (!add)

935 gmi_freepage(&tab->page);

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

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

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

939 tab->page.code = 20;

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

941 gmi_load(&tab->page);

942 if (add)

943 gmi_addtohistory(tab);

944 else if (tab->history) {

945 tab->history->page = tab->page;

946 tab->history->cached = 1;

947 }

948 tab->scroll = -1;

949 tab->request.data = NULL;

950 pthread_mutex_unlock(&tab->render_mutex);

951 }

952

953 struct gmi_tab* gmi_newtab() {

954 return gmi_newtab_url("about:home");

955 }

956

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

958

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

960 struct gmi_tab tmp;

961 int proto = parse_url(url,

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

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

964 &tmp.request.port);

965 switch (proto) {

966 case PROTO_GEMINI:

967 break;

968 case PROTO_HTTP:

969 case PROTO_HTTPS:

970 case PROTO_GOPHER:

971 xdg_request(url);

972 /* fallthrough */

973 default:

974 return client.tab;

975 }

976 }

977

978 client.tabs_count++;

979 if (client.tab) {

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

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

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

983 memory_failure(client.tab);

984 return client.tab;

985 }

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

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

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

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

990 if (next)

991 next->prev = client.tab;

992 } else {

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

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

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

996 }

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

998

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

1000 return client.tab;

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

1002 (void *(*)(void *))gmi_request_thread,

1003 (void*)client.tab);

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

1005 if (url)

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

1007

1008 client.tab->scroll = -1;

1009 return client.tab;

1010 }

1011

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

1013 if (!tab) return -1;

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

1015 gmi_gohome(tab, add);

1016 return tab->page.data_len;

1017 }

1018 tab->show_error = 0;

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

1020 tab->selected = 0;

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

1022 int proto = parse_url(url,

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

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

1025 &tab->request.port);

1026

1027 if (proto == -2) {

1028 tab->show_error = 1;

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

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

1031 return -1;

1032 }

1033 if (proto == PROTO_FILE) {

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

1035 }

1036 if (proto != PROTO_GEMINI) {

1037 #ifndef DISABLE_XDG

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

1039 #endif

1040 tab->show_error = 1;

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

1042 "Unable to open the link");

1043 return -1;

1044 }

1045 if (tab->request.tls)

1046 tls_reset(tab->request.tls);

1047 else

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

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

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

1051 "Failed to initialize TLS");

1052 tab->show_error = 1;

1053 return -1;

1054 }

1055

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

1057 if (cert >= 0 &&

1058 tls_config_set_keypair_mem(config,

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

1060 client.certs[cert].crt_len,

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

1062 client.certs[cert].key_len))

1063 {

1064 tab->show_error = 1;

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

1066 tls_config_error(config));

1067 return -1;

1068 }

1069

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

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

1072 "Failed to configure TLS");

1073 tab->show_error = 1;

1074 return -1;

1075 }

1076 tab->request.state = STATE_REQUESTED;

1077 return 0;

1078 }

1079

1080 #ifdef __linux

1081 void dns_async(union sigval sv) {

1082 struct gmi_tab* tab = sv.sival_ptr;

1083 tab->request.resolved = 1;

1084 }

1085 #endif

1086

1087 int gmi_request_dns(struct gmi_tab* tab) {

1088 char host[1024];

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

1090 host, sizeof(host))) {

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

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

1093 tab->show_error = 1;

1094 return -1;

1095 }

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

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

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

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

1100 memory_failure(tab);

1101 return -1;

1102 }

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

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

1105 tab->request.resolved = 0;

1106 struct sigevent sevp;

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

1108 sevp.sigev_notify = SIGEV_THREAD;

1109 sevp.sigev_notify_function = dns_async;

1110 sevp.sigev_value.sival_ptr = tab;

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

1112 if (ret) {

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

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

1115 tab->show_error = 1;

1116 return -1;

1117 }

1118

1119 long start = time(NULL);

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

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

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

1123 nanosleep(&timeout, NULL);

1124 }

1125

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

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

1128 gai_cancel(tab->request.gaicb_ptr);

1129 free(tab->request.gaicb_ptr);

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

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

1132 tab->show_error = 1;

1133 return -1;

1134 }

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

1136 #else

1137 struct addrinfo hints, *result;

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

1139 hints.ai_family = AF_INET;

1140 hints.ai_socktype = SOCK_STREAM;

1141 hints.ai_flags |= AI_CANONNAME;

1142 errno = 0;

1143

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

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

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

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

1148 tab->show_error = 1;

1149 return -1;

1150 }

1151 #endif

1152

1153 struct sockaddr_in addr4;

1154 struct sockaddr_in6 addr6;

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

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

1157

1158 int error = 0;

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

1160 addr4.sin_addr =

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

1162 addr4.sin_family = AF_INET;

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

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

1165 tab->request.addr =

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

1167 }

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

1169 addr6.sin6_addr =

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

1171 addr6.sin6_family = AF_INET6;

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

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

1174 tab->request.addr =

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

1176 } else {

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

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

1179 tab->request.host);

1180 error = 1;

1181 }

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

1183 freeaddrinfo(result);

1184 #ifdef __linux__

1185 free(tab->request.gaicb_ptr);

1186 #endif

1187 if (error) {

1188 tab->request.state = STATE_CANCEL;

1189 return -1;

1190 }

1191

1192 tab->request.state = STATE_DNS;

1193 return 0;

1194 }

1195

1196 int gmi_request_connect(struct gmi_tab* tab) {

1197

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

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

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

1201 "Failed to create socket");

1202 return -1;

1203 }

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

1205 if (flags == -1 ||

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

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

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

1209 return -1;

1210 }

1211

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

1213 sizeof(struct sockaddr_in):

1214 sizeof(struct sockaddr_in6);

1215

1216 int connected = 0;

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

1218 tab->request.addr, addr_size);

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

1220 errno != EINPROGRESS && errno != EALREADY &&

1221 errno != 0):failed;

1222 while (!failed) {

1223 struct pollfd fds[2];

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

1225 fds[0].events = POLLIN;

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

1227 fds[1].events = POLLOUT;

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

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

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

1231 int value;

1232 socklen_t len = sizeof(value);

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

1234 SO_ERROR, &value, &len);

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

1236 break;

1237 }

1238

1239 if (!connected) {

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

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

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

1243 return -1;

1244 }

1245

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

1247 tab->request.host)) {

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

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

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

1251 return -1;

1252 }

1253 return 0;

1254 }

1255

1256 int gmi_request_handshake(struct gmi_tab* tab) {

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

1258 tab->request.socket,

1259 tab->request.host)) {

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

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

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

1263 return -1;

1264 }

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

1266 int ret = 0;

1267 time_t start = time(0);

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

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

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

1271 (ret < 0 &&

1272 ret != TLS_WANT_POLLIN &&

1273 ret != TLS_WANT_POLLOUT))

1274 break;

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

1276 nanosleep(&timeout, NULL);

1277 }

1278 if (ret) {

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

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

1281 tls_error(tab->request.tls),

1282 tab->request.host);

1283 return -1;

1284 }

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

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

1287 tls_peer_cert_hash(tab->request.tls),

1288 tls_peer_cert_notbefore(tab->request.tls),

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

1290 switch (ret) {

1291 case 0: // success

1292 break;

1293 case -1: // invalid certificate

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

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

1296 "(The certificate changed)",

1297 tab->request.host);

1298 return -1;

1299 case -3: // sandbox error

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

1301 "Sandbox error when verifying server certificate " \

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

1303 return -1;

1304 case -5: // expired

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

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

1307 "Expired certificate, " \

1308 "the certificate for %s has expired",

1309 tab->request.host);

1310 return -1;

1311 case -6: // certificate changed

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

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

1314 " to forget the old certificate.",

1315 tab->request.host);

1316 return -1;

1317 default: // failed to write certificate

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

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

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

1321 return -1;

1322 }

1323

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

1325 char buf[MAX_URL];

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

1327 char toascii[1024];

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

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

1330 toascii, sizeof(toascii))) {

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

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

1333 return -1;

1334 }

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

1336 char url[1024];

1337 if (len < MAX_URL)

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

1339 if (len >= MAX_URL ||

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

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

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

1343 return -1;

1344 }

1345

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

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

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

1349 return -1;

1350 }

1351

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

1353 tab->request.state = STATE_CONNECTED;

1354 return 0;

1355 }

1356

1357 int gmi_request_header(struct gmi_tab* tab) {

1358 time_t now = time(0);

1359 char buf[1024];

1360 int recv = 0;

1361 while (1) {

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

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

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

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

1366 tab->request.host);

1367 return -1;

1368 }

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

1370 sizeof(buf) - recv - 1);

1371 if (n == TLS_WANT_POLLIN) {

1372 nanosleep(&timeout, NULL);

1373 continue;

1374 }

1375 if (n < 1) {

1376 recv = -1;

1377 break;

1378 }

1379 recv += n;

1380 buf[recv] = '\0';

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

1382 }

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

1384

1385 if (recv <= 0) {

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

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

1388 recv, tab->request.host);

1389 return -1;

1390 }

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

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

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

1394 return -1;

1395 }

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

1397 if (ptr && ptr + 1 >= strnstr(buf, "\r\n", sizeof(buf))) ptr = NULL;

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

1399

1400 int previous_code = tab->page.code;

1401 char c = buf[2];

1402 buf[2] = '\0';

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

1404 buf[2] = c;

1405

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

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

1408 tab->redirects = 0;

1409 }

1410 switch (tab->page.code) {

1411 case 10:

1412 case 11:

1413 if (ptr) {

1414 ptr++;

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

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

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

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

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

1420 } else client.input.label[0] = '\0';

1421 tab->request.state = STATE_RECV_HEADER;

1422 tab->request.recv = recv;

1423 return 2; // input

1424 case 20:

1425 {

1426 if (!ptr) {

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

1428 break;

1429 }

1430 #ifdef TERMINAL_IMG_VIEWER

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

1432 #endif

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

1434 if (!meta) {

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

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

1437 return -1;

1438 }

1439 *meta = '\0';

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

1441 *meta = '\r';

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

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

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

1445 #ifdef TERMINAL_IMG_VIEWER

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

1447 #endif

1448 )) {

1449 if (tab->history)

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

1451 else

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

1453 tab->request.ask = 2;

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

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

1456 tab->request.meta);

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

1458 tb_interupt();

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

1460 nanosleep(&timeout, NULL);

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

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

1463 tab->request.recv = -1;

1464 return 1; // download

1465 }

1466 }

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

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

1469 }

1470 break;

1471 case 30:

1472 case 31:

1473 tab->redirects++;

1474 if (tab->redirects > MAX_REDIRECT) {

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

1476 "Too many redirects");

1477 return -2;

1478 }

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

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

1481 break;

1482 case 40:

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

1484 "Temporary failure");

1485 return -2;

1486 case 41:

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

1488 "Server unavailable");

1489 return -2;

1490 case 42:

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

1492 "CGI error");

1493 return -2;

1494 case 43:

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

1496 "Proxy error");

1497 return -2;

1498 case 44:

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

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

1501 return -2;

1502 case 50:

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

1504 "Permanent failure");

1505 return -2;

1506 case 51:

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

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

1509 return -2;

1510 case 52:

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

1512 "Resource gone");

1513 return -2;

1514 case 53:

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

1516 "Proxy request refused");

1517 return -2;

1518 case 59:

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

1520 "Bad request");

1521 return -2;

1522 case 60:

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

1524 "Client certificate required");

1525 return -2;

1526 case 61:

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

1528 "Client not authorised");

1529 return -2;

1530 case 62:

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

1532 "Client certificate not valid");

1533 return -2;

1534 default:

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

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

1537 tab->page.code = previous_code;

1538 return -2;

1539 }

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

1541 tab->request.state = STATE_RECV_HEADER;

1542 tab->request.recv = recv;

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

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

1545 memory_failure(tab);

1546 return -1;

1547 }

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

1549 return 0;

1550 }

1551

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

1553 time_t now = time(0);

1554 char buf[1024];

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

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

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

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

1559 "Server %s stopped responding",

1560 tab->request.host);

1561 return -1;

1562 }

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

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

1565 if (bytes == 0) break;

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

1567 nanosleep(&timeout, NULL);

1568 continue;

1569 }

1570 if (bytes < 1) {

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

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

1573 tab->request.host,

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

1575 return -1;

1576 }

1577 if (fd > 0) {

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

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

1580 "Failed to download file: %s",

1581 strerror(errno));

1582 return -1;

1583 }

1584 now = time(0);

1585 continue;

1586 }

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

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

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

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

1591 if (!ptr) {

1592 free(tab->request.data);

1593 tab->request.data = NULL;

1594 memory_failure(tab);

1595 return -1;

1596 }

1597 tab->request.data = ptr;

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

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

1600 tab->request.recv += bytes;

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

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

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

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

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

1606 return -1;

1607 }

1608 now = time(0);

1609 }

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

1611 tab->request.state = STATE_RECV_BODY;

1612 return 0;

1613 }

1614

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

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

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

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

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

1620 #ifndef SANDBOX_SUN

1621 int fd = getdownloadfd();

1622 if (fd < 0) {

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

1624 "Unable to open download folder");

1625 tab->show_error = 1;

1626 return -1;

1627 }

1628 #endif

1629 if (ptr)

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

1631 else {

1632 #ifdef __OpenBSD__

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

1634 #else

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

1636 #endif

1637

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

1639 }

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

1641 char c = path[i];

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

1643 !(c == '.' ||

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

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

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

1647 ))

1648 path[i] = '_';

1649 }

1650 #ifdef SANDBOX_SUN

1651 if (sandbox_download(tab, path))

1652 return -1;

1653 int dfd = wr_pair[1];

1654 sandbox_dl_length(-1);

1655 #else

1656 int dfd = openat(fd, path,

1657 O_CREAT|O_EXCL|O_WRONLY, 0600);

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

1659 char buf[1024];

1660 #ifdef __OpenBSD__

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

1662 #else

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

1664 #endif

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

1666 strlcpy(path, buf, path_len);

1667 dfd = openat(fd, path,

1668 O_CREAT|O_EXCL|O_WRONLY, 0600);

1669 if (dfd < 0) {

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

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

1672 tab->show_error = 1;

1673 return -1;

1674 }

1675 }

1676 #ifdef SANDBOX_FREEBSD

1677 if (makefd_writeonly(dfd)) {

1678 client.shutdown = 1;

1679 return -1;

1680 }

1681 #endif

1682 #endif

1683 return dfd;

1684 }

1685

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

1687

1688 #ifdef SANDBOX_SUN

1689 if (wr_pair[1] != fd)

1690 return -1;

1691 #else

1692 close(fd);

1693 #endif

1694 #ifndef DISABLE_XDG

1695 int fail = 0;

1696 if (client.xdg) {

1697 tab->request.ask = 2;

1698 snprintf(tab->request.info,

1699 sizeof(tab->request.info),

1700 "# %s download",

1701 tab->request.meta);

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

1703 tb_interupt();

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

1705 nanosleep(&timeout, NULL);

1706 if (tab->request.ask)

1707 fail = xdg_open(path);

1708 }

1709 if (fail) {

1710 tab->show_error = 1;

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

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

1713 } else

1714 #endif

1715 {

1716 tab->show_info = 1;

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

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

1719 }

1720 return fail;

1721 }

1722

1723 void* gmi_request_thread(struct gmi_tab* tab) {

1724 unsigned int signal = 0;

1725 while (!client.shutdown) {

1726 tab->selected = 0;

1727 tab->request.state = STATE_DONE;

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

1729 tb_interupt();

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

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

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

1733 tab->request.state = STATE_STARTED;

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

1735 tab->thread.add);

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

1737 ret || gmi_request_dns(tab)) {

1738 if (tab->request.tls) {

1739 tls_close(tab->request.tls);

1740 tls_free(tab->request.tls);

1741 tab->request.tls = NULL;

1742 }

1743 if (ret == -1)

1744 tab->show_error = 1;

1745 tab->request.recv = ret;

1746 continue;

1747 }

1748 if (gmi_request_connect(tab) ||

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

1750 close(tab->request.socket);

1751 if (tab->request.tls) {

1752 tls_close(tab->request.tls);

1753 tls_free(tab->request.tls);

1754 tab->request.tls = NULL;

1755 }

1756 tab->show_error = 1;

1757 tab->request.recv = -1;

1758 continue;

1759 }

1760 ret = -1;

1761 if (!gmi_request_handshake(tab) &&

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

1763 ret = gmi_request_header(tab);

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

1765 char out[1024];

1766 int fd = -1;

1767 if (tab->request.download)

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

1769 if (gmi_request_body(tab, fd)) {

1770 close(fd);

1771 tab->show_error = 1;

1772 free(tab->request.data);

1773 continue;

1774 }

1775 if (fd > 0)

1776 gmi_postdownload(tab, fd, out);

1777 }

1778 }

1779

1780 if (ret == -2) {

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

1782 if (cr) {

1783 *cr = '\0';

1784 char buf[256];

1785 snprintf(buf, sizeof(buf),

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

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

1788 STRLCPY(tab->error, buf);

1789 *cr = '\r';

1790 }

1791 }

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

1793 free(tab->request.data);

1794 tab->request.data = NULL;

1795 if (tab->history)

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

1797 else

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

1799 tab->show_error = 1;

1800 tab->request.recv = -1;

1801 tab->page.code = 20;

1802 }

1803

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

1805 close(tab->request.socket);

1806 if (tab->request.tls) {

1807 tls_close(tab->request.tls);

1808 tls_free(tab->request.tls);

1809 tab->request.tls = NULL;

1810 }

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

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

1813 free(tab->request.data);

1814 tab->request.data = NULL;

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

1816 continue;

1817 }

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

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

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

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

1822 if (!ptr) {

1823 free(tab->request.data);

1824 continue;

1825 }

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

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

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

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

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

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

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

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

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

1835 free(tab->request.data);

1836 continue;

1837 }

1838 tab->page.data_len = r;

1839 free(tab->request.data);

1840 tab->request.data = NULL;

1841 gmi_load(&tab->page);

1842 tab->history->page = tab->page;

1843 tab->history->cached = 1;

1844 tab->request.recv = r;

1845 continue;

1846 }

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

1848 tab->request.recv > 0 &&

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

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

1851 struct gmi_link* link_ptr = tab->history?

1852 tab->history->prev:NULL;

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

1854 if (!link_ptr) break;

1855 link_ptr = link_ptr->prev;

1856 }

1857 while (link_ptr) {

1858 if (link_ptr->cached) {

1859 gmi_freepage(&link_ptr->page);

1860 link_ptr->cached = 0;

1861 }

1862 link_ptr = link_ptr->prev;

1863 }

1864 pthread_mutex_lock(&tab->render_mutex);

1865 if (!tab->thread.add)

1866 gmi_freepage(&tab->page);

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

1868 tab->page.code = 20;

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

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

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

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

1873 gmi_load(&tab->page);

1874 if (tab->thread.add)

1875 gmi_addtohistory(tab);

1876 else if (tab->history) {

1877 tab->history->page = tab->page;

1878 tab->history->cached = 1;

1879 }

1880 tab->scroll = -1;

1881 pthread_mutex_unlock(&tab->render_mutex);

1882 }

1883 }

1884 return NULL;

1885 }

1886

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

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

1889 tab->request.state = STATE_CANCEL;

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

1891 nanosleep(&timeout, NULL);

1892 tab->thread.add = add;

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

1894 int signal = 0;

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

1896 sizeof(signal))

1897 return -1;

1898 return 0;

1899 }

1900

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

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

1903 if (!f) {

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

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

1906 tab->show_error = 1;

1907 return -1;

1908 }

1909 #ifdef SANDBOX_FREEBSD

1910 if (make_readonly(f)) {

1911 fclose(f);

1912 client.shutdown = 1;

1913 return -1;

1914 }

1915 #endif

1916 fseek(f, 0, SEEK_END);

1917 size_t len = ftell(f);

1918 fseek(f, 0, SEEK_SET);

1919 char* data = malloc(len);

1920 if (!data) {

1921 fclose(f);

1922 memory_failure(tab);

1923 return -1;

1924 }

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

1926 fclose(f);

1927 free(data);

1928 return -1;

1929 }

1930 fclose(f);

1931 pthread_mutex_lock(&tab->render_mutex);

1932 tab->page.code = 20;

1933 tab->page.data_len = len;

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

1935 tab->page.data = data;

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

1937 int i = strnlen(path, PATH_MAX);

1938 int gmi = 0;

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

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

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

1942 gmi = 1;

1943 if (gmi)

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

1945 else

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

1947 tab->page.no_header = 1;

1948 gmi_load(&tab->page);

1949 gmi_addtohistory(tab);

1950 pthread_mutex_unlock(&tab->render_mutex);

1951 return len;

1952 }

1953

1954 int gmi_init() {

1955 if (tls_init()) {

1956 tb_shutdown();

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

1958 return -1;

1959 }

1960

1961 config = tls_config_new();

1962 if (!config) {

1963 tb_shutdown();

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

1965 return -1;

1966 }

1967

1968 config_empty = tls_config_new();

1969 if (!config_empty) {

1970 tb_shutdown();

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

1972 return -1;

1973 }

1974

1975 tls_config_insecure_noverifycert(config);

1976 tls_config_insecure_noverifycert(config_empty);

1977 #ifndef DISABLE_XDG

1978 int xdg = client.xdg;

1979 #endif

1980 #ifdef SANDBOX_SUN

1981 char** bookmarks = client.bookmarks;

1982 #endif

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

1984 #ifdef SANDBOX_SUN

1985 client.bookmarks = bookmarks;

1986 #endif

1987 #ifndef DISABLE_XDG

1988 client.xdg = xdg;

1989 #endif

1990

1991 int fd = getconfigfd();

1992 if (fd < 0) {

1993 tb_shutdown();

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

1995 return -1;

1996 }

1997

1998 #ifndef SANDBOX_SUN

1999 if (gmi_loadbookmarks()) {

2000 gmi_newbookmarks();

2001 }

2002

2003 if (cert_load()) {

2004 tb_shutdown();

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

2006 return -1;

2007 }

2008 #endif

2009

2010 return 0;

2011 }

2012

2013 void gmi_free() {

2014 while (client.tab)

2015 gmi_freetab(client.tab);

2016 gmi_savebookmarks();

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

2018 free(client.bookmarks[i]);

2019 free(client.bookmarks);

2020 cert_free();

2021 }

2022