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

1 #include <tinyscheme/scheme.h>

2 #ifdef __linux__

3 #define _GNU_SOURCE

4 #endif

5 #include <string.h>

6 #include <netinet/in.h>

7 #include <stdint.h>

8 #include <stdlib.h>

9 #include <stdio.h>

10 #include <strings.h>

11 #include <unistd.h>

12 #include <netdb.h>

13 #include <tls.h>

14 #include <poll.h>

15 #include <fcntl.h>

16 #include <errno.h>

17 #include <termbox.h>

18 #include <ctype.h>

19 #include <time.h>

20 #include "gemini.h"

21 #include "scheme.h"

22 #ifdef __linux__

23 #include <bsd/string.h>

24 char *strcasestr(const char *haystack, const char *needle);

25 #endif

26 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

27 #include <pthread.h>

28 #include <sys/socket.h>

29 #endif

30 #include "cert.h"

31 #include "wcwidth.h"

32 #include "display.h"

33

34 #define TIMEOUT 3

35 struct timespec timeout = {0, 50000000};

36

37 struct tls_config* config;

38 struct tls_config* config_empty;

39 struct tls* ctx;

40 struct gmi_client client;

41

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

43

44 void fatal() {

45 tb_shutdown();

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

47 exit(0);

48 }

49

50 int fatalI() {

51 fatal();

52 return -1;

53 }

54

55 void* fatalP() {

56 fatal();

57 return NULL;

58 }

59

60 int getbookmark(char* path, size_t len) {

61 int length = getcachefolder(path, len);

62 const char bookmark[] = "/bookmarks.txt";

63 if (length + sizeof(bookmark) >= len) return -1;

64 return length + strlcpy(&path[length], bookmark, len - length);

65 }

66

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

68 int j = 0;

69 int inquery = 0;

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

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

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

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

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

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

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

77 url[i] == ':' || url[i] == '-') {

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

79 buf[j] = url[i];

80 j++;

81 } else {

82 char format[8];

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

84 buf[j] = '\0';

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

86 }

87 }

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

89 buf[j] = '\0';

90 return j;

91 }

92

93 int gmi_goto(int id) {

94 id--;

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

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

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

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

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

100 client.input.error = 1;

101 return -1;

102 }

103 gmi_cleanforward(&client.tabs[client.tab]);

104 int ret = gmi_nextlink(client.tabs[client.tab].url, page->links[id]);

105 if (ret < 1) return ret;

106 client.tabs[client.tab].page.data_len = ret;

107 gmi_load(page);

108 client.tabs[client.tab].scroll = -1;

109 return ret;

110 }

111

112 int gmi_goto_new(int id) {

113 id--;

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

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

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

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

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

119 client.input.error = 1;

120 return -1;

121 }

122 int old_tab = client.tab;

123 gmi_newtab();

124 client.tab = client.tabs_count - 1;

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

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

127 if (ret < 1) return ret;

128 client.tabs[client.tab].page.data_len = ret;

129 gmi_load(&client.tabs[client.tab].page);

130 return ret;

131 }

132

133 int gmi_nextlink(char* url, char* link) {

134 int url_len = strnlen(url, MAX_URL);

135 if (link[0] == '(' && link[strnlen(link, MAX_URL)-1] == ')') {

136 scheme_load_string(client.tabs[client.tab].ctx, link);

137 return 0;

138 }

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

140 int ret = gmi_request(&link[2]);

141 if (ret < 1) return ret;

142 return ret;

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

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

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

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

147 }

148 char urlbuf[MAX_URL];

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

150 if (l >= sizeof(urlbuf))

151 goto nextlink_overflow;

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

153 goto nextlink_overflow;

154 int ret = gmi_request(urlbuf);

155 return ret;

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

157 strstr(link, "http://") ||

158 strstr(link, "gopher://")) {

159 #ifndef DISABLE_XDG

160 if (client.xdg) {

161 char buf[1048];

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

163 #ifdef __OpenBSD__

164 if (fork() == 0) {

165 setsid();

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

167 execvp(argv[0], argv);

168 exit(0);

169 }

170 #else

171 system(buf);

172 #endif

173 return -1;

174 }

175 #endif

176 client.input.error = 1;

177 snprintf(client.tabs[client.tab].error,

178 sizeof(client.tabs[client.tab].error),

179 "Can't open web link");

180 return -1;

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

182 int ret = gmi_request(link);

183 return ret;

184 } else {

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

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

187 char urlbuf[MAX_URL];

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

189 if (l >= sizeof(urlbuf))

190 goto nextlink_overflow;

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

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

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

194 goto nextlink_overflow;

195 l += l2;

196 }

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

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

199 goto nextlink_overflow;

200 l += l2;

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

202 int ret = gmi_request(urlbuf);

203 return ret;

204 }

205 nextlink_overflow:

206 client.input.error = 1;

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

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

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

210 return -1;

211 }

212

213 void gmi_load(struct gmi_page* page) {

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

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

216 free(page->links);

217 page->links = NULL;

218 page->links_count = 0;

219 page->lines = 0;

220 int x = 0;

221 page->objs_count = 0;

222 page->data[page->data_len] = '\0';

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

224 if (page->data[c] == '%!'(MISSING) &&

225 page->data[c+1] == '%!'(MISSING) &&

226 page->data[c+2] == ':'

227 ) {

228 page->data_len = c-1;

229 page->script_start = c+3;

230 break;

231 }

232 if (page->data[c] == '%!'(MISSING) && page->data[c+1] == '{') {

233 int end = 0;

234 int space = 1;

235 char buf[64];

236 int pos = 0;

237 for (int i = c+2; i < page->data_len; i++) {

238 if (space &&

239 (page->data[i] == ' ' ||

240 page->data[i] == '\t'))

241 continue;

242 if (page->data[i] == '\n') break;

243 if (page->data[i] == '}') {

244 end = i;

245 break;

246 }

247 buf[pos] = page->data[i];

248 pos++;

249 }

250 buf[pos] = '\0';

251 if (end) {

252 struct gmi_obj obj;

253 strlcpy(obj.id, buf, sizeof(obj.id));

254 obj.value = NULL;

255 obj.start = c;

256 obj.end = end;

257 page->objs_count++;

258 page->objs = realloc(page->objs,

259 sizeof(obj)*page->objs_count);

260 memcpy(&page->objs[page->objs_count-1], &obj, sizeof(obj));

261 c = end;

262 }

263 }

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

265 c += 2;

266 int nospace = c;

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

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

269 c = nospace;

270 break;

271 }

272 }

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

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

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

276 break;

277 }

278 }

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

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

281 if (page->links)

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

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

284 else

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

286 if (!page->links) {

287 fatal();

288 return;

289 }

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

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

292 page->data[c] = save;

293 continue;

294 }

295 int len = strnlen(url, MAX_URL);

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

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

298 fatal();

299 return;

300 }

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

302 page->links_count++;

303 page->data[c] = save;

304 }

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

306 page->lines++;

307 x = 0;

308 continue;

309 }

310 x++;

311 }

312 }

313

314 int gmi_render(struct gmi_tab* tab) {

315 if (!tab->page.script_exec) {

316 //tb_shutdown();

317 scheme_load_string(tab->ctx, &tab->page.data[tab->page.script_start]);

318 //tb_init();

319 tab->page.script_exec = 1;

320 }

321 #include "img.h"

322 #ifdef TERMINAL_IMG_VIEWER

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

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

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

326 if (!ptr) {

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

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

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

330 return 1;

331 }

332 ptr++;

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

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

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

336 }

337 tab->page.img.data =

338 stbi_load_from_memory((unsigned char*)ptr,

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

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

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

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

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

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

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

346 return 1;

347 }

348

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

350 }

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

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

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

354 client.tabs_count>1);

355 return 1;

356 }

357 }

358 #endif

359

360 int text = 0;

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

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

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

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

365 return 1;

366 }

367 text = 1;

368 }

369 int line = 0;

370 int x = 0;

371 int links = 0;

372 uintattr_t color = TB_DEFAULT;

373 int start = 1;

374 int ignore = 0;

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

376 for (int c = 0; c < tab->page.data_len; c++) {

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

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

379 line++;

380 continue;

381 }

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

383 x+=4;

384 continue;

385 }

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

387 int atobj = 0;

388 for (int i = 0; i < tab->page.objs_count; i++) {

389 if (tab->page.objs[i].start == c) {

390 atobj = 1;

391 c = tab->page.objs[i].end;

392 if (tab->page.objs[i].value) {

393 tb_printf(x+2, line-1-tab->scroll,

394 TB_DEFAULT, TB_DEFAULT,

395 "%!s(MISSING)", tab->page.objs[i].value);

396 }

397 break;

398 }

399 }

400 if (atobj) continue;

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

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

403 ignore = !ignore;

404 if (!ignore && !text) {

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

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

407 color = TB_RED + i;

408 break;

409 }

410 }

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

412 color = TB_ITALIC|TB_CYAN;

413 }

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

415 color = TB_ITALIC|TB_MAGENTA;

416 }

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

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

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

420 char buf[32];

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

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

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

424 TB_DEFAULT, buf);

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

426 }

427 c += 2;

428

429 while (

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

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

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

433

434 int initial = c;

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

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

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

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

439

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

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

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

443

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

445 c = initial;

446 x+=3;

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

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

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

450 links++;

451 }

452 }

453

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

455 x+4 >= tb_width()) {

456 int end = 0;

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

458 end = 1;

459 }

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

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

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

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

464 newline = 0;

465 break;

466 }

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

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

469 break;

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

471 }

472 if (newline) {

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

474 line++;

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

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

477 color = TB_DEFAULT;

478 start = 1;

479 }

480 x = 0;

481 continue;

482 } else if (end) {

483 c++;

484 x++;

485 }

486 }

487 uint32_t ch = 0;

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

489 if (size > 0)

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

491

492 int wc = mk_wcwidth(ch);

493 if (wc < 0) wc = 0;

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

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

496 if (wc == 1)

497 tb_set_cell(x+2, line-1-tab->scroll, ch, color, TB_DEFAULT);

498 else

499 tb_set_cell_ex(x+2, line-1-tab->scroll, &ch,

500 wc, color, TB_DEFAULT);

501 }

502

503 x += wc;

504 start = 0;

505 }

506 line++;

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

508 if (h > line) return line;

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

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

511 if (size < 1) size = 1;

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

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

514 + (client.tabs_count>1) ;

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

516 if (pos < 0) pos = 0;

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

518 int w = tb_width();

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

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

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

522 else

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

524 return line;

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 free(link);

535 link = ptr;

536 }

537 tab->history->next = NULL;

538 }

539

540

541 void gmi_freetab(struct gmi_tab* tab) {

542 if (tab->history) {

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

544 while (link) {

545 struct gmi_link* ptr = link->next;

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

547 free(link);

548 link = ptr;

549 }

550 link = tab->history->prev;

551 while (link) {

552 struct gmi_link* ptr = link->prev;

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

554 free(link);

555 link = ptr;

556 }

557 bzero(tab->history->url, sizeof(tab->history->url));

558 free(tab->history);

559 }

560 for (int i=0; i < tab->page.links_count; i++)

561 free(tab->page.links[i]);

562 free(tab->page.links);

563 free(tab->page.data);

564 #ifdef TERMINAL_IMG_VIEWER

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

566 #endif

567 }

568

569

570 char home_page[] =

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

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

573 "## Bookmarks\n\n" \

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

575 "## Keybindings\n\n" \

576 "* k - Scroll up\n" \

577 "* j - Scroll down\n" \

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

593 "## Commands\n\n" \

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

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

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

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

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

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

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

601 "* :gencert - Generate a certificate for the current capsule"

602 ;

603

604 void gmi_newbookmarks() {

605 int len;

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

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

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

609 if (!client.bookmarks) goto fail_malloc;

610

611 len = sizeof(geminispace);

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

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

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

615

616 len = sizeof(gemigit);

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

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

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

620

621 client.bookmarks[2] = NULL;

622 return;

623 fail_malloc:

624 fatal();

625 }

626

627 int gmi_loadbookmarks() {

628 char path[1024];

629 if (getbookmark(path, sizeof(path)) < 1) return -1;

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

631 if (!f)

632 return -1;

633 fseek(f, 0, SEEK_END);

634 size_t len = ftell(f);

635 fseek(f, 0, SEEK_SET);

636 char* data = malloc(len);

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

638 fclose(f);

639 return -1;

640 }

641 fclose(f);

642 char* ptr = data;

643 long n = 0;

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

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

646 client.bookmarks[n] = NULL;

647 n = 0;

648 ptr = data;

649 char* str = data;

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

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

652 *ptr = '\0';

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

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

655 n++;

656 str = ptr+1;

657 }

658 }

659 free(data);

660 return 0;

661 }

662

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

664 int start = -1;

665 int end = -1;

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

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

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

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

670 break;

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

672 start = j;

673 break;

674 }

675 }

676 }

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

678 end = i;

679 break;

680 }

681 }

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

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

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

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

686 *len = end - start + 1;

687 return title;

688 }

689

690 int gmi_removebookmark(int index) {

691 index--;

692 if (index < 0) return -1;

693 int fail = -1;

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

695 if (i == index) {

696 free(client.bookmarks[i]);

697 fail = 0;

698 }

699 if (!fail)

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

701 }

702 return fail;

703 }

704

705 void gmi_addbookmark(char* url, char* title) {

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

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

708 snprintf(tab->error,

709 sizeof(client.tabs[client.tab].error),

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

711 client.input.error = 1;

712 return;

713 }

714 int title_len = 0;

715 char* tmp_title = NULL;

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

717 else title_len = strnlen(title, 128);

718 long n = 0;

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

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

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

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

723 if (title)

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

725 else

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

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

728 free(tmp_title);

729 }

730

731 int gmi_savebookmarks() {

732 char path[1024];

733 if (getbookmark(path, sizeof(path)) < 1) return -1;

734 FILE* f = fopen(path, "wb");

735 if (!f) {

736 return -1;

737 }

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

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

740 fclose(f);

741 return 0;

742

743 }

744

745 char* gmi_getbookmarks(int* len) {

746 char* data = NULL;

747 int n = 0;

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

749 char line[2048];

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

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

752 if (!data) return fatalP();

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

754 n += length-1;

755 }

756 *len = n;

757 return data;

758 }

759

760 void gmi_gohome(struct gmi_tab* tab) {

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

762 tab->scroll = -1;

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

764 tab->page.code = 20;

765 int bm;

766 char* data = gmi_getbookmarks(&bm);

767 tab->page.data = malloc(sizeof(home_page) + bm);

768 if (!tab->page.data) {

769 fatal();

770 return;

771 }

772

773 tab->page.data_len =

774 snprintf(tab->page.data, sizeof(home_page) + bm, home_page,

775 data?data:"") + 1;

776 free(data);

777

778 strlcpy(tab->page.meta, "text/gemini",

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

780

781 gmi_load(&tab->page);

782 }

783

784 void gmi_newtab() {

785 int tab = client.tabs_count;

786 client.tabs_count++;

787 if (client.tabs)

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

789 else

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

791 if (!client.tabs) {

792 fatal();

793 return;

794 }

795 gmi_gohome(&client.tabs[tab]);

796 scm_init_tab(&client.tabs[tab]);

797 }

798

799 #define PROTO_GEMINI 0

800 #define PROTO_HTTP 1

801 #define PROTO_HTTPS 2

802 #define PROTO_GOPHER 3

803 #define PROTO_FILE 4

804

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

806 int url_len, unsigned short* port) {

807 int proto = PROTO_GEMINI;

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

809 char* ptr = (char*)url;

810 if (!proto_ptr) {

811 goto skip_proto;

812 }

813 char proto_buf[16];

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

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

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

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

818 }

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

820 ptr+=3;

821 proto_ptr+=3;

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

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

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

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

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

827 else {

828 return -1; // unknown protocol

829 }

830 skip_proto:;

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

832 if (!proto_ptr) proto_ptr = ptr;

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

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

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

836 if (port_ptr && port_ptr < host_ptr) {

837 port_ptr++;

838 char c = *host_ptr;

839 *host_ptr = '\0';

840 if (port) {

841 *port = atoi(port_ptr);

842 if (*port < 1) {

843 return -1; // invalid port

844 }

845 }

846 *host_ptr = c;

847 host_ptr = port_ptr - 1;

848 }

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

850 if (host_len <= host_ptr-ptr) {

851 return -1;

852 }

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

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

855 return -1;

856 }

857 host[ptr-proto_ptr] = *ptr;

858 }

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

860 if (!urlbuf) return proto;

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

862 int len = 0;

863 switch (proto) {

864 case PROTO_GEMINI:

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

866 break;

867 case PROTO_HTTP:

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

869 break;

870 case PROTO_HTTPS:

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

872 break;

873 case PROTO_GOPHER:

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

875 break;

876 case PROTO_FILE:

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

878 break;

879 default:

880 return -1;

881 }

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

883 if (l >= sizeof(urlbuf) - len) {

884 goto parseurl_overflow;

885 }

886 len += l;

887 if (host_ptr &&

888 strlcpy(urlbuf + len, host_ptr, sizeof(urlbuf) - len) >=

889 sizeof(urlbuf) - len)

890 goto parseurl_overflow;

891 return proto;

892 parseurl_overflow:

893 client.input.error = 1;

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

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

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

897 return -1;

898 }

899

900 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

901 struct conn {

902 int connected;

903 int socket;

904 struct sockaddr* addr;

905 int family;

906 struct sockaddr_in addr4;

907 struct sockaddr_in6 addr6;

908 pthread_t tid;

909 pthread_cond_t cond;

910 pthread_mutex_t mutex;

911 };

912 struct conn conn;

913

914 void conn_thread() {

915 if (pthread_mutex_init(&conn.mutex, NULL)) {

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

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

918 "Failed to initialize connection mutex\n");

919 return;

920 }

921 pthread_mutex_lock(&conn.mutex);

922 while (1) {

923 pthread_cond_wait(&conn.cond, &conn.mutex);

924 if (connect(conn.socket, conn.addr,

925 (conn.family == AF_INET)?sizeof(conn.addr4):sizeof(conn.addr6)) != 0) {

926 conn.connected = -1;

927 continue;

928 }

929 conn.connected = 1;

930 }

931 }

932

933 struct dnsquery {

934 struct addrinfo* result;

935 int resolved;

936 char* host;

937 pthread_t tid;

938 pthread_cond_t cond;

939 pthread_mutex_t mutex;

940 };

941 struct dnsquery dnsquery;

942

943 void dnsquery_thread(int signal) {

944 if (signal != 0 && signal != SIGUSR1) return;

945 if (pthread_mutex_init(&dnsquery.mutex, NULL)) {

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

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

948 "Failed to initialize connection mutex\n");

949 return;

950 }

951 pthread_mutex_lock(&dnsquery.mutex);

952 while (1) {

953 pthread_cond_wait(&dnsquery.cond, &dnsquery.mutex);

954 struct addrinfo hints;

955 memset (&hints, 0, sizeof (hints));

956 hints.ai_family = PF_UNSPEC;

957 hints.ai_socktype = SOCK_STREAM;

958 hints.ai_flags |= AI_CANONNAME;

959 if (getaddrinfo(dnsquery.host, NULL, &hints, &dnsquery.result)) {

960 dnsquery.resolved = -1;

961 continue;

962 }

963 dnsquery.resolved = 1;

964 }

965 }

966

967 void signal_cb() {

968 pthread_t thread = pthread_self();

969 if (thread == conn.tid)

970 pthread_mutex_destroy(&conn.mutex);

971 if (thread == dnsquery.tid)

972 pthread_mutex_destroy(&dnsquery.mutex);

973 pthread_exit(NULL);

974 }

975

976 #endif

977

978 int gmi_request(const char* url) {

979 client.input.error = 0;

980 char* data_buf = NULL;

981 char meta_buf[1024];

982 char url_buf[1024];

983 int download = 0;

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

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

986 tab->selected = 0;

987 int recv = TLS_WANT_POLLIN;

988 int sockfd = -1;

989 char gmi_host[256];

990 gmi_host[0] = '\0';

991 unsigned short port;

992 int proto = gmi_parseurl(url, gmi_host, sizeof(gmi_host),

993 url_buf, sizeof(url_buf), &port);

994 if (proto == PROTO_FILE) {

995 if (gmi_loadfile(&url_buf[P_FILE]) > 0)

996 gmi_load(&tab->page);

997 return 0;

998 }

999 if (proto != PROTO_GEMINI) {

1000 snprintf(tab->error, sizeof(tab->error), "Invalid url: %!s(MISSING)", url);

1001 client.input.error = 1;

1002 return -1;

1003 }

1004 if (ctx) {

1005 tls_close(ctx);

1006 ctx = NULL;

1007 }

1008 ctx = tls_client();

1009 if (!ctx) {

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

1011 client.input.error = 1;

1012 return -1;

1013 }

1014

1015 int cert = 0;

1016 char crt[1024];

1017 char key[1024];

1018 if (cert_getpath(gmi_host, crt, sizeof(crt), key, sizeof(key)) == -1) {

1019 client.input.error = 1;

1020 cert = -1;

1021 }

1022

1023 if (cert == 0) {

1024 FILE* f = fopen(crt, "rb");

1025 if (f) {

1026 cert = 1;

1027 fclose(f);

1028 }

1029 }

1030

1031 if (cert == 1 && tls_config_set_keypair_file(config, crt, key)) {

1032 client.input.error = 1;

1033 snprintf(tab->error, sizeof(tab->error), "%!s(MISSING)",

1034 tls_config_error(config));

1035 return -1;

1036 }

1037

1038 if (tls_configure(ctx, cert?config:config_empty)) {

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

1040 "Failed to configure TLS");

1041 client.input.error = 1;

1042 return -1;

1043 }

1044

1045 // Manually create socket to set a timeout

1046 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

1047 dnsquery.host = gmi_host;

1048 dnsquery.resolved = 0;

1049 pthread_cond_signal(&dnsquery.cond);

1050 for (int i=0; i < TIMEOUT * 20 && !dnsquery.resolved; i++)

1051 nanosleep(&timeout, NULL);

1052

1053 if (dnsquery.resolved != 1 || dnsquery.result == NULL) {

1054 if (!dnsquery.resolved) {

1055 pthread_cancel(dnsquery.tid);

1056 pthread_kill(dnsquery.tid, SIGUSR1);

1057 pthread_join(dnsquery.tid, NULL);

1058 pthread_create(&dnsquery.tid, NULL,

1059 (void *(*)(void *))dnsquery_thread, NULL);

1060 }

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

1062 "Unknown domain name: %!s(MISSING)", gmi_host);

1063 goto request_error;

1064 }

1065 struct addrinfo *result = dnsquery.result;

1066 #else

1067 struct addrinfo hints, *result;

1068 memset (&hints, 0, sizeof (hints));

1069 hints.ai_family = PF_UNSPEC;

1070 hints.ai_socktype = SOCK_STREAM;

1071 hints.ai_flags |= AI_CANONNAME;

1072 if (getaddrinfo(gmi_host, NULL, &hints, &result)) {

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

1074 "Unknown domain name: %!s(MISSING)", gmi_host);

1075 goto request_error;

1076 }

1077 #endif

1078

1079 struct sockaddr_in addr4;

1080 struct sockaddr_in6 addr6;

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

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

1083 sockfd = socket(AF_INET, SOCK_STREAM, 0);

1084 struct timeval tv;

1085 tv.tv_sec = TIMEOUT;

1086 tv.tv_usec = 0;

1087 setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv);

1088 setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

1089 struct sockaddr* addr = NULL;

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

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

1092 addr4.sin_family = AF_INET;

1093 addr4.sin_port = htons(port);

1094 addr = (struct sockaddr*)&addr4;

1095 }

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

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

1098 addr6.sin6_family = AF_INET6;

1099 addr6.sin6_port = htons(port);

1100 addr = (struct sockaddr*)&addr6;

1101 } else {

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

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

1104 goto request_error;

1105 }

1106 int family = result->ai_family;

1107 freeaddrinfo(result);

1108

1109 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

1110 conn.connected = 0;

1111 conn.addr = addr;

1112 conn.addr4 = addr4;

1113 conn.addr6 = addr6;

1114 conn.family = family;

1115 conn.socket = sockfd;

1116 pthread_cond_signal(&conn.cond);

1117 for (int i=0; i < TIMEOUT * 20 && !conn.connected; i++)

1118 nanosleep(&timeout, NULL);

1119 if (conn.connected != 1) {

1120 if (!conn.connected) {

1121 pthread_cancel(conn.tid);

1122 pthread_kill(conn.tid, SIGUSR1);

1123 pthread_join(conn.tid, NULL);

1124 pthread_create(&conn.tid, NULL, (void *(*)(void *))conn_thread, NULL);

1125 }

1126 #else

1127 int addr_size = (family == AF_INET)?sizeof(addr4):sizeof(addr6);

1128 if (connect(sockfd, addr, addr_size) != 0) {

1129 #endif

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

1131 "Connection to %!s(MISSING) timed out", gmi_host);

1132 goto request_error;

1133 }

1134

1135 if (tls_connect_socket(ctx, sockfd, gmi_host)) {

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

1137 "Unable to connect to: %!s(MISSING)", gmi_host);

1138 goto request_error;

1139 }

1140 if (tls_handshake(ctx)) {

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

1142 "Failed to handshake: %!s(MISSING)", gmi_host);

1143 goto request_error;

1144 }

1145 char buf[MAX_URL];

1146 ssize_t len = gmi_parseuri(url_buf, MAX_URL, buf, MAX_URL);

1147 if (len >= MAX_URL ||

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

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

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

1151 goto request_error;

1152 }

1153 if (tls_write(ctx, buf, len) < len) {

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

1155 "Failed to send data to: %!s(MISSING)", gmi_host);

1156 goto request_error;

1157 }

1158 time_t now = time(0);

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

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

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

1162 "Connection to %!s(MISSING) timed out (sent no data)", gmi_host);

1163 goto request_error;

1164 }

1165 recv = tls_read(ctx, buf, sizeof(buf));

1166 }

1167

1168 if (recv <= 0) {

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

1170 "[%!d(MISSING)] Invalid data from: %!s(MISSING)", recv, gmi_host);

1171 goto request_error;

1172 }

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

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

1175 "Invalid data from: %!s(MISSING) (no CRLF)", gmi_host);

1176 goto request_error;

1177 }

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

1179 if (!ptr) {

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

1181 "Invalid data from: %!s(MISSING)", gmi_host);

1182 goto request_error;

1183 }

1184 int previous_code = tab->page.code;

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

1186

1187 switch (tab->page.code) {

1188 case 10:

1189 case 11:

1190 ptr++;

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

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

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

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

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

1196 goto exit;

1197 case 20:

1198 {

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

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

1201 if (!meta) {

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

1203 "Invalid data from: %!s(MISSING)", gmi_host);

1204 goto request_error;

1205 }

1206 *meta = '\0';

1207 strlcpy(meta_buf, ptr, MAX_META);

1208 *meta = '\r';

1209 if ((strcasestr(meta_buf, "charset=") &&

1210 !strcasestr(meta_buf, "charset=utf-8"))

1211 || ((strncmp(meta_buf, "text/", 5))

1212 #ifdef TERMINAL_IMG_VIEWER

1213 && (strncmp(meta_buf, "image/", 6))

1214 #endif

1215 )) {

1216 if (tab->history) {

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

1218 } else {

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

1220 }

1221 download = display_download(meta_buf);

1222 if (!download) {

1223 recv = -1;

1224 goto exit_download;

1225 }

1226 }

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

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

1229 }

1230 break;

1231 case 30:

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

1233 "Redirect temporary");

1234 break;

1235 case 31:

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

1237 "Redirect permanent");

1238 break;

1239 case 40:

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

1241 "Temporary failure");

1242 goto request_error_msg;

1243 case 41:

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

1245 "Server unavailable");

1246 goto request_error_msg;

1247 case 42:

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

1249 "CGI error");

1250 goto request_error_msg;

1251 case 43:

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

1253 "Proxy error");

1254 goto request_error_msg;

1255 case 44:

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

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

1258 goto request_error_msg;

1259 case 50:

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

1261 "Permanent failure");

1262 goto request_error_msg;

1263 case 51:

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

1265 "Not found %!s(MISSING)", url);

1266 goto request_error_msg;

1267 case 52:

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

1269 "Resource gone");

1270 goto request_error_msg;

1271 case 53:

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

1273 "Proxy request refused");

1274 goto request_error_msg;

1275 case 59:

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

1277 "Bad request");

1278 goto request_error_msg;

1279 case 60:

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

1281 "Client certificate required");

1282 goto request_error_msg;

1283 case 61:

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

1285 "Client not authorised");

1286 goto request_error_msg;

1287 case 62:

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

1289 "Client certificate not valid");

1290 goto request_error_msg;

1291 default:

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

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

1294 tab->page.code = previous_code;

1295 goto request_error;

1296 }

1297 data_buf = malloc(recv+1);

1298 if (!data_buf) return fatalI();

1299 memcpy(data_buf, buf, recv);

1300 now = time(0);

1301 while (1) {

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

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

1304 "Server %!s(MISSING) stopped responding", gmi_host);

1305 goto request_error;

1306 }

1307 int bytes = tls_read(ctx, buf, sizeof(buf));

1308 if (bytes == 0) break;

1309 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) continue;

1310 if (bytes < 1) {

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

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

1313 gmi_host, tls_error(ctx));

1314 goto request_error;

1315 }

1316 data_buf = realloc(data_buf, recv+bytes+1);

1317 memcpy(&data_buf[recv], buf, bytes);

1318 recv += bytes;

1319 now = time(0);

1320 }

1321 exit:

1322 if (!download && recv > 0 && tab->page.code == 20 &&

1323 (!tab->history || (tab->history && !tab->history->next))) {

1324 gmi_cleanforward(tab);

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

1326 if (!link) return fatalI();

1327 link->next = NULL;

1328 link->prev = tab->history;

1329 link->scroll = tab->scroll;

1330 strlcpy(link->url, url_buf, sizeof(link->url));

1331 tab->history = link;

1332 if (link->prev)

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

1334 }

1335 if (0) {

1336 request_error_msg:;

1337 char* cr = strchr(ptr+1, '\r');

1338 if (cr) {

1339 *cr = '\0';

1340 char buf[256];

1341 snprintf(buf, sizeof(buf),

1342 "%!s(MISSING) (%!d(MISSING) : %!s(MISSING))", ptr+1, tab->page.code, tab->error);

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

1344 *cr = '\r';

1345 }

1346 request_error:

1347 free(data_buf);

1348 if (tab->history) {

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

1350 } else {

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

1352 }

1353 client.input.error = 1;

1354 recv = -1;

1355 }

1356 exit_download:

1357 if (sockfd != -1)

1358 close(sockfd);

1359 if (ctx) {

1360 tls_close(ctx);

1361 tls_free(ctx);

1362 ctx = NULL;

1363 }

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

1365 free(data_buf);

1366 strlcpy(tab->url, url_buf, sizeof(tab->url));

1367 return tab->page.data_len;

1368 }

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

1370 char* ptr = strchr(data_buf, ' ');

1371 if (!ptr) {

1372 free(data_buf);

1373 return -1;

1374 }

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

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

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

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

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

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

1381 strlcpy(tab->url, url_buf, sizeof(tab->url));

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

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

1384 free(data_buf);

1385 return tab->page.data_len;

1386 }

1387 tab->page.data_len = r;

1388 free(data_buf);

1389 data_buf = NULL;

1390 gmi_load(&tab->page);

1391 tab->scroll = -1;

1392 return r;

1393 }

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

1395 strlcpy(tab->page.meta, meta_buf, sizeof(tab->page.meta));

1396 strlcpy(tab->url, url_buf, sizeof(tab->url));

1397 tab->page.data_len = recv;

1398 free(tab->page.data); // should be cached

1399 tab->page.data = data_buf;

1400 gmi_load(&tab->page);

1401 }

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

1403 int len = strnlen(url_buf, sizeof(url_buf));

1404 if (url_buf[len-1] == '/')

1405 url_buf[len-1] = '\0';

1406 char* ptr = strrchr(url_buf, '/');

1407 FILE* f;

1408 char path[1024];

1409 int plen = getdownloadfolder(path, sizeof(path));

1410 if (plen < 0) plen = 0;

1411 else {

1412 path[plen] = '/';

1413 plen++;

1414 }

1415 if (ptr)

1416 strlcpy(&path[plen], ptr+1, sizeof(path)-plen);

1417 else

1418 strlcpy(&path[plen], "output.dat", sizeof(path)-plen);

1419 f = fopen(path, "wb");

1420 if (!f) {

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

1422 "Failed to write the downloaded file");

1423 client.input.error = 1;

1424 } else {

1425 char* ptr = strchr(data_buf, '\n')+1;

1426 if (ptr) {

1427 fwrite(ptr, 1, recv - (ptr-data_buf), f);

1428 fclose(f);

1429 #ifndef DISABLE_XDG

1430 if (client.xdg && display_open(path)) {

1431 char buf[1048];

1432 snprintf(buf, sizeof(buf), "xdg-open %!s(MISSING)", path);

1433 system(buf);

1434 }

1435 #endif

1436 client.input.info = 1;

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

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

1439

1440 } else {

1441 client.input.error = 1;

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

1443 "Server sent invalid data");

1444 }

1445 }

1446 free(data_buf);

1447 recv = tab->page.data_len;

1448 }

1449 return recv;

1450 }

1451

1452 int gmi_loadfile(char* path) {

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

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

1455 if (!f) {

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

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

1458 client.input.error = 1;

1459 return -1;

1460 }

1461 fseek(f, 0, SEEK_END);

1462 size_t len = ftell(f);

1463 fseek(f, 0, SEEK_SET);

1464 char* data = malloc(len+1);

1465 if (!data) return fatalI();

1466 data[0] = '\n';

1467 if (len != fread(&data[1], 1, len, f)) {

1468 fclose(f);

1469 free(data);

1470 return -1;

1471 }

1472 fclose(f);

1473 tab->page.code = 20;

1474 tab->page.data_len = len;

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

1476 tab->page.data = data;

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

1478 return len;

1479 }

1480

1481 int gmi_init() {

1482 if (tls_init()) {

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

1484 return -1;

1485 }

1486

1487 config = tls_config_new();

1488 if (!config) {

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

1490 return -1;

1491 }

1492

1493 config_empty = tls_config_new();

1494 if (!config_empty) {

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

1496 return -1;

1497 }

1498

1499 tls_config_insecure_noverifycert(config);

1500 tls_config_insecure_noverifycert(config_empty);

1501 ctx = NULL;

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

1503

1504 #ifndef DISABLE_XDG

1505 if (!system("which xdg-open > /dev/null 2>&1"))

1506 client.xdg = 1;

1507 #endif

1508

1509 if (gmi_loadbookmarks()) {

1510 gmi_newbookmarks();

1511 }

1512

1513 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

1514 bzero(&conn, sizeof(conn));

1515 if (pthread_cond_init(&conn.cond, NULL)) {

1516 printf("Failed to initialize pthread condition\n");

1517 return -1;

1518 }

1519 if (pthread_create(&conn.tid, NULL, (void *(*)(void *))conn_thread, NULL)) {

1520 printf("Failed to initialize connection thread\n");

1521 return -1;

1522 }

1523 bzero(&dnsquery, sizeof(dnsquery));

1524 if (pthread_cond_init(&dnsquery.cond, NULL)) {

1525 printf("Failed to initialize thread condition\n");

1526 return -1;

1527 }

1528 if (pthread_create(&dnsquery.tid, NULL, (void *(*)(void *))dnsquery_thread, NULL)) {

1529 printf("Failed to initialize dns query thread\n");

1530 return -1;

1531 }

1532 signal(SIGUSR1, signal_cb);

1533 #endif

1534

1535 scm_init();

1536

1537 return 0;

1538 }

1539

1540 void gmi_free() {

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

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

1543 gmi_savebookmarks();

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

1545 free(client.bookmarks[i]);

1546 free(client.bookmarks);

1547 free(client.tabs);

1548 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)

1549 pthread_kill(conn.tid, SIGUSR1);

1550 pthread_join(conn.tid, NULL);

1551 pthread_kill(dnsquery.tid, SIGUSR1);

1552 pthread_join(dnsquery.tid, NULL);

1553 #endif

1554 client.shutdown = 1;

1555 scm_free();

1556 }

1557

1558