cgo.c (24653B)
1 /* 2 * cgo - a simple terminal based gopher client 3 * Copyright (c) 2019 Sebastian Steinhauer <s.steinhauer@yahoo.de> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #include <sys/types.h> 18 #include <sys/stat.h> 19 #include <sys/wait.h> 20 #include <sys/socket.h> 21 #include <netdb.h> 22 #include <fcntl.h> 23 #include <unistd.h> 24 #include <errno.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <ctype.h> 29 30 /* some "configuration" */ 31 #define START_URI "gopher://gopher.floodgap.com:70" 32 #define CMD_TEXT "less" 33 #define CMD_IMAGE "display" 34 #define CMD_BROWSER "firefox" 35 #define CMD_PLAYER "mplayer" 36 #define CMD_TELNET "telnet" 37 #define COLOR_PROMPT "1;34" 38 #define COLOR_SELECTOR "1;32" 39 #define HEAD_CHECK_LEN 5 40 #define GLOBAL_CONFIG_FILE "/etc/cgorc" 41 #define LOCAL_CONFIG_FILE "/.cgorc" 42 #define NUM_BOOKMARKS 20 43 #define VERBOSE "true" 44 45 /* some internal defines */ 46 #define KEY_RANGE (('z' - 'a') + 1) 47 48 /* structs */ 49 typedef struct link_s link_t; 50 struct link_s { 51 link_t *next; 52 char which; 53 short key; 54 char *host; 55 char *port; 56 char *selector; 57 }; 58 59 typedef struct config_s config_t; 60 struct config_s { 61 char start_uri[512]; 62 char cmd_text[512]; 63 char cmd_image[512]; 64 char cmd_browser[512]; 65 char cmd_player[512]; 66 char color_prompt[512]; 67 char color_selector[512]; 68 char verbose[512]; 69 }; 70 71 char tmpfilename[256]; 72 link_t *links = NULL; 73 link_t *history = NULL; 74 int link_key; 75 char current_host[512], current_port[64], current_selector[1024]; 76 char parsed_host[512], parsed_port[64], parsed_selector[1024]; 77 char bookmarks[NUM_BOOKMARKS][512]; 78 config_t config; 79 80 /* function prototypes */ 81 int parse_uri(const char *uri); 82 83 /* implementation */ 84 void usage() 85 { 86 fputs("usage: cgo [-v] [-H] [gopher URI]\n", 87 stderr); 88 exit(EXIT_SUCCESS); 89 } 90 91 void banner(FILE *f) 92 { 93 fputs("cgo 0.6.0 Copyright (c) 2019 Sebastian Steinhauer\n", f); 94 } 95 96 int check_option_true(const char *option) 97 { 98 return strcasecmp(option, "false") && strcasecmp(option, "off"); 99 } 100 101 void parse_config_line(const char *line) 102 { 103 char token[1024]; 104 char bkey[128]; 105 char *value = NULL; 106 int i, j; 107 108 while (*line == ' ' || *line == '\t') line++; 109 for (i = 0; *line && *line != ' ' && *line != '\t'; line++) 110 if (i < sizeof(token) - 1) token[i++] = *line; 111 token[i] = 0; 112 113 if (! strcmp(token, "start_uri")) value = &config.start_uri[0]; 114 else if (! strcmp(token, "cmd_text")) value = &config.cmd_text[0]; 115 else if (! strcmp(token, "cmd_browser")) value = &config.cmd_browser[0]; 116 else if (! strcmp(token, "cmd_image")) value = &config.cmd_image[0]; 117 else if (! strcmp(token, "cmd_player")) value = &config.cmd_player[0]; 118 else if (! strcmp(token, "color_prompt")) value = &config.color_prompt[0]; 119 else if (! strcmp(token, "color_selector")) value = &config.color_selector[0]; 120 else if (! strcmp(token, "verbose")) value = &config.verbose[0]; 121 else { 122 for (j = 0; j < NUM_BOOKMARKS; j++) { 123 snprintf(bkey, sizeof(bkey), "bookmark%d", j+1); 124 if (! strcmp(token, bkey)) { 125 value = &bookmarks[j][0]; 126 break; 127 } 128 } 129 if (! value) return; 130 }; 131 132 while (*line == ' ' || *line == '\t') line++; 133 for (i = 0; *line; line++) 134 if (i < 512-1) value[i++] = *line; 135 for (i--; i > 0 && (value[i] == ' ' || value[i] == '\t'); i--) ; 136 value[++i] = 0; 137 138 } 139 140 void load_config(const char *filename) 141 { 142 FILE *fp; 143 int ch, i; 144 char line[1024]; 145 146 fp = fopen(filename, "r"); 147 if (! fp) return; 148 149 memset(line, 0, sizeof(line)); 150 i = 0; 151 ch = fgetc(fp); 152 while (1) { 153 switch (ch) { 154 case '#': 155 while (ch != '\n' && ch != -1) 156 ch = fgetc(fp); 157 break; 158 case -1: 159 parse_config_line(line); 160 fclose(fp); 161 return; 162 case '\r': 163 ch = fgetc(fp); 164 break; 165 case '\n': 166 parse_config_line(line); 167 memset(line, 0, sizeof(line)); 168 i = 0; 169 ch = fgetc(fp); 170 break; 171 default: 172 if (i < sizeof(line) - 1) 173 line[i++] = ch; 174 ch = fgetc(fp); 175 break; 176 } 177 } 178 } 179 180 void init_config() 181 { 182 char filename[1024]; 183 const char *home; 184 int i; 185 186 /* copy defaults */ 187 snprintf(config.start_uri, sizeof(config.start_uri), START_URI); 188 snprintf(config.cmd_text, sizeof(config.cmd_text), "%s", CMD_TEXT); 189 snprintf(config.cmd_image, sizeof(config.cmd_image), "%s", CMD_IMAGE); 190 snprintf(config.cmd_browser, sizeof(config.cmd_browser), "%s", CMD_BROWSER); 191 snprintf(config.cmd_player, sizeof(config.cmd_player), "%s", CMD_PLAYER); 192 snprintf(config.color_prompt, sizeof(config.color_prompt), "%s", COLOR_PROMPT); 193 snprintf(config.color_selector, sizeof(config.color_selector), "%s", COLOR_SELECTOR); 194 snprintf(config.verbose, sizeof(config.verbose), "%s", VERBOSE); 195 for (i = 0; i < NUM_BOOKMARKS; i++) bookmarks[i][0] = 0; 196 /* read configs */ 197 load_config(GLOBAL_CONFIG_FILE); 198 home = getenv("HOME"); 199 if (home) { 200 snprintf(filename, sizeof(filename), "%s%s", home, LOCAL_CONFIG_FILE); 201 load_config(filename); 202 } 203 } 204 205 int dial(const char *host, const char *port, const char *selector) 206 { 207 struct addrinfo hints; 208 struct addrinfo *res, *r; 209 int srv = -1, l; 210 char request[512]; 211 212 memset(&hints, 0, sizeof(hints)); 213 hints.ai_family = AF_UNSPEC; 214 hints.ai_socktype = SOCK_STREAM; 215 if (getaddrinfo(host, port, &hints, &res) != 0) { 216 fprintf(stderr, "error: cannot resolve hostname '%s:%s': %s\n", 217 host, port, strerror(errno)); 218 return -1; 219 } 220 for (r = res; r; r = r->ai_next) { 221 srv = socket(r->ai_family, r->ai_socktype, r->ai_protocol); 222 if (srv == -1) 223 continue; 224 if (connect(srv, r->ai_addr, r->ai_addrlen) == 0) 225 break; 226 close(srv); 227 } 228 freeaddrinfo(res); 229 if (! r) { 230 fprintf(stderr, "error: cannot connect to host '%s:%s'\n", 231 host, port); 232 return -1; 233 } 234 snprintf(request, sizeof(request), "%s\r\n", selector); 235 l = strlen(request); 236 if (write(srv, request, l) != l) { 237 fprintf(stderr, "error: cannot complete request\n"); 238 close(srv); 239 return -1; 240 } 241 return srv; 242 } 243 244 int read_line(int fd, char *buf, size_t buf_len) 245 { 246 size_t i = 0; 247 char c = 0; 248 249 do { 250 if (read(fd, &c, sizeof(char)) != sizeof(char)) 251 return 0; 252 if (c != '\r') 253 buf[i++] = c; 254 } while (c != '\n' && i < buf_len); 255 buf[i - 1] = '\0'; 256 return 1; 257 } 258 259 int download_file(const char *host, const char *port, 260 const char *selector, int fd) 261 { 262 int srvfd, len; 263 unsigned long total = 0; 264 char buffer[4096]; 265 266 if (check_option_true(config.verbose)) 267 printf("downloading [%s]...\r", selector); 268 srvfd = dial(host, port, selector); 269 if (srvfd == -1) { 270 printf("\033[2Kerror: downloading [%s] failed\n", selector); 271 close(fd); 272 return 0; 273 } 274 while ((len = read(srvfd, buffer, sizeof(buffer))) > 0) { 275 write(fd, buffer, len); 276 total += len; 277 if (check_option_true(config.verbose)) 278 printf("downloading [%s] (%ld kb)...\r", selector, total / 1024); 279 } 280 close(fd); 281 close(srvfd); 282 if (check_option_true(config.verbose)) 283 printf("\033[2Kdownloading [%s] complete\n", selector); 284 return 1; 285 } 286 287 int download_temp(const char *host, const char *port, const char *selector) 288 { 289 int tmpfd; 290 291 #if defined(__OpenBSD__) 292 strlcpy(tmpfilename, "/tmp/cgoXXXXXX", sizeof(tmpfilename)); 293 #else 294 strcpy(tmpfilename, "/tmp/cgoXXXXXX"); 295 #endif 296 tmpfd = mkstemp(tmpfilename); 297 if (tmpfd == -1) { 298 fputs("error: unable to create tmp file\n", stderr); 299 return 0; 300 } 301 if (! download_file(host, port, selector, tmpfd)) { 302 unlink(tmpfilename); 303 return 0; 304 } 305 return 1; 306 } 307 308 int make_key(char c1, char c2, char c3) 309 { 310 if (! c1 || ! c2) 311 return -1; 312 313 if (! c3) 314 return ((c1 - 'a') * KEY_RANGE) + (c2 - 'a'); 315 else 316 return (((c1 - 'a' + 1) * KEY_RANGE * KEY_RANGE) + ((c2 - 'a') * KEY_RANGE) + (c3 - 'a')); 317 } 318 319 void make_key_str(int key, char *c1, char *c2, char *c3) { 320 if (key < (KEY_RANGE * KEY_RANGE)) { 321 *c1 = 'a' + (key / KEY_RANGE); 322 *c2 = 'a' + (key % KEY_RANGE); 323 *c3 = 0; 324 } else { 325 *c1 = 'a' + (key / (KEY_RANGE * KEY_RANGE)) - 1; 326 *c2 = 'a' + ((key / KEY_RANGE) % KEY_RANGE); 327 *c3 = 'a' + (key % KEY_RANGE); 328 } 329 } 330 331 void add_link(char which, const char *name, 332 const char *host, const char *port, const char *selector) 333 { 334 link_t *link; 335 char a = 0, b = 0, c = 0; 336 337 if (! host || ! port || ! selector) 338 return; /* ignore incomplete selectors */ 339 link = calloc(1, sizeof(link_t)); 340 link->which = which; 341 link->key = link_key; 342 link->host = strdup(host); 343 link->port = strdup(port); 344 link->selector = strdup(selector); 345 if (! links) 346 link->next = NULL; 347 else 348 link->next = links; 349 links = link; 350 351 make_key_str(link_key++, &a, &b, &c); 352 printf("\033[%sm%c%c%c\033[0m \033[1m%s\033[0m\n", 353 config.color_selector, a, b, c, name); 354 } 355 356 void clear_links() 357 { 358 link_t *link, *next; 359 360 for (link = links; link; ) { 361 next = link->next; 362 free(link->host); 363 free(link->port); 364 free(link->selector); 365 free(link); 366 link = next; 367 } 368 links = NULL; 369 link_key = 0; 370 } 371 372 void add_history() 373 { 374 link_t *link; 375 376 link = calloc(1, sizeof(link_t)); 377 link->host = strdup(current_host); 378 link->port = strdup(current_port); 379 link->selector = strdup(current_selector); 380 link->which = 0; /* not needed for history...just clear them */ 381 link->key = 0; 382 if (! history) 383 link->next = NULL; 384 else 385 link->next = history; 386 history = link; 387 } 388 389 void handle_directory_line(char *line) 390 { 391 int i; 392 char *lp, *last, *fields[4]; 393 394 /* tokenize */ 395 for (i = 0; i < 4; i++) 396 fields[i] = NULL; 397 last = &line[1]; 398 for (lp = last, i = 0; i < 4; lp++) { 399 if (*lp == '\t' || *lp == '\0') { 400 fields[i] = last; 401 last = lp + 1; 402 if (*lp == '\0') 403 break; 404 *lp = '\0'; 405 i++; 406 } 407 } 408 /* determine listing type */ 409 switch (line[0]) { 410 case 'i': 411 case '3': 412 printf(" %s\n", fields[0]); 413 break; 414 case '.': /* some gopher servers use this */ 415 puts(""); 416 break; 417 case '0': 418 case '1': 419 case '5': 420 case '7': 421 case '8': 422 case '9': 423 case 'g': 424 case 'I': 425 case 'p': 426 case 'h': 427 case 's': 428 add_link(line[0], fields[0], fields[2], fields[3], fields[1]); 429 break; 430 default: 431 printf("miss [%c]: %s\n", line[0], fields[0]); 432 break; 433 } 434 } 435 436 int is_valid_directory_entry(const char *line) 437 { 438 switch (line[0]) { 439 case 'i': 440 case '3': 441 case '.': /* some gopher servers use this */ 442 case '0': 443 case '1': 444 case '5': 445 case '7': 446 case '8': 447 case '9': 448 case 'g': 449 case 'I': 450 case 'p': 451 case 'h': 452 case 's': 453 return 1; 454 default: 455 return 0; 456 } 457 } 458 459 void view_directory(const char *host, const char *port, 460 const char *selector, int make_current) 461 { 462 int is_dir; 463 int srvfd, i, head_read; 464 char line[1024]; 465 char head[HEAD_CHECK_LEN][1024]; 466 467 srvfd = dial(host, port, selector); 468 if (srvfd != -1) { /* only adapt current prompt when successful */ 469 /* make history entry */ 470 if (make_current) 471 add_history(); 472 /* don't overwrite the current_* things... */ 473 if (host != current_host) 474 snprintf(current_host, sizeof(current_host), "%s", host); 475 if (port != current_port) 476 snprintf(current_port, sizeof(current_port), "%s", port); 477 if (selector != current_selector) 478 snprintf(current_selector, sizeof(current_selector), 479 "%s", selector); 480 } 481 clear_links(); /* clear links *AFTER* dialing out!! */ 482 if (srvfd == -1) 483 return; /* quit if not successful */ 484 head_read = 0; 485 is_dir = 1; 486 while (head_read < HEAD_CHECK_LEN && read_line(srvfd, line, sizeof(line))) { 487 strcpy(head[head_read], line); 488 if (!is_valid_directory_entry(head[head_read])) { 489 is_dir = 0; 490 break; 491 } 492 head_read++; 493 } 494 if (!is_dir) { 495 puts("error: Not a directory."); 496 close(srvfd); 497 return; 498 } 499 for (i = 0; i < head_read; i++) { 500 handle_directory_line(head[i]); 501 } 502 while (read_line(srvfd, line, sizeof(line))) { 503 handle_directory_line(line); 504 } 505 close(srvfd); 506 } 507 508 void view_file(const char *cmd, const char *host, 509 const char *port, const char *selector) 510 { 511 pid_t pid; 512 int status, i, j; 513 char buffer[1024], *argv[32], *p; 514 515 if (check_option_true(config.verbose)) 516 printf("h(%s) p(%s) s(%s)\n", host, port, selector); 517 518 if (! download_temp(host, port, selector)) 519 return; 520 521 /* parsed command line string */ 522 argv[0] = &buffer[0]; 523 for (p = (char*) cmd, i = 0, j = 1; *p && i < sizeof(buffer) - 1 && j < 30; ) { 524 if (*p == ' ' || *p == '\t') { 525 buffer[i++] = 0; 526 argv[j++] = &buffer[i]; 527 while (*p == ' ' || *p == '\t') p++; 528 } else buffer[i++] = *p++; 529 } 530 buffer[i] = 0; 531 argv[j++] = tmpfilename; 532 argv[j] = NULL; 533 534 /* fork and execute */ 535 if (check_option_true(config.verbose)) 536 printf("executing: %s %s\n", cmd, tmpfilename); 537 pid = fork(); 538 if (pid == 0) { 539 if (execvp(argv[0], argv) == -1) 540 puts("error: execvp() failed!"); 541 } else if (pid == -1) puts("error: fork() failed"); 542 sleep(1); /* to wait for browsers etc. that return immediatly */ 543 waitpid(pid, &status, 0); 544 unlink(tmpfilename); 545 } 546 547 void view_telnet(const char *host, const char *port) 548 { 549 pid_t pid; 550 int status; 551 552 printf("executing: %s %s %s\n", CMD_TELNET, host, port); 553 pid = fork(); 554 if (pid == 0) { 555 if (execlp(CMD_TELNET, CMD_TELNET, host, port, NULL) == -1) 556 puts("error: execlp() failed!"); 557 } else if (pid == -1) puts("error: fork() failed!"); 558 waitpid(pid, &status, 0); 559 puts("(done)"); 560 } 561 562 void view_download(const char *host, const char *port, const char *selector) 563 { 564 int fd; 565 char filename[1024], line[1024]; 566 567 snprintf(filename, sizeof(filename), "%s", strrchr(selector, '/') + 1); 568 printf("enter filename for download [%s]: ", filename); 569 fflush(stdout); 570 if (! read_line(0, line, sizeof(line))) { 571 puts("download aborted"); 572 return; 573 } 574 if (strlen(line) > 0) 575 #if defined(__OpenBSD__) 576 strlcpy(filename, line, sizeof(filename)); 577 #else 578 strcpy(filename, line); 579 #endif 580 fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); 581 if (fd == -1) { 582 printf("error: unable to create file [%s]: %s\n", 583 filename, strerror(errno)); 584 return; 585 } 586 if (! download_file(host, port, selector, fd)) { 587 printf("error: unable to download [%s]\n", selector); 588 unlink(filename); 589 return; 590 } 591 } 592 593 void view_search(const char *host, const char *port, const char *selector) 594 { 595 char search_selector[1024]; 596 char line[1024]; 597 598 printf("enter search string: "); 599 fflush(stdout); 600 if (! read_line(0, line, sizeof(line))) { 601 puts("search aborted"); 602 return; 603 } 604 snprintf(search_selector, sizeof(search_selector), "%s\t%s", 605 selector, line); 606 view_directory(host, port, search_selector, 1); 607 } 608 609 void view_history(int key) 610 { 611 int history_key = 0; 612 char a, b, c; 613 link_t *link; 614 615 if (! history) { 616 puts("(empty history)"); 617 return; 618 } 619 if ( key < 0 ) { 620 puts("(history)"); 621 for ( link = history; link; link = link->next ) { 622 make_key_str(history_key++, &a, &b, &c); 623 printf("\033[%sm%c%c%c\033[0m \033[1m%s:%s%s\033[0m\n", 624 COLOR_SELECTOR, a, b, c, link->host, link->port, link->selector); 625 } 626 } else { 627 /* traverse history list */ 628 for ( link = history; link; link = link->next, ++history_key ) { 629 if ( history_key == key ) { 630 view_directory(link->host, link->port, link->selector, 0); 631 return; 632 } 633 } 634 puts("history item not found"); 635 } 636 } 637 638 void view_bookmarks(int key) 639 { 640 int i; 641 char a, b, c; 642 643 if (key < 0) { 644 puts("(bookmarks)"); 645 for (i = 0; i < NUM_BOOKMARKS; i++) { 646 if (bookmarks[i][0]) { 647 make_key_str(i, &a, &b, &c); 648 printf("\033[%sm%c%c%c\033[0m \033[1m%s\033[0m\n", 649 COLOR_SELECTOR, a, b, c, &bookmarks[i][0]); 650 } 651 } 652 } else { 653 for (i = 0; i < NUM_BOOKMARKS; i++) { 654 if (bookmarks[i][0] && i == key) { 655 if (parse_uri(&bookmarks[i][0])) view_directory(parsed_host, parsed_port, parsed_selector, 0); 656 else printf("invalid gopher URI: %s", &bookmarks[i][0]); 657 return; 658 } 659 } 660 } 661 } 662 663 void pop_history() 664 { 665 link_t *next; 666 667 if (! history) { 668 puts("(empty history)"); 669 return; 670 } 671 /* reload page from history (and don't count as history) */ 672 view_directory(history->host, history->port, history->selector, 0); 673 /* history is history... :) */ 674 next = history->next; 675 free(history->host); 676 free(history->port); 677 free(history->selector); 678 free(history); 679 history = next; 680 } 681 682 int follow_link(int key) 683 { 684 link_t *link; 685 686 for (link = links; link; link = link->next) { 687 if (link->key != key) 688 continue; 689 switch (link->which) { 690 case '0': 691 view_file(&config.cmd_text[0], link->host, link->port, link->selector); 692 break; 693 case '1': 694 view_directory(link->host, link->port, link->selector, 1); 695 break; 696 case '7': 697 view_search(link->host, link->port, link->selector); 698 break; 699 case '5': 700 case '9': 701 view_download(link->host, link->port, link->selector); 702 break; 703 case '8': 704 view_telnet(link->host, link->port); 705 break; 706 case 'g': 707 case 'I': 708 case 'p': 709 view_file(&config.cmd_image[0], link->host, link->port, link->selector); 710 break; 711 case 'h': 712 view_file(&config.cmd_browser[0], link->host, link->port, link->selector); 713 break; 714 case 's': 715 view_file(&config.cmd_player[0], link->host, link->port, link->selector); 716 break; 717 default: 718 printf("missing handler [%c]\n", link->which); 719 break; 720 } 721 return 1; /* return the array is broken after view! */ 722 } 723 return 0; 724 } 725 726 void download_link(int key) 727 { 728 link_t *link; 729 730 for (link = links; link; link = link->next) { 731 if (link->key != key) 732 continue; 733 view_download(link->host, link->port, link->selector); 734 return; 735 } 736 puts("link not found"); 737 } 738 739 int parse_uri(const char *uri) 740 { 741 int i; 742 743 /* strip gopher:// */ 744 if (! strncmp(uri, "gopher://", 9)) 745 uri += 9; 746 /* parse host */ 747 for (i = 0; *uri && *uri != ':' && *uri != '/'; uri++) { 748 if (*uri != ' ' && i < sizeof(parsed_host) - 1) 749 parsed_host[i++] = *uri; 750 } 751 if (i > 0) parsed_host[i] = 0; 752 else return 0; 753 /* parse port */ 754 if (*uri == ':') { 755 uri++; 756 for (i = 0; *uri && *uri != '/'; uri++) 757 if (*uri != ' ' && i < sizeof(parsed_port) - 1) 758 parsed_port[i++] = *uri; 759 parsed_port[i] = 0; 760 } else snprintf(parsed_port, sizeof(parsed_port), "%d", 70); 761 /* parse selector */ 762 if (*uri == '/') { 763 for (i = 0, ++uri; *uri; ++uri) 764 if (i < sizeof(parsed_selector) - 1) 765 parsed_selector[i++] = *uri; 766 parsed_selector[i++] = *uri; 767 } else snprintf(parsed_selector, sizeof(parsed_selector), "%s", ""); 768 769 return 1; 770 } 771 772 int main(int argc, char *argv[]) 773 { 774 int i; 775 char line[1024], *uri; 776 777 /* copy defaults */ 778 init_config(); 779 uri = &config.start_uri[0]; 780 781 /* parse command line */ 782 for (i = 1; i < argc; i++) { 783 if (argv[i][0] == '-') switch(argv[i][1]) { 784 case 'H': 785 usage(); 786 break; 787 case 'v': 788 banner(stdout); 789 exit(EXIT_SUCCESS); 790 default: 791 usage(); 792 } else { 793 uri = argv[i]; 794 } 795 } 796 797 /* parse uri */ 798 if (! parse_uri(uri)) { 799 banner(stderr); 800 fprintf(stderr, "invalid gopher URI: %s", argv[i]); 801 exit(EXIT_FAILURE); 802 } 803 804 /* main loop */ 805 view_directory(parsed_host, parsed_port, parsed_selector, 0); 806 for (;;) { 807 printf("\033[%sm%s:%s%s\033[0m ", config.color_prompt, 808 current_host, current_port, current_selector); 809 fflush(stdout); /* to display the prompt */ 810 if (! read_line(0, line, sizeof(line))) { 811 puts("QUIT"); 812 return EXIT_SUCCESS; 813 } 814 i = strlen(line); 815 switch (line[0]) { 816 case '?': 817 puts( 818 "? - help\n" 819 "* - reload directory\n" 820 "< - go back in history\n" 821 ".[LINK] - download the given link\n" 822 "H - show history\n" 823 "H[LINK] - jump to the specified history item\n" 824 "G[URI] - jump to the given gopher URI\n" 825 "B - show bookmarks\n" 826 "B[LINK] - jump to the specified bookmark item\n" 827 "C^d - quit"); 828 break; 829 case '<': 830 pop_history(); 831 break; 832 case '*': 833 view_directory(current_host, current_port, 834 current_selector, 0); 835 break; 836 case '.': 837 download_link(make_key(line[1], line[2], line[3])); 838 break; 839 case 'H': 840 if (i == 1 || i == 3 || i == 4) view_history(make_key(line[1], line[2], line[3])); 841 break; 842 case 'G': 843 if (parse_uri(&line[1])) view_directory(parsed_host, parsed_port, parsed_selector, 1); 844 else puts("invalid gopher URI"); 845 break; 846 case 'B': 847 if (i == 1 || i == 3 || i == 4) view_bookmarks(make_key(line[1], line[2], line[3])); 848 break; 849 default: 850 follow_link(make_key(line[0], line[1], line[2])); 851 break; 852 } 853 } 854 return EXIT_SUCCESS; /* never get's here but stops cc complaining */ 855 }