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

1 #ifdef __linux__

2 #define _GNU_SOURCE

3 #endif

4 #include <pthread.h>

5 #include <netinet/in.h>

6 #include <stdint.h>

7 #include <stdlib.h>

8 #include <stdio.h>

9 #include <strings.h>

10 #include <unistd.h>

11 #include <sys/socket.h>

12 #include <string.h>

13 #include <netdb.h>

14 #include <tls.h>

15 #include <poll.h>

16 #include <fcntl.h>

17 #include <errno.h>

18 #include <termbox.h>

19 #include <ctype.h>

20 #include <time.h>

21 #include "gemini.h"

22 #include "cert.h"

23 #include "wcwidth.h"

24 #include "display.h"

25 #include "input.h"

26 #include "sandbox.h"

27 #include "str.h"

28

29 #define MAX_CACHE 10

30 #define TIMEOUT 8

31 struct timespec timeout = {0, 10000000};

32

33 struct tls_config* config;

34 struct tls_config* config_empty;

35 struct gmi_client client;

36

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

38 void* gmi_request_thread(void* tab);

39

40 void fatal() {

41 tb_shutdown();

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

43 exit(0);

44 }

45

46 int fatalI() {

47 fatal();

48 return -1;

49 }

50

51 void* fatalP() {

52 fatal();

53 return NULL;

54 }

55

56 #ifndef DISABLE_XDG

57 int xdg_open(char* str) {

58 if (client.xdg) {

59 char buf[4096];

60 snprintf(buf, sizeof(buf), "xdg-open %!s(MISSING) > /dev/null 2>&1", str);

61 if (fork() == 0) {

62 setsid();

63 char* argv[] = {"/bin/sh", "-c", buf, NULL};

64 execvp(argv[0], argv);

65 exit(0);

66 }

67 }

68 return 0;

69 }

70

71 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux__)

72 int xdg_request(char*);

73 #define xdg_open(x) xdg_request(x)

74 #endif

75

76 #endif

77

78 int gmi_parseuri(const char* url, int len, char* buf, int llen) {

79 int j = 0;

80 int inquery = 0;

81 for (int i=0; j < llen && i < len && url[i]; i++) {

82 if (url[i] == '/') inquery = 0;

83 if ((url[i] >= 'a' && url[i] <= 'z') ||

84 (url[i] >= 'A' && url[i] <= 'Z') ||

85 (url[i] >= '0' && url[i] <= '9') ||

86 (url[i] == '?' && !inquery) ||

87 url[i] == '.' || url[i] == '/' ||

88 url[i] == ':' || url[i] == '-' ||

89 url[i] == '_') {

90 if (url[i] == '?') inquery = 1;

91 buf[j] = url[i];

92 j++;

93 } else {

94 char format[8];

95 snprintf(format, sizeof(format), "%%!x(MISSING)", (unsigned char)url[i]);

96 buf[j] = '\0';

97 j = strlcat(buf, format, llen);

98 }

99 }

100 if (j >= llen) j = llen - 1;

101 buf[j] = '\0';

102 return j;

103 }

104

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

106 id--;

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

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

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

110 "Invalid link number, %!d(MISSING)/%!d(MISSING)", id, page->links_count);

111 tab->show_error = 1;

112 client.input.mode = 0;

113 return -1;

114 }

115 gmi_cleanforward(tab);

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

117 return ret;

118 }

119

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

121 id--;

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

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

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

125 "Invalid link number, %!d(MISSING)/%!d(MISSING)", id, page->links_count);

126 tab->show_error = 1;

127 client.input.mode = 0;

128 return -1;

129 }

130 int old_tab = client.tab;

131 struct gmi_tab* new_tab = gmi_newtab_url(NULL);

132 client.tab = client.tabs_count - 1;

133 page = &client.tabs[old_tab].page;

134 int ret = gmi_nextlink(new_tab, client.tabs[old_tab].url, page->links[id]);

135 return ret;

136 }

137

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

139 int url_len = strnlen(url, MAX_URL);

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

141 gmi_gohome(tab, 1);

142 return 0;

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

144 int ret = gmi_request(tab, &link[2], 1);

145 if (ret < 1) return ret;

146 return ret;

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

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

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

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

151 }

152 char urlbuf[MAX_URL];

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

154 if (l >= sizeof(urlbuf))

155 goto nextlink_overflow;

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

157 goto nextlink_overflow;

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

159 return ret;

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

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

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

163 #ifndef DISABLE_XDG

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

165 #endif

166 tab->show_error = 1;

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

168 "Unable to open the link");

169 return -1;

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

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

172 return ret;

173 } else {

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

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

176 char urlbuf[MAX_URL];

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

178 if (l >= sizeof(urlbuf))

179 goto nextlink_overflow;

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

181 size_t l2 = strlcpy(urlbuf + l, "/", sizeof(urlbuf) - l);

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

183 goto nextlink_overflow;

184 l += l2;

185 }

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

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

188 goto nextlink_overflow;

189 l += l2;

190 if (urlbuf[l-1] == '/') urlbuf[l-1] = '\0';

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

192 return ret;

193 }

194 nextlink_overflow:

195 tab->show_error = 1;

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

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

198 return -1;

199 }

200

201 void gmi_load(struct gmi_page* page) {

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

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

204 free(page->links);

205 page->links = NULL;

206 page->links_count = 0;

207 page->lines = 0;

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

209 page->links = NULL;

210 page->links_count = 0;

211 page->lines = 0;

212 return;

213 }

214 int x = 0;

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

216 if (x == 0 && page->data[c] == '=' && page->data[c+1] == '>') {

217 c += 2;

218 int nospace = c;

219 for (; page->data[c]==' ' || page->data[c]=='\t'; c++) {

220 if (page->data[c] == '\n' || page->data[c] == '\0') {

221 c = nospace;

222 break;

223 }

224 }

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

226 for (; page->data[c]!=' ' && page->data[c]!='\t'; c++) {

227 if (page->data[c] == '\n' || page->data[c] == '\0') {

228 break;

229 }

230 }

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

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

233 if (page->links)

234 page->links = realloc(page->links, sizeof(char*)

235 * (page->links_count+1));

236 else

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

238 if (!page->links) {

239 fatal();

240 return;

241 }

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

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

244 page->data[c] = save;

245 continue;

246 }

247 int len = strnlen(url, MAX_URL);

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

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

250 fatal();

251 return;

252 }

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

254 page->links_count++;

255 page->data[c] = save;

256 }

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

258 page->lines++;

259 x = 0;

260 continue;

261 }

262 x++;

263 }

264 }

265

266 int gmi_render(struct gmi_tab* tab) {

267 pthread_mutex_lock(&tab->render_mutex);

268 #include "img.h"

269 #ifdef TERMINAL_IMG_VIEWER

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

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

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

273 if (!ptr) {

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

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

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

277 pthread_mutex_unlock(&tab->render_mutex);

278 return 1;

279 }

280 ptr++;

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

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

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

284 }

285 tab->page.img.data =

286 stbi_load_from_memory((unsigned char*)ptr,

287 tab->page.data_len-(int)(ptr-tab->page.data),

288 &tab->page.img.w, &tab->page.img.h,

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

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

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

292 "Failed to decode image: %!s(MISSING);", tab->page.meta);

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

294 pthread_mutex_unlock(&tab->render_mutex);

295 return 1;

296 }

297

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

299 }

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

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

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

303 client.tabs_count>1);

304 pthread_mutex_unlock(&tab->render_mutex);

305 return 1;

306 }

307 }

308 #endif

309

310 int text = 0;

311 if (strncmp(tab->page.meta, "text/gemini", 11)) {

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

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

314 "Unable to render format : %!s(MISSING)", tab->page.meta);

315 pthread_mutex_unlock(&tab->render_mutex);

316 return 1;

317 }

318 text = 1;

319 }

320 int line = 0;

321 int x = 0;

322 int links = 0;

323 uintattr_t color = TB_DEFAULT;

324 int start = 1;

325 int ignore = 0;

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

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

328 &client.input.field[1]:

329 tab->search.entry;

330 int search_len = search?strnlen(search, sizeof(client.input.field) - 1):0;

331 if (!search_len) search = NULL;

332 int highlight = 0;

333 int hlcolor = (client.c256?58:TB_YELLOW);

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

335 tab->search.cursor = 0;

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

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

338 int previous_count = tab->search.count;

339 tab->search.count = 0;

340 char* ptr = strstr(tab->page.data, "\r\n");

341 line++;

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

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

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

345 line++;

346 continue;

347 }

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

349 x+=4;

350 continue;

351 }

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

353 if (!text && start && tab->page.data[c] == '`' &&

354 tab->page.data[c+1] == '`' && tab->page.data[c+2] == '`')

355 ignore = !ignore;

356

357 if (!ignore && !text) {

358 for (int i=0; start && tab->page.data[c+i] == '#' && i<3; i++) {

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

360 color = TB_RED + i;

361 break;

362 }

363 }

364 if (start && tab->page.data[c] == '*' && tab->page.data[c+1] == ' ') {

365 color = TB_ITALIC|TB_CYAN;

366 }

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

368 color = TB_ITALIC|TB_MAGENTA;

369 }

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

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

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

373 char buf[32];

374 snprintf(buf, sizeof(buf), "[%!d(MISSING)]", links+1);

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

376 links+1 == tab->selected?TB_RED:TB_BLUE,

377 TB_DEFAULT, buf);

378 x += strnlen(buf, sizeof(buf));

379 }

380 c += 2;

381

382 while (

383 (tab->page.data[c]==' ' || tab->page.data[c]=='\t') &&

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

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

386

387 int initial = c;

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

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

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

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

392

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

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

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

396

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

398 c = initial;

399 x+=3;

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

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

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

403 links++;

404 }

405 }

406

407 if (search &&

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

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

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

411 (previous_count - 1)))

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

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

414 hlcolor++;

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

416 tab->search.count == 0)

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

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

419 hlcolor--;

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

421 }

422 highlight += search_len;

423 tab->search.count++;

424 }

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

426 x+4 >= tb_width()) {

427 int end = 0;

428 if (x+4>=tb_width()) {

429 end = 1;

430 }

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

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

433 if (x+i == tb_width() - 1) break;

434 if (i > tb_width() - 4) {

435 newline = 0;

436 break;

437 }

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

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

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

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

442 break;

443 if (tb_width()-4<=x+i) newline = 1;

444 }

445 if (newline) {

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

447 line++;

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

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

450 color = TB_DEFAULT;

451 start = 1;

452 }

453 x = 0;

454 continue;

455 } else if (end) {

456 c++;

457 x++;

458 }

459 }

460 uint32_t ch = 0;

461 int size = tb_utf8_char_to_unicode(&ch, &tab->page.data[c])-1;

462 if (size > 0)

463 c += tb_utf8_char_to_unicode(&ch, &tab->page.data[c])-1;

464

465 int wc = mk_wcwidth(ch);

466 if (wc < 0) wc = 0;

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

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

469 if (wc == 1)

470 tb_set_cell(x+2, line-1-tab->scroll, ch, color,

471 highlight?hlcolor:TB_DEFAULT);

472 else

473 tb_set_cell_ex(x+2, line-1-tab->scroll, &ch, wc, color,

474 highlight?hlcolor:TB_DEFAULT);

475 }

476 if (highlight > 0)

477 highlight--;

478

479 x += wc;

480 start = 0;

481 }

482 line++;

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

484 if (h > line) {

485 pthread_mutex_unlock(&tab->render_mutex);

486 return line;

487 }

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

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

490 if (size < 1) size = 1;

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

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

493 + (client.tabs_count>1);

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

495 if (pos < 0) pos = 0;

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

497 int w = tb_width();

498 for (int y = (client.tabs_count>1); y < h+(client.tabs_count>1); y++)

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

500 tb_set_cell(w-1, y, ' ', TB_DEFAULT, client.c256?246:TB_CYAN);

501 else

502 tb_set_cell(w-1, y, ' ', TB_DEFAULT, client.c256?233:TB_BLACK);

503 pthread_mutex_unlock(&tab->render_mutex);

504 return line;

505 }

506

507 void gmi_addtohistory(struct gmi_tab* tab) {

508 if (!(!tab->history || (tab->history && !tab->history->next))) return;

509 gmi_cleanforward(tab);

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

511 if (!link) {

512 fatal();

513 return;

514 }

515 link->next = NULL;

516 link->prev = tab->history;

517 if (link->prev)

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

519 link->page = tab->page;

520 link->cached = 1;

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

522 tab->history = link;

523 if (link->prev)

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

525 }

526

527 void gmi_cleanforward(struct gmi_tab* tab) {

528 if (!tab->history)

529 return;

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

531 while (link) {

532 struct gmi_link* ptr = link->next;

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

534 if (link->cached) {

535 gmi_freepage(&link->page);

536 link->cached = 0;

537 }

538 free(link);

539 link = ptr;

540 }

541 tab->history->next = NULL;

542 }

543

544 void gmi_freepage(struct gmi_page* page) {

545 if (!page) return;

546 free(page->data);

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

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

549 free(page->links);

550 #ifdef TERMINAL_IMG_VIEWER

551 stbi_image_free(page->img.data);

552 #endif

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

554 }

555

556 void gmi_freetab(struct gmi_tab* tab) {

557 if (!tab) return;

558 tab->request.state = STATE_CANCEL;

559 int signal = 0xFFFFFFFF;

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

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

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

563 if (tab->history) {

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

565 while (link) {

566 struct gmi_link* ptr = link->next;

567 if (link->cached) {

568 gmi_freepage(&link->page);

569 link->cached = 0;

570 }

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

572 free(link);

573 link = ptr;

574 }

575 link = tab->history;

576 while (link) {

577 struct gmi_link* ptr = link->prev;

578 if (link->cached) {

579 gmi_freepage(&link->page);

580 link->cached = 0;

581 }

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

583 free(link);

584 link = ptr;

585 }

586 }

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

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

589 pthread_mutex_destroy(&tab->render_mutex);

590 bzero(tab, sizeof(struct gmi_tab));

591 }

592

593 char home_page[] =

594 "20 text/gemini\r\n# Vgmi\n\n" \

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

596 "## Bookmarks\n\n" \

597 "%!s(MISSING)\n" \

598 "## Keybindings\n\n" \

599 "* k - Scroll up\n" \

600 "* j - Scroll down\n" \

601 "* h - Switch to the previous tab\n" \

602 "* l - Switch to the next tab\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

617 "## Commands\n\n" \

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

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

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

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

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

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

624 "* :[number] - Follow the link\n" \

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

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

627 "* :download [name] - Download the current page, the name is optional"

628 ;

629

630 void gmi_newbookmarks() {

631 int len;

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

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

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

635 if (!client.bookmarks) goto fail_malloc;

636

637 len = sizeof(geminispace);

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

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

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

641

642 len = sizeof(gemigit);

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

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

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

646

647 client.bookmarks[2] = NULL;

648 return;

649 fail_malloc:

650 fatal();

651 }

652

653 int gmi_loadbookmarks() {

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

655 if (fd < 0)

656 return -1;

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

658 if (!f)

659 return -1;

660 #ifdef __FreeBSD__

661 if (makefd_readonly(fd)) {

662 fclose(f);

663 return -1;

664 }

665 #endif

666 fseek(f, 0, SEEK_END);

667 size_t len = ftell(f);

668 fseek(f, 0, SEEK_SET);

669 char* data = malloc(len);

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

671 fclose(f);

672 return -1;

673 }

674 fclose(f);

675 char* ptr = data;

676 long n = 0;

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

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

679 client.bookmarks[n] = NULL;

680 n = 0;

681 ptr = data;

682 char* str = data;

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

684 if (*ptr == '\n') {

685 *ptr = '\0';

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

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

688 n++;

689 str = ptr+1;

690 }

691 }

692 free(data);

693 return 0;

694 }

695

696 char* gmi_gettitle(struct gmi_page page, int* len) {

697 int start = -1;

698 int end = -1;

699 for (int i=0; i < page.data_len; i++) {

700 if (start == -1 && page.data[i] == '#') {

701 for (int j = i+1; j < page.data_len; j++) {

702 if (j && page.data[j-1] == '#' && page.data[j] == '#')

703 break;

704 if (page.data[j] != ' ') {

705 start = j;

706 break;

707 }

708 }

709 }

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

711 end = i;

712 break;

713 }

714 }

715 if (start == -1 || end == -1) return NULL;

716 char* title = malloc(end - start + 1);

717 strlcpy(title, &page.data[start], end - start + 1);

718 title[end - start] = '\0';

719 *len = end - start + 1;

720 return title;

721 }

722

723 int gmi_removebookmark(int index) {

724 index--;

725 if (index < 0) return -1;

726 int fail = -1;

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

728 if (i == index) {

729 free(client.bookmarks[i]);

730 fail = 0;

731 }

732 if (!fail)

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

734 }

735 return fail;

736 }

737

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

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

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

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

742 tab->show_error = 1;

743 return;

744 }

745 int title_len = 0;

746 char* tmp_title = NULL;

747 if (!title) tmp_title = title = gmi_gettitle(tab->page, &title_len);

748 else title_len = strnlen(title, 128);

749 long n = 0;

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

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

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

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

754 if (title)

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

756 else

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

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

759 free(tmp_title);

760 }

761

762 int gmi_savebookmarks() {

763 int fd = openat(config_fd, "bookmarks.txt", O_CREAT|O_WRONLY|O_TRUNC, 0600);

764 if (fd < 0)

765 return -1;

766 FILE* f = fdopen(fd, "wb");

767 if (!f)

768 return -1;

769 #ifdef __FreeBSD__

770 if (makefd_writeonly(fd)) {

771 fclose(f);

772 return -1;

773 }

774 #endif

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

776 fprintf(f, "%!s(MISSING)\n", client.bookmarks[i]);

777 fclose(f);

778 return 0;

779

780 }

781

782 char* gmi_getbookmarks(int* len) {

783 char* data = NULL;

784 int n = 0;

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

786 char line[2048];

787 long length = snprintf(line, sizeof(line), "=>%!s(MISSING)\n ", client.bookmarks[i]);

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

789 if (!data) return fatalP();

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

791 n += length-1;

792 }

793 *len = n;

794 return data;

795 }

796

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

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

799 int bm;

800 char* data = gmi_getbookmarks(&bm);

801 pthread_mutex_lock(&tab->render_mutex);

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

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

804 fatal();

805 return;

806 }

807

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

809 sizeof(home_page) + bm, home_page,

810 data?data:"") + 1;

811 free(data);

812

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

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

815

816 if (!add) gmi_freepage(&tab->page);

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

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

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

820 tab->page.code = 20;

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

822 gmi_load(&tab->page);

823 if (add)

824 gmi_addtohistory(tab);

825 else if (tab->history) {

826 tab->history->page = tab->page;

827 tab->history->cached = 1;

828 }

829 tab->scroll = -1;

830 tab->request.data = NULL;

831 pthread_mutex_unlock(&tab->render_mutex);

832 }

833

834 struct gmi_tab* gmi_newtab() {

835 return gmi_newtab_url("about:home");

836 }

837

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

839 size_t index = client.tabs_count;

840 client.tabs_count++;

841 if (client.tabs)

842 client.tabs = realloc(client.tabs, sizeof(struct gmi_tab) * client.tabs_count);

843 else

844 client.tabs = malloc(sizeof(struct gmi_tab));

845 if (!client.tabs) return fatalP();

846 struct gmi_tab* tab = &client.tabs[index];

847 bzero(tab, sizeof(struct gmi_tab));

848 pthread_mutex_init(&tab->render_mutex, NULL);

849

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

851 return NULL;

852 pthread_create(&tab->thread.thread, NULL,

853 (void *(*)(void *))gmi_request_thread, (void*)index);

854 tab->thread.started = 1;

855 if (url)

856 gmi_request(tab, url, 1);

857

858 tab->scroll = -1;

859 return tab;

860 }

861

862 #define PROTO_GEMINI 0

863 #define PROTO_HTTP 1

864 #define PROTO_HTTPS 2

865 #define PROTO_GOPHER 3

866 #define PROTO_FILE 4

867

868 int gmi_parseurl(const char* url, char* host, int host_len, char* urlbuf,

869 int url_len, unsigned short* port) {

870 int proto = PROTO_GEMINI;

871 char* proto_ptr = strstr(url, "://");

872 char* ptr = (char*)url;

873 if (!proto_ptr) {

874 goto skip_proto;

875 }

876 char proto_buf[16];

877 for(; proto_ptr!=ptr; ptr++) {

878 if (!((*ptr > 'a' && *ptr < 'z') || (*ptr > 'A' && *ptr < 'Z'))) goto skip_proto;

879 if (ptr - url >= (signed)sizeof(proto_buf)) goto skip_proto;

880 proto_buf[ptr-url] = tolower(*ptr);

881 }

882 proto_buf[ptr-url] = '\0';

883 ptr+=3;

884 proto_ptr+=3;

885 if (!strcmp(proto_buf,"gemini")) goto skip_proto;

886 else if (!strcmp(proto_buf,"http")) proto = PROTO_HTTP;

887 else if (!strcmp(proto_buf,"https")) proto = PROTO_HTTPS;

888 else if (!strcmp(proto_buf,"gopher")) proto = PROTO_GOPHER;

889 else if (!strcmp(proto_buf,"file")) proto = PROTO_FILE;

890 else {

891 return -1; // unknown protocol

892 }

893 skip_proto:;

894 if (port && proto == PROTO_GEMINI) *port = 1965;

895 if (!proto_ptr) proto_ptr = ptr;

896 char* host_ptr = strchr(ptr, '/');

897 if (!host_ptr) host_ptr = ptr+strnlen(ptr, MAX_URL);

898 char* port_ptr = strchr(ptr, ':');

899 if (port_ptr && port_ptr < host_ptr) {

900 port_ptr++;

901 char c = *host_ptr;

902 *host_ptr = '\0';

903 if (port) {

904 *port = atoi(port_ptr);

905 if (*port < 1) {

906 return -1; // invalid port

907 }

908 }

909 *host_ptr = c;

910 host_ptr = port_ptr - 1;

911 }

912 for(; host_ptr!=ptr; ptr++) {

913 if (host_len <= host_ptr-ptr) {

914 return -1;

915 }

916 if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')

917 || (*ptr >= '0' && *ptr <= '9') || *ptr == '.' || *ptr == '-')) {

918 return -1;

919 }

920 host[ptr-proto_ptr] = *ptr;

921 }

922 host[ptr-proto_ptr] = '\0';

923 if (!urlbuf) return proto;

924 if (url_len < 16) return -1; // buffer too small

925 unsigned int len = 0;

926 switch (proto) {

927 case PROTO_GEMINI:

928 len = strlcpy(urlbuf, "gemini://", url_len);

929 break;

930 case PROTO_HTTP:

931 len = strlcpy(urlbuf, "http://", url_len);

932 break;

933 case PROTO_HTTPS:

934 len = strlcpy(urlbuf, "https://", url_len);

935 break;

936 case PROTO_GOPHER:

937 len = strlcpy(urlbuf, "gopher://", url_len);

938 break;

939 case PROTO_FILE:

940 len = strlcpy(urlbuf, "file://", url_len);

941 break;

942 default:

943 return -1;

944 }

945 size_t l = strlcpy(urlbuf + len, host, sizeof(urlbuf) - len);

946 if (l >= url_len - len) {

947 goto parseurl_overflow;

948 }

949 len += l;

950 if (host_ptr &&

951 strlcpy(urlbuf + len, host_ptr, url_len - len) >=

952 url_len - len)

953 goto parseurl_overflow;

954 return proto;

955 parseurl_overflow:

956 return -2;

957 }

958

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

960 if (!tab) return -1;

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

962 gmi_gohome(tab, add);

963 return tab->page.data_len;

964 }

965 tab->show_error = 0;

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

967 tab->selected = 0;

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

969 int proto = gmi_parseurl(url, tab->request.host, sizeof(tab->request.host),

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

971 &tab->request.port);

972 if (proto == -2) {

973 tab->show_error = 1;

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

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

976 return -1;

977 }

978 if (proto == PROTO_FILE) {

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

980 }

981 if (proto != PROTO_GEMINI) {

982 #ifndef DISABLE_XDG

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

984 #endif

985 tab->show_error = 1;

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

987 "Unable to open the link");

988 return -1;

989 }

990 if (tab->request.tls) {

991 tls_reset(tab->request.tls);

992 } else {

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

994 }

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

996 snprintf(tab->error, sizeof(tab->error), "Failed to initialize TLS");

997 tab->show_error = 1;

998 return -1;

999 }

1000

1001 int cert = cert_getcert(tab->request.host);

1002 if (cert >= 0 &&

1003 tls_config_set_keypair_mem(config,

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

1005 client.certs[cert].crt_len,

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

1007 client.certs[cert].key_len))

1008 {

1009 tab->show_error = 1;

1010 snprintf(tab->error, sizeof(tab->error), "tls error: %!s(MISSING)",

1011 tls_config_error(config));

1012 return -1;

1013 }

1014

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

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

1017 "Failed to configure TLS");

1018 tab->show_error = 1;

1019 return -1;

1020 }

1021 tab->request.state = STATE_REQUESTED;

1022 return 0;

1023 }

1024

1025 #ifdef __linux

1026 void dns_async(union sigval sv) {

1027 struct gmi_tab* tab = sv.sival_ptr;

1028 tab->request.resolved = 1;

1029 }

1030 #endif

1031

1032 int gmi_request_dns(struct gmi_tab* tab) {

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

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

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

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

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

1038 tab->request.resolved = 0;

1039 struct sigevent sevp;

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

1041 sevp.sigev_notify = SIGEV_THREAD;

1042 sevp.sigev_notify_function = dns_async;

1043 sevp.sigev_value.sival_ptr = tab;

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

1045 if (ret) {

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

1047 "Unable request domain name: %!s(MISSING)", tab->request.host);

1048 tab->show_error = 1;

1049 return -1;

1050 }

1051

1052 long start = time(NULL);

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

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

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

1056 nanosleep(&timeout, NULL);

1057 }

1058

1059 if (tab->request.resolved != 1 || tab->request.gaicb_ptr->ar_result == NULL) {

1060 gai_cancel(tab->request.gaicb_ptr);

1061 free(tab->request.gaicb_ptr);

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

1063 "Unknown domain name: %!s(MISSING)", tab->request.host);

1064 tab->show_error = 1;

1065 return -1;

1066 }

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

1068 #else

1069 struct addrinfo hints, *result;

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

1071 hints.ai_family = AF_INET;

1072 hints.ai_socktype = SOCK_STREAM;

1073 hints.ai_flags |= AI_CANONNAME;

1074 errno = 0;

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

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

1077 "Unknown domain name: %!s(MISSING), %!s(MISSING)", tab->request.host, strerror(errno));

1078 tab->show_error = 1;

1079 return -1;

1080 }

1081 #endif

1082

1083 struct sockaddr_in addr4;

1084 struct sockaddr_in6 addr6;

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

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

1087

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

1089 addr4.sin_addr = ((struct sockaddr_in*)result->ai_addr)->sin_addr;

1090 addr4.sin_family = AF_INET;

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

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

1093 tab->request.addr = (struct sockaddr*)&tab->request._addr.addr4;

1094 }

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

1096 addr6.sin6_addr = ((struct sockaddr_in6*)result->ai_addr)->sin6_addr;

1097 addr6.sin6_family = AF_INET6;

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

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

1100 tab->request.addr = (struct sockaddr*)&tab->request._addr.addr6;

1101 } else {

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

1103 "Unexpected error, invalid address family %!s(MISSING)",

1104 tab->request.host);

1105 return -1;

1106 }

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

1108 freeaddrinfo(result);

1109 #ifdef __linux__

1110 free(tab->request.gaicb_ptr);

1111 #endif

1112

1113 tab->request.state = STATE_DNS;

1114 return 0;

1115 }

1116

1117 int gmi_request_connect(struct gmi_tab* tab) {

1118

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

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

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

1122 "Failed to create socket");

1123 return -1;

1124 }

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

1126 if (flags == -1 ||

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

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

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

1130 return -1;

1131 }

1132

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

1134 sizeof(struct sockaddr_in):

1135 sizeof(struct sockaddr_in6);

1136

1137 int connected = 0;

1138 time_t start = time(NULL);

1139 while (!connected) {

1140 errno = 0;

1141 connected = !connect(tab->request.socket, tab->request.addr, addr_size);

1142 if (errno == EISCONN) connected = 1;

1143 if (connected) break;

1144 if (errno != EAGAIN && errno != EWOULDBLOCK &&

1145 errno != EINPROGRESS && errno != EALREADY)

1146 break;

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

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

1149 nanosleep(&timeout, NULL);

1150 }

1151 if (!connected) {

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

1153 "Connection to %!s(MISSING) timed out : %!s(MISSING)",

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

1155 return -1;

1156 }

1157

1158 if (tls_connect_socket(tab->request.tls, tab->request.socket, tab->request.host)) {

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

1160 "Unable to connect to: %!s(MISSING)",

1161 tab->request.host);

1162 return -1;

1163 }

1164 return 0;

1165 }

1166

1167 int gmi_request_handshake(struct gmi_tab* tab) {

1168 if (tls_connect_socket(tab->request.tls, tab->request.socket, tab->request.host)) {

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

1170 "Unable to connect to: %!s(MISSING)",

1171 tab->request.host);

1172 return -1;

1173 }

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

1175 int ret = 0;

1176 time_t start = time(0);

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

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

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

1180 (ret < 0 && ret !=TLS_WANT_POLLIN))

1181 break;

1182 if (ret == TLS_WANT_POLLIN)

1183 nanosleep(&timeout, NULL);

1184 }

1185 if (ret) {

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

1187 "Failed to handshake: %!s(MISSING) (%!s(MISSING))",

1188 tls_error(tab->request.tls),

1189 tab->request.host);

1190 return -1;

1191 }

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

1193 ret = cert_verify(tab->request.host, tls_peer_cert_hash(tab->request.tls),

1194 tls_peer_cert_notbefore(tab->request.tls),

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

1196 if (ret == 1) {

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

1198 "Invalid certificate, enter \":forget %!s(MISSING)\"" \

1199 " to forget the old certificate.",

1200 tab->request.host);

1201 return -1;

1202 }

1203 else if (ret) {

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

1205 ret==-1?"Failed to verify server certificate for %!s(MISSING)" \

1206 "(The certificate changed) %!s(MISSING)":

1207 "Failed to write %!s(MISSING) certificate information : %!s(MISSING)",

1208 tab->request.host, ret==-1?"":strerror(errno));

1209 return -1;

1210 }

1211

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

1213 char buf[MAX_URL];

1214 ssize_t len = gmi_parseuri(tab->request.url, MAX_URL, buf, MAX_URL);

1215 if (len >= MAX_URL ||

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

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

1218 "Url too long: %!s(MISSING)", buf);

1219 return -1;

1220 }

1221 if (tls_write(tab->request.tls, buf, len) < len) {

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

1223 "Failed to send data to: %!s(MISSING)", tab->request.host);

1224 return -1;

1225 }

1226

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

1228 tab->request.state = STATE_CONNECTED;

1229 return 0;

1230 }

1231

1232 int gmi_request_header(struct gmi_tab* tab) {

1233 time_t now = time(0);

1234 char buf[1024];

1235 int recv = TLS_WANT_POLLIN;

1236 while (recv==TLS_WANT_POLLIN || recv==TLS_WANT_POLLOUT) {

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

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

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

1240 "Connection to %!s(MISSING) timed out (sent no data)",

1241 tab->request.host);

1242 return -1;

1243 }

1244 recv = tls_read(tab->request.tls, buf, sizeof(buf));

1245 if (recv == TLS_WANT_POLLIN)

1246 nanosleep(&timeout, NULL);

1247 }

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

1249

1250 if (recv <= 0) {

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

1252 "[%!d(MISSING)] Invalid data from: %!s(MISSING)", recv, tab->request.host);

1253 return -1;

1254 }

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

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

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

1258 return -1;

1259 }

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

1261 if (!ptr) {

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

1263 "Invalid data from: %!s(MISSING)", tab->request.host);

1264 return -1;

1265 }

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

1267

1268 int previous_code = tab->page.code;

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

1270

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

1272 switch (tab->page.code) {

1273 case 10:

1274 case 11:

1275 ptr++;

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

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

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

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

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

1281 tab->request.state = STATE_RECV_HEADER;

1282 tab->request.recv = recv;

1283 return 2; // input

1284 case 20:

1285 {

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

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

1288 if (!meta) {

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

1290 "Invalid data from: %!s(MISSING)", tab->request.host);

1291 return -1;

1292 }

1293 *meta = '\0';

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

1295 *meta = '\r';

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

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

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

1299 #ifdef TERMINAL_IMG_VIEWER

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

1301 #endif

1302 )) {

1303 if (tab->history) {

1304 strlcpy(tab->url, tab->history->url, sizeof(tab->url));

1305 } else {

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

1307 }

1308 tab->request.ask = 2;

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

1310 "# Non-renderable meta-data : %!s(MISSING)",

1311 tab->request.meta);

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

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

1314 tb_interupt();

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

1316 nanosleep(&timeout, NULL);

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

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

1319 tab->request.recv = -1;

1320 return 1; // download

1321 }

1322 }

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

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

1325 }

1326 break;

1327 case 30:

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

1329 "Redirect temporary");

1330 break;

1331 case 31:

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

1333 "Redirect permanent");

1334 break;

1335 case 40:

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

1337 "Temporary failure");

1338 return -2;

1339 case 41:

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

1341 "Server unavailable");

1342 return -2;

1343 case 42:

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

1345 "CGI error");

1346 return -2;

1347 case 43:

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

1349 "Proxy error");

1350 return -2;

1351 case 44:

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

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

1354 return -2;

1355 case 50:

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

1357 "Permanent failure");

1358 return -2;

1359 case 51:

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

1361 "Not found %!s(MISSING)", tab->request.url);

1362 return -2;

1363 case 52:

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

1365 "Resource gone");

1366 return -2;

1367 case 53:

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

1369 "Proxy request refused");

1370 return -2;

1371 case 59:

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

1373 "Bad request");

1374 return -2;

1375 case 60:

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

1377 "Client certificate required");

1378 return -2;

1379 case 61:

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

1381 "Client not authorised");

1382 return -2;

1383 case 62:

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

1385 "Client certificate not valid");

1386 return -2;

1387 default:

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

1389 "Unknown status code: %!d(MISSING)", tab->page.code);

1390 tab->page.code = previous_code;

1391 return -2;

1392 }

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

1394 tab->request.state = STATE_RECV_HEADER;

1395 tab->request.recv = recv;

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

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

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

1399 return 0;

1400 }

1401

1402 int gmi_request_body(struct gmi_tab* tab) {

1403 time_t now = time(0);

1404 char buf[1024];

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

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

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

1408 "Server %!s(MISSING) stopped responding",

1409 tab->request.host);

1410 return -1;

1411 }

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

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

1414 if (bytes == 0) break;

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

1416 nanosleep(&timeout, NULL);

1417 continue;

1418 }

1419 if (bytes < 1) {

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

1421 "Invalid data from %!s(MISSING): %!s(MISSING)",

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

1423 return -1;

1424 }

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

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

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

1428 tab->request.recv += bytes;

1429 now = time(0);

1430 }

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

1432 tab->request.state = STATE_RECV_BODY;

1433 return 0;

1434 }

1435

1436 void* gmi_request_thread(void* ptr) {

1437 size_t index = (size_t)ptr;

1438 #define tab (&client.tabs[index])

1439 unsigned int signal = 0;

1440 while (!client.shutdown) {

1441 tab->selected = 0;

1442 tab->request.state = STATE_DONE;

1443 if (tab->page.code == 10 ||

1444 tab->page.code == 11 ||

1445 tab->page.code == 20)

1446 tb_interupt();

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

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

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

1450 tab->request.state = STATE_STARTED;

1451 int ret = gmi_request_init(tab, tab->thread.url, tab->thread.add);

1452 if (tab->request.state == STATE_CANCEL || ret || gmi_request_dns(tab)) {

1453 if (tab->request.tls) {

1454 tls_close(tab->request.tls);

1455 tls_free(tab->request.tls);

1456 tab->request.tls= NULL;

1457 }

1458 if (ret == -1)

1459 tab->show_error = 1;

1460 tab->request.recv = ret;

1461 continue;

1462 }

1463 if (gmi_request_connect(tab) ||

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

1465 close(tab->request.socket);

1466 if (tab->request.tls) {

1467 tls_close(tab->request.tls);

1468 tls_free(tab->request.tls);

1469 tab->request.tls= NULL;

1470 }

1471 tab->show_error = 1;

1472 tab->request.recv = -1;

1473 continue;

1474 }

1475 ret = -1;

1476 if (!gmi_request_handshake(tab) && tab->request.state != STATE_CANCEL) {

1477 ret = gmi_request_header(tab);

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

1479 }

1480

1481 if (ret == -2) {

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

1483 if (cr) {

1484 *cr = '\0';

1485 char buf[256];

1486 snprintf(buf, sizeof(buf),

1487 "%!s(MISSING) (%!d(MISSING) : %!s(MISSING))", tab->request.error,

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

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

1490 *cr = '\r';

1491 }

1492 }

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

1494 free(tab->request.data);

1495 tab->request.data = NULL;

1496 if (tab->history) {

1497 strlcpy(tab->url, tab->history->url, sizeof(tab->url));

1498 } else {

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

1500 }

1501 tab->show_error = 1;

1502 tab->request.recv = -1;

1503 tab->page.code = 20;

1504 }

1505

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

1507 close(tab->request.socket);

1508 if (tab->request.tls) {

1509 tls_close(tab->request.tls);

1510 tls_free(tab->request.tls);

1511 tab->request.tls= NULL;

1512 }

1513 if (tab->request.recv > 0 && (tab->page.code == 11 || tab->page.code == 10)) {

1514 free(tab->request.data);

1515 tab->request.data = NULL;

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

1517 continue;

1518 }

1519 if (tab->request.recv > 0 && (tab->page.code == 31 || tab->page.code == 30)) {

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

1521 if (!ptr) {

1522 free(tab->request.data);

1523 continue;

1524 }

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

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

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

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

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

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

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

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

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

1534 free(tab->request.data);

1535 continue;

1536 }

1537 tab->page.data_len = r;

1538 free(tab->request.data);

1539 tab->request.data = NULL;

1540 gmi_load(&tab->page);

1541 tab->history->page = tab->page;

1542 tab->history->cached = 1;

1543 tab->request.recv = r;

1544 continue;

1545 }

1546 if (!tab->request.download && tab->request.recv > 0 && tab->page.code == 20) {

1547 struct gmi_link* link_ptr = tab->history?tab->history->prev:NULL;

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

1549 if (!link_ptr) break;

1550 link_ptr = link_ptr->prev;

1551 }

1552 while (link_ptr) {

1553 if (link_ptr->cached) {

1554 gmi_freepage(&link_ptr->page);

1555 link_ptr->cached = 0;

1556 }

1557 link_ptr = link_ptr->prev;

1558 }

1559 pthread_mutex_lock(&tab->render_mutex);

1560 if (!tab->thread.add)

1561 gmi_freepage(&tab->page);

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

1563 tab->page.code = 20;

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

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

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

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

1568 gmi_load(&tab->page);

1569 if (tab->thread.add)

1570 gmi_addtohistory(tab);

1571 else if (tab->history) {

1572 tab->history->page = tab->page;

1573 tab->history->cached = 1;

1574 }

1575 tab->scroll = -1;

1576 pthread_mutex_unlock(&tab->render_mutex);

1577 }

1578 if (tab->request.download && tab->request.recv > 0 && tab->page.code == 20) {

1579 int len = strnlen(tab->request.url, sizeof(tab->request.url));

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

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

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

1583 FILE* f;

1584 int fd = getdownloadfd();

1585 if (fd < 0) {

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

1587 "Unable to open download folder");

1588 tab->show_error = 1;

1589 goto request_end;

1590 }

1591 char path[1024];

1592 if (ptr)

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

1594 else

1595 snprintf(path, sizeof(path),

1596 "output_%!l(MISSING)d.dat", time(NULL));

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

1598 char c = path[i];

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

1600 !(c == '.' ||

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

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

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

1604 ))

1605 path[i] = '_';

1606 }

1607 int dfd = openat(fd, path, O_CREAT|O_EXCL|O_WRONLY, 0600);

1608 if (dfd < 0) {

1609 char buf[1024];

1610 snprintf(buf, sizeof(buf), "%!l(MISSING)d_%!s(MISSING)", time(NULL), path);

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

1612 dfd = openat(fd, path, O_CREAT|O_EXCL|O_WRONLY, 0600);

1613 if (dfd < 0) {

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

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

1616 tab->show_error = 1;

1617 goto request_end;

1618 }

1619 }

1620 f = fdopen(dfd, "wb");

1621 if (!f) {

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

1623 "Failed to write the downloaded file");

1624 tab->show_error = 1;

1625 goto request_end;

1626 } else {

1627 #ifdef __FreeBSD__

1628 if (make_writeonly(f)) {

1629 client.shutdown = 1;

1630 break;

1631 }

1632 #endif

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

1634 if (ptr) {

1635 fwrite(ptr, 1,

1636 tab->request.recv - (ptr-tab->request.data),

1637 f);

1638 fclose(f);

1639 #ifndef DISABLE_XDG

1640 int fail = 0;

1641 if (client.xdg) {

1642 tab->request.ask = 2;

1643 snprintf(tab->request.info,

1644 sizeof(tab->request.info),

1645 "# %!s(MISSING) download",

1646 tab->request.meta);

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

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

1649 tb_interupt();

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

1651 nanosleep(&timeout, NULL);

1652 if (tab->request.ask) {

1653 char file[1024];

1654 snprintf(file, sizeof(file), "%!s(MISSING)/%!s(MISSING)",

1655 download_path, path);

1656 fail = xdg_open(file);

1657 }

1658 }

1659 if (fail) {

1660 tab->show_error = 1;

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

1662 "Failed to open %!s(MISSING)", path);

1663 } else

1664 #endif

1665 {

1666 tab->show_info = 1;

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

1668 "File downloaded to %!s(MISSING)", path);

1669 }

1670

1671 } else {

1672 tab->show_error = 1;

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

1674 "Server sent invalid data");

1675 }

1676 }

1677 request_end:

1678 free(tab->request.data);

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

1680 }

1681 }

1682 #undef tab

1683 return NULL;

1684 }

1685

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

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

1688 tab->request.state = STATE_CANCEL;

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

1690 nanosleep(&timeout, NULL);

1691 tab->thread.add = add;

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

1693 int signal = 0;

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

1695 return -1;

1696 return 0;

1697 }

1698

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

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

1701 if (!f) {

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

1703 "Failed to open %!s(MISSING)", path);

1704 tab->show_error = 1;

1705 return -1;

1706 }

1707 #ifdef __FreeBSD__

1708 if (make_readonly(f)) {

1709 fclose(f);

1710 client.shutdown = 1;

1711 return -1;

1712 }

1713 #endif

1714 fseek(f, 0, SEEK_END);

1715 size_t len = ftell(f);

1716 fseek(f, 0, SEEK_SET);

1717 char* data = malloc(len);

1718 if (!data) return fatalI();

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

1720 fclose(f);

1721 free(data);

1722 return -1;

1723 }

1724 fclose(f);

1725 pthread_mutex_lock(&tab->render_mutex);

1726 tab->page.code = 20;

1727 tab->page.data_len = len;

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

1729 tab->page.data = data;

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

1731 int i = strnlen(path, 1024);

1732 int gmi = 0;

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

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

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

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

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

1738 gmi = 1;

1739 if (gmi)

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

1741 else

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

1743 gmi_load(&tab->page);

1744 gmi_addtohistory(tab);

1745 pthread_mutex_unlock(&tab->render_mutex);

1746 return len;

1747 }

1748

1749 int gmi_init() {

1750 if (tls_init()) {

1751 tb_shutdown();

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

1753 return -1;

1754 }

1755

1756 config = tls_config_new();

1757 if (!config) {

1758 tb_shutdown();

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

1760 return -1;

1761 }

1762

1763 config_empty = tls_config_new();

1764 if (!config_empty) {

1765 tb_shutdown();

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

1767 return -1;

1768 }

1769

1770 tls_config_insecure_noverifycert(config);

1771 tls_config_insecure_noverifycert(config_empty);

1772 #ifndef DISABLE_XDG

1773 int xdg = client.xdg;

1774 #endif

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

1776 #ifndef DISABLE_XDG

1777 client.xdg = xdg;

1778 #endif

1779

1780 int fd = getconfigfd();

1781 if (fd < 0) {

1782 tb_shutdown();

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

1784 return -1;

1785 }

1786

1787 if (gmi_loadbookmarks()) {

1788 gmi_newbookmarks();

1789 }

1790

1791 if (cert_load()) {

1792 tb_shutdown();

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

1794 return -1;

1795 }

1796

1797 return 0;

1798 }

1799

1800 void gmi_free() {

1801 for (int i=0; i < client.tabs_count; i++)

1802 gmi_freetab(&client.tabs[i]);

1803 gmi_savebookmarks();

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

1805 free(client.bookmarks[i]);

1806 free(client.bookmarks);

1807 free(client.tabs);

1808 cert_free();

1809 }

1810