|
|
|
@ -0,0 +1,854 @@
|
|
|
|
|
/*
|
|
|
|
|
* cgo - a simple terminal based gopher client
|
|
|
|
|
* Copyright (c) 2019 Sebastian Steinhauer <s.steinhauer@yahoo.de>
|
|
|
|
|
*
|
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <netdb.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
|
|
/* some "configuration" */
|
|
|
|
|
#define START_URI "gopher://gopher.floodgap.com:70"
|
|
|
|
|
#define CMD_TEXT "less"
|
|
|
|
|
#define CMD_IMAGE "display"
|
|
|
|
|
#define CMD_BROWSER "firefox"
|
|
|
|
|
#define CMD_PLAYER "mplayer"
|
|
|
|
|
#define CMD_TELNET "telnet"
|
|
|
|
|
#define COLOR_PROMPT "1;34"
|
|
|
|
|
#define COLOR_SELECTOR "1;32"
|
|
|
|
|
#define HEAD_CHECK_LEN 5
|
|
|
|
|
#define GLOBAL_CONFIG_FILE "/etc/cgorc"
|
|
|
|
|
#define LOCAL_CONFIG_FILE "/.cgorc"
|
|
|
|
|
#define NUM_BOOKMARKS 20
|
|
|
|
|
#define VERBOSE "true"
|
|
|
|
|
|
|
|
|
|
/* some internal defines */
|
|
|
|
|
#define KEY_RANGE (('z' - 'a') + 1)
|
|
|
|
|
|
|
|
|
|
/* structs */
|
|
|
|
|
typedef struct link_s link_t;
|
|
|
|
|
struct link_s {
|
|
|
|
|
link_t *next;
|
|
|
|
|
char which;
|
|
|
|
|
short key;
|
|
|
|
|
char *host;
|
|
|
|
|
char *port;
|
|
|
|
|
char *selector;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef struct config_s config_t;
|
|
|
|
|
struct config_s {
|
|
|
|
|
char start_uri[512];
|
|
|
|
|
char cmd_text[512];
|
|
|
|
|
char cmd_image[512];
|
|
|
|
|
char cmd_browser[512];
|
|
|
|
|
char cmd_player[512];
|
|
|
|
|
char color_prompt[512];
|
|
|
|
|
char color_selector[512];
|
|
|
|
|
char verbose[512];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
char tmpfilename[256];
|
|
|
|
|
link_t *links = NULL;
|
|
|
|
|
link_t *history = NULL;
|
|
|
|
|
int link_key;
|
|
|
|
|
char current_host[512], current_port[64], current_selector[1024];
|
|
|
|
|
char parsed_host[512], parsed_port[64], parsed_selector[1024];
|
|
|
|
|
char bookmarks[NUM_BOOKMARKS][512];
|
|
|
|
|
config_t config;
|
|
|
|
|
|
|
|
|
|
/* function prototypes */
|
|
|
|
|
int parse_uri(const char *uri);
|
|
|
|
|
|
|
|
|
|
/* implementation */
|
|
|
|
|
void usage()
|
|
|
|
|
{
|
|
|
|
|
fputs("usage: cgo [-v] [-H] [gopher URI]\n",
|
|
|
|
|
stderr);
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void banner(FILE *f)
|
|
|
|
|
{
|
|
|
|
|
fputs("cgo 0.6.1 Copyright (c) 2020 Sebastian Steinhauer\n", f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int check_option_true(const char *option)
|
|
|
|
|
{
|
|
|
|
|
return strcasecmp(option, "false") && strcasecmp(option, "off");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void parse_config_line(const char *line)
|
|
|
|
|
{
|
|
|
|
|
char token[1024];
|
|
|
|
|
char bkey[128];
|
|
|
|
|
char *value = NULL;
|
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
|
|
while (*line == ' ' || *line == '\t') line++;
|
|
|
|
|
for (i = 0; *line && *line != ' ' && *line != '\t'; line++)
|
|
|
|
|
if (i < sizeof(token) - 1) token[i++] = *line;
|
|
|
|
|
token[i] = 0;
|
|
|
|
|
|
|
|
|
|
if (! strcmp(token, "start_uri")) value = &config.start_uri[0];
|
|
|
|
|
else if (! strcmp(token, "cmd_text")) value = &config.cmd_text[0];
|
|
|
|
|
else if (! strcmp(token, "cmd_browser")) value = &config.cmd_browser[0];
|
|
|
|
|
else if (! strcmp(token, "cmd_image")) value = &config.cmd_image[0];
|
|
|
|
|
else if (! strcmp(token, "cmd_player")) value = &config.cmd_player[0];
|
|
|
|
|
else if (! strcmp(token, "color_prompt")) value = &config.color_prompt[0];
|
|
|
|
|
else if (! strcmp(token, "color_selector")) value = &config.color_selector[0];
|
|
|
|
|
else if (! strcmp(token, "verbose")) value = &config.verbose[0];
|
|
|
|
|
else {
|
|
|
|
|
for (j = 0; j < NUM_BOOKMARKS; j++) {
|
|
|
|
|
snprintf(bkey, sizeof(bkey), "bookmark%d", j+1);
|
|
|
|
|
if (! strcmp(token, bkey)) {
|
|
|
|
|
value = &bookmarks[j][0];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (! value) return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
while (*line == ' ' || *line == '\t') line++;
|
|
|
|
|
for (i = 0; *line; line++)
|
|
|
|
|
if (i < 512-1) value[i++] = *line;
|
|
|
|
|
for (i--; i > 0 && (value[i] == ' ' || value[i] == '\t'); i--) ;
|
|
|
|
|
value[++i] = 0;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void load_config(const char *filename)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp;
|
|
|
|
|
int ch, i;
|
|
|
|
|
char line[1024];
|
|
|
|
|
|
|
|
|
|
fp = fopen(filename, "r");
|
|
|
|
|
if (! fp) return;
|
|
|
|
|
|
|
|
|
|
memset(line, 0, sizeof(line));
|
|
|
|
|
i = 0;
|
|
|
|
|
ch = fgetc(fp);
|
|
|
|
|
while (1) {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '#':
|
|
|
|
|
while (ch != '\n' && ch != -1)
|
|
|
|
|
ch = fgetc(fp);
|
|
|
|
|
break;
|
|
|
|
|
case -1:
|
|
|
|
|
parse_config_line(line);
|
|
|
|
|
fclose(fp);
|
|
|
|
|
return;
|
|
|
|
|
case '\r':
|
|
|
|
|
ch = fgetc(fp);
|
|
|
|
|
break;
|
|
|
|
|
case '\n':
|
|
|
|
|
parse_config_line(line);
|
|
|
|
|
memset(line, 0, sizeof(line));
|
|
|
|
|
i = 0;
|
|
|
|
|
ch = fgetc(fp);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (i < sizeof(line) - 1)
|
|
|
|
|
line[i++] = ch;
|
|
|
|
|
ch = fgetc(fp);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void init_config()
|
|
|
|
|
{
|
|
|
|
|
char filename[1024];
|
|
|
|
|
const char *home;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* copy defaults */
|
|
|
|
|
snprintf(config.start_uri, sizeof(config.start_uri), START_URI);
|
|
|
|
|
snprintf(config.cmd_text, sizeof(config.cmd_text), "%s", CMD_TEXT);
|
|
|
|
|
snprintf(config.cmd_image, sizeof(config.cmd_image), "%s", CMD_IMAGE);
|
|
|
|
|
snprintf(config.cmd_browser, sizeof(config.cmd_browser), "%s", CMD_BROWSER);
|
|
|
|
|
snprintf(config.cmd_player, sizeof(config.cmd_player), "%s", CMD_PLAYER);
|
|
|
|
|
snprintf(config.color_prompt, sizeof(config.color_prompt), "%s", COLOR_PROMPT);
|
|
|
|
|
snprintf(config.color_selector, sizeof(config.color_selector), "%s", COLOR_SELECTOR);
|
|
|
|
|
snprintf(config.verbose, sizeof(config.verbose), "%s", VERBOSE);
|
|
|
|
|
for (i = 0; i < NUM_BOOKMARKS; i++) bookmarks[i][0] = 0;
|
|
|
|
|
/* read configs */
|
|
|
|
|
load_config(GLOBAL_CONFIG_FILE);
|
|
|
|
|
home = getenv("HOME");
|
|
|
|
|
if (home) {
|
|
|
|
|
snprintf(filename, sizeof(filename), "%s%s", home, LOCAL_CONFIG_FILE);
|
|
|
|
|
load_config(filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int dial(const char *host, const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
struct addrinfo hints;
|
|
|
|
|
struct addrinfo *res, *r;
|
|
|
|
|
int srv = -1, l;
|
|
|
|
|
char request[512];
|
|
|
|
|
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
|
if (getaddrinfo(host, port, &hints, &res) != 0) {
|
|
|
|
|
fprintf(stderr, "error: cannot resolve hostname '%s:%s': %s\n",
|
|
|
|
|
host, port, strerror(errno));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
for (r = res; r; r = r->ai_next) {
|
|
|
|
|
srv = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
|
|
|
|
|
if (srv == -1)
|
|
|
|
|
continue;
|
|
|
|
|
if (connect(srv, r->ai_addr, r->ai_addrlen) == 0)
|
|
|
|
|
break;
|
|
|
|
|
close(srv);
|
|
|
|
|
}
|
|
|
|
|
freeaddrinfo(res);
|
|
|
|
|
if (! r) {
|
|
|
|
|
fprintf(stderr, "error: cannot connect to host '%s:%s'\n",
|
|
|
|
|
host, port);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
snprintf(request, sizeof(request), "%s\r\n", selector);
|
|
|
|
|
l = strlen(request);
|
|
|
|
|
if (write(srv, request, l) != l) {
|
|
|
|
|
fprintf(stderr, "error: cannot complete request\n");
|
|
|
|
|
close(srv);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return srv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int read_line(int fd, char *buf, size_t buf_len)
|
|
|
|
|
{
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
char c = 0;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
if (read(fd, &c, sizeof(char)) != sizeof(char))
|
|
|
|
|
return 0;
|
|
|
|
|
if (c != '\r')
|
|
|
|
|
buf[i++] = c;
|
|
|
|
|
} while (c != '\n' && i < buf_len);
|
|
|
|
|
buf[i - 1] = '\0';
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int download_file(const char *host, const char *port,
|
|
|
|
|
const char *selector, int fd)
|
|
|
|
|
{
|
|
|
|
|
int srvfd, len;
|
|
|
|
|
unsigned long total = 0;
|
|
|
|
|
char buffer[4096];
|
|
|
|
|
|
|
|
|
|
if (check_option_true(config.verbose))
|
|
|
|
|
printf("downloading [%s]...\r", selector);
|
|
|
|
|
srvfd = dial(host, port, selector);
|
|
|
|
|
if (srvfd == -1) {
|
|
|
|
|
printf("\033[2Kerror: downloading [%s] failed\n", selector);
|
|
|
|
|
close(fd);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
while ((len = read(srvfd, buffer, sizeof(buffer))) > 0) {
|
|
|
|
|
write(fd, buffer, len);
|
|
|
|
|
total += len;
|
|
|
|
|
if (check_option_true(config.verbose))
|
|
|
|
|
printf("downloading [%s] (%ld kb)...\r", selector, total / 1024);
|
|
|
|
|
}
|
|
|
|
|
close(fd);
|
|
|
|
|
close(srvfd);
|
|
|
|
|
if (check_option_true(config.verbose))
|
|
|
|
|
printf("\033[2Kdownloading [%s] complete\n", selector);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int download_temp(const char *host, const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
int tmpfd;
|
|
|
|
|
|
|
|
|
|
#if defined(__OpenBSD__)
|
|
|
|
|
strlcpy(tmpfilename, "/tmp/cgoXXXXXX", sizeof(tmpfilename));
|
|
|
|
|
#else
|
|
|
|
|
strcpy(tmpfilename, "/tmp/cgoXXXXXX");
|
|
|
|
|
#endif
|
|
|
|
|
tmpfd = mkstemp(tmpfilename);
|
|
|
|
|
if (tmpfd == -1) {
|
|
|
|
|
fputs("error: unable to create tmp file\n", stderr);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if (! download_file(host, port, selector, tmpfd)) {
|
|
|
|
|
unlink(tmpfilename);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int make_key(char c1, char c2, char c3)
|
|
|
|
|
{
|
|
|
|
|
if (! c1 || ! c2)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (! c3)
|
|
|
|
|
return ((c1 - 'a') * KEY_RANGE) + (c2 - 'a');
|
|
|
|
|
else
|
|
|
|
|
return (((c1 - 'a' + 1) * KEY_RANGE * KEY_RANGE) + ((c2 - 'a') * KEY_RANGE) + (c3 - 'a'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void make_key_str(int key, char *c1, char *c2, char *c3) {
|
|
|
|
|
if (key < (KEY_RANGE * KEY_RANGE)) {
|
|
|
|
|
*c1 = 'a' + (key / KEY_RANGE);
|
|
|
|
|
*c2 = 'a' + (key % KEY_RANGE);
|
|
|
|
|
*c3 = 0;
|
|
|
|
|
} else {
|
|
|
|
|
*c1 = 'a' + (key / (KEY_RANGE * KEY_RANGE)) - 1;
|
|
|
|
|
*c2 = 'a' + ((key / KEY_RANGE) % KEY_RANGE);
|
|
|
|
|
*c3 = 'a' + (key % KEY_RANGE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_link(char which, const char *name,
|
|
|
|
|
const char *host, const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
link_t *link;
|
|
|
|
|
char a = 0, b = 0, c = 0;
|
|
|
|
|
|
|
|
|
|
if (! host || ! port || ! selector)
|
|
|
|
|
return; /* ignore incomplete selectors */
|
|
|
|
|
link = calloc(1, sizeof(link_t));
|
|
|
|
|
link->which = which;
|
|
|
|
|
link->key = link_key;
|
|
|
|
|
link->host = strdup(host);
|
|
|
|
|
link->port = strdup(port);
|
|
|
|
|
link->selector = strdup(selector);
|
|
|
|
|
if (! links)
|
|
|
|
|
link->next = NULL;
|
|
|
|
|
else
|
|
|
|
|
link->next = links;
|
|
|
|
|
links = link;
|
|
|
|
|
|
|
|
|
|
make_key_str(link_key++, &a, &b, &c);
|
|
|
|
|
printf("\033[%sm%c%c%c\033[0m \033[1m%s\033[0m\n",
|
|
|
|
|
config.color_selector, a, b, c, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void clear_links()
|
|
|
|
|
{
|
|
|
|
|
link_t *link, *next;
|
|
|
|
|
|
|
|
|
|
for (link = links; link; ) {
|
|
|
|
|
next = link->next;
|
|
|
|
|
free(link->host);
|
|
|
|
|
free(link->port);
|
|
|
|
|
free(link->selector);
|
|
|
|
|
free(link);
|
|
|
|
|
link = next;
|
|
|
|
|
}
|
|
|
|
|
links = NULL;
|
|
|
|
|
link_key = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_history()
|
|
|
|
|
{
|
|
|
|
|
link_t *link;
|
|
|
|
|
|
|
|
|
|
link = calloc(1, sizeof(link_t));
|
|
|
|
|
link->host = strdup(current_host);
|
|
|
|
|
link->port = strdup(current_port);
|
|
|
|
|
link->selector = strdup(current_selector);
|
|
|
|
|
link->which = 0; /* not needed for history...just clear them */
|
|
|
|
|
link->key = 0;
|
|
|
|
|
if (! history)
|
|
|
|
|
link->next = NULL;
|
|
|
|
|
else
|
|
|
|
|
link->next = history;
|
|
|
|
|
history = link;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handle_directory_line(char *line)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
char *lp, *last, *fields[4];
|
|
|
|
|
|
|
|
|
|
/* tokenize */
|
|
|
|
|
for (i = 0; i < 4; i++)
|
|
|
|
|
fields[i] = NULL;
|
|
|
|
|
last = &line[1];
|
|
|
|
|
for (lp = last, i = 0; i < 4; lp++) {
|
|
|
|
|
if (*lp == '\t' || *lp == '\0') {
|
|
|
|
|
fields[i] = last;
|
|
|
|
|
last = lp + 1;
|
|
|
|
|
if (*lp == '\0')
|
|
|
|
|
break;
|
|
|
|
|
*lp = '\0';
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* determine listing type */
|
|
|
|
|
switch (line[0]) {
|
|
|
|
|
case 'i':
|
|
|
|
|
case '3':
|
|
|
|
|
printf(" %s\n", fields[0]);
|
|
|
|
|
break;
|
|
|
|
|
case '.': /* some gopher servers use this */
|
|
|
|
|
puts("");
|
|
|
|
|
break;
|
|
|
|
|
case '0':
|
|
|
|
|
case '1':
|
|
|
|
|
case '5':
|
|
|
|
|
case '7':
|
|
|
|
|
case '8':
|
|
|
|
|
case '9':
|
|
|
|
|
case 'g':
|
|
|
|
|
case 'I':
|
|
|
|
|
case 'p':
|
|
|
|
|
case 'h':
|
|
|
|
|
case 's':
|
|
|
|
|
add_link(line[0], fields[0], fields[2], fields[3], fields[1]);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
printf("miss [%c]: %s\n", line[0], fields[0]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int is_valid_directory_entry(const char *line)
|
|
|
|
|
{
|
|
|
|
|
switch (line[0]) {
|
|
|
|
|
case 'i':
|
|
|
|
|
case '3':
|
|
|
|
|
case '.': /* some gopher servers use this */
|
|
|
|
|
case '0':
|
|
|
|
|
case '1':
|
|
|
|
|
case '5':
|
|
|
|
|
case '7':
|
|
|
|
|
case '8':
|
|
|
|
|
case '9':
|
|
|
|
|
case 'g':
|
|
|
|
|
case 'I':
|
|
|
|
|
case 'p':
|
|
|
|
|
case 'h':
|
|
|
|
|
case 's':
|
|
|
|
|
return 1;
|
|
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_directory(const char *host, const char *port,
|
|
|
|
|
const char *selector, int make_current)
|
|
|
|
|
{
|
|
|
|
|
int is_dir;
|
|
|
|
|
int srvfd, i, head_read;
|
|
|
|
|
char line[1024];
|
|
|
|
|
char head[HEAD_CHECK_LEN][1024];
|
|
|
|
|
|
|
|
|
|
srvfd = dial(host, port, selector);
|
|
|
|
|
if (srvfd != -1) { /* only adapt current prompt when successful */
|
|
|
|
|
/* make history entry */
|
|
|
|
|
if (make_current)
|
|
|
|
|
add_history();
|
|
|
|
|
/* don't overwrite the current_* things... */
|
|
|
|
|
if (host != current_host)
|
|
|
|
|
snprintf(current_host, sizeof(current_host), "%s", host);
|
|
|
|
|
if (port != current_port)
|
|
|
|
|
snprintf(current_port, sizeof(current_port), "%s", port);
|
|
|
|
|
if (selector != current_selector)
|
|
|
|
|
snprintf(current_selector, sizeof(current_selector),
|
|
|
|
|
"%s", selector);
|
|
|
|
|
}
|
|
|
|
|
clear_links(); /* clear links *AFTER* dialing out!! */
|
|
|
|
|
if (srvfd == -1)
|
|
|
|
|
return; /* quit if not successful */
|
|
|
|
|
head_read = 0;
|
|
|
|
|
is_dir = 1;
|
|
|
|
|
while (head_read < HEAD_CHECK_LEN && read_line(srvfd, line, sizeof(line))) {
|
|
|
|
|
strcpy(head[head_read], line);
|
|
|
|
|
if (!is_valid_directory_entry(head[head_read])) {
|
|
|
|
|
is_dir = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
head_read++;
|
|
|
|
|
}
|
|
|
|
|
if (!is_dir) {
|
|
|
|
|
puts("error: Not a directory.");
|
|
|
|
|
close(srvfd);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < head_read; i++) {
|
|
|
|
|
handle_directory_line(head[i]);
|
|
|
|
|
}
|
|
|
|
|
while (read_line(srvfd, line, sizeof(line))) {
|
|
|
|
|
handle_directory_line(line);
|
|
|
|
|
}
|
|
|
|
|
close(srvfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_file(const char *cmd, const char *host,
|
|
|
|
|
const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
pid_t pid;
|
|
|
|
|
int status, i, j;
|
|
|
|
|
char buffer[1024], *argv[32], *p;
|
|
|
|
|
|
|
|
|
|
if (check_option_true(config.verbose))
|
|
|
|
|
printf("h(%s) p(%s) s(%s)\n", host, port, selector);
|
|
|
|
|
|
|
|
|
|
if (! download_temp(host, port, selector))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* parsed command line string */
|
|
|
|
|
argv[0] = &buffer[0];
|
|
|
|
|
for (p = (char*) cmd, i = 0, j = 1; *p && i < sizeof(buffer) - 1 && j < 30; ) {
|
|
|
|
|
if (*p == ' ' || *p == '\t') {
|
|
|
|
|
buffer[i++] = 0;
|
|
|
|
|
argv[j++] = &buffer[i];
|
|
|
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
|
|
|
} else buffer[i++] = *p++;
|
|
|
|
|
}
|
|
|
|
|
buffer[i] = 0;
|
|
|
|
|
argv[j++] = tmpfilename;
|
|
|
|
|
argv[j] = NULL;
|
|
|
|
|
|
|
|
|
|
/* fork and execute */
|
|
|
|
|
if (check_option_true(config.verbose))
|
|
|
|
|
printf("executing: %s %s\n", cmd, tmpfilename);
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
if (execvp(argv[0], argv) == -1)
|
|
|
|
|
puts("error: execvp() failed!");
|
|
|
|
|
} else if (pid == -1) puts("error: fork() failed");
|
|
|
|
|
sleep(1); /* to wait for browsers etc. that return immediatly */
|
|
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
|
unlink(tmpfilename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_telnet(const char *host, const char *port)
|
|
|
|
|
{
|
|
|
|
|
pid_t pid;
|
|
|
|
|
int status;
|
|
|
|
|
|
|
|
|
|
printf("executing: %s %s %s\n", CMD_TELNET, host, port);
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
if (execlp(CMD_TELNET, CMD_TELNET, host, port, NULL) == -1)
|
|
|
|
|
puts("error: execlp() failed!");
|
|
|
|
|
} else if (pid == -1) puts("error: fork() failed!");
|
|
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
|
puts("(done)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_download(const char *host, const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
int fd;
|
|
|
|
|
char filename[1024], line[1024];
|
|
|
|
|
|
|
|
|
|
snprintf(filename, sizeof(filename), "%s", strrchr(selector, '/') + 1);
|
|
|
|
|
printf("enter filename for download [%s]: ", filename);
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
if (! read_line(0, line, sizeof(line))) {
|
|
|
|
|
puts("download aborted");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (strlen(line) > 0)
|
|
|
|
|
#if defined(__OpenBSD__)
|
|
|
|
|
strlcpy(filename, line, sizeof(filename));
|
|
|
|
|
#else
|
|
|
|
|
strcpy(filename, line);
|
|
|
|
|
#endif
|
|
|
|
|
fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
|
|
|
|
|
if (fd == -1) {
|
|
|
|
|
printf("error: unable to create file [%s]: %s\n",
|
|
|
|
|
filename, strerror(errno));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (! download_file(host, port, selector, fd)) {
|
|
|
|
|
printf("error: unable to download [%s]\n", selector);
|
|
|
|
|
unlink(filename);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_search(const char *host, const char *port, const char *selector)
|
|
|
|
|
{
|
|
|
|
|
char search_selector[1024];
|
|
|
|
|
char line[1024];
|
|
|
|
|
|
|
|
|
|
printf("enter search string: ");
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
if (! read_line(0, line, sizeof(line))) {
|
|
|
|
|
puts("search aborted");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
snprintf(search_selector, sizeof(search_selector), "%s\t%s",
|
|
|
|
|
selector, line);
|
|
|
|
|
view_directory(host, port, search_selector, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_history(int key)
|
|
|
|
|
{
|
|
|
|
|
int history_key = 0;
|
|
|
|
|
char a, b, c;
|
|
|
|
|
link_t *link;
|
|
|
|
|
|
|
|
|
|
if (! history) {
|
|
|
|
|
puts("(empty history)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( key < 0 ) {
|
|
|
|
|
puts("(history)");
|
|
|
|
|
for ( link = history; link; link = link->next ) {
|
|
|
|
|
make_key_str(history_key++, &a, &b, &c);
|
|
|
|
|
printf("\033[%sm%c%c%c\033[0m \033[1m%s:%s/1%s\033[0m\n",
|
|
|
|
|
COLOR_SELECTOR, a, b, c, link->host, link->port, link->selector);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* traverse history list */
|
|
|
|
|
for ( link = history; link; link = link->next, ++history_key ) {
|
|
|
|
|
if ( history_key == key ) {
|
|
|
|
|
view_directory(link->host, link->port, link->selector, 0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
puts("history item not found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void view_bookmarks(int key)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
char a, b, c;
|
|
|
|
|
|
|
|
|
|
if (key < 0) {
|
|
|
|
|
puts("(bookmarks)");
|
|
|
|
|
for (i = 0; i < NUM_BOOKMARKS; i++) {
|
|
|
|
|
if (bookmarks[i][0]) {
|
|
|
|
|
make_key_str(i, &a, &b, &c);
|
|
|
|
|
printf("\033[%sm%c%c%c\033[0m \033[1m%s\033[0m\n",
|
|
|
|
|
COLOR_SELECTOR, a, b, c, &bookmarks[i][0]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (i = 0; i < NUM_BOOKMARKS; i++) {
|
|
|
|
|
if (bookmarks[i][0] && i == key) {
|
|
|
|
|
if (parse_uri(&bookmarks[i][0])) view_directory(parsed_host, parsed_port, parsed_selector, 0);
|
|
|
|
|
else printf("invalid gopher URI: %s", &bookmarks[i][0]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pop_history()
|
|
|
|
|
{
|
|
|
|
|
link_t *next;
|
|
|
|
|
|
|
|
|
|
if (! history) {
|
|
|
|
|
puts("(empty history)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* reload page from history (and don't count as history) */
|
|
|
|
|
view_directory(history->host, history->port, history->selector, 0);
|
|
|
|
|
/* history is history... :) */
|
|
|
|
|
next = history->next;
|
|
|
|
|
free(history->host);
|
|
|
|
|
free(history->port);
|
|
|
|
|
free(history->selector);
|
|
|
|
|
free(history);
|
|
|
|
|
history = next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int follow_link(int key)
|
|
|
|
|
{
|
|
|
|
|
link_t *link;
|
|
|
|
|
|
|
|
|
|
for (link = links; link; link = link->next) {
|
|
|
|
|
if (link->key != key)
|
|
|
|
|
continue;
|
|
|
|
|
switch (link->which) {
|
|
|
|
|
case '0':
|
|
|
|
|
view_file(&config.cmd_text[0], link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
case '1':
|
|
|
|
|
view_directory(link->host, link->port, link->selector, 1);
|
|
|
|
|
break;
|
|
|
|
|
case '7':
|
|
|
|
|
view_search(link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
case '5':
|
|
|
|
|
case '9':
|
|
|
|
|
view_download(link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
case '8':
|
|
|
|
|
view_telnet(link->host, link->port);
|
|
|
|
|
break;
|
|
|
|
|
case 'g':
|
|
|
|
|
case 'I':
|
|
|
|
|
case 'p':
|
|
|
|
|
view_file(&config.cmd_image[0], link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
case 'h':
|
|
|
|
|
view_file(&config.cmd_browser[0], link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
case 's':
|
|
|
|
|
view_file(&config.cmd_player[0], link->host, link->port, link->selector);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
printf("missing handler [%c]\n", link->which);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return 1; /* return the array is broken after view! */
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void download_link(int key)
|
|
|
|
|
{
|
|
|
|
|
link_t *link;
|
|
|
|
|
|
|
|
|
|
for (link = links; link; link = link->next) {
|
|
|
|
|
if (link->key != key)
|
|
|
|
|
continue;
|
|
|
|
|
view_download(link->host, link->port, link->selector);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
puts("link not found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int parse_uri(const char *uri)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* strip gopher:// */
|
|
|
|
|
if (! strncmp(uri, "gopher://", 9))
|
|
|
|
|
uri += 9;
|
|
|
|
|
/* parse host */
|
|
|
|
|
for (i = 0; *uri && *uri != ':' && *uri != '/'; uri++) {
|
|
|
|
|
if (*uri != ' ' && i < sizeof(parsed_host) - 1)
|
|
|
|
|
parsed_host[i++] = *uri;
|
|
|
|
|
}
|
|
|
|
|
if (i > 0) parsed_host[i] = 0;
|
|
|
|
|
else return 0;
|
|
|
|
|
/* parse port */
|
|
|
|
|
if (*uri == ':') {
|
|
|
|
|
uri++;
|
|
|
|
|
for (i = 0; *uri && *uri != '/'; uri++)
|
|
|
|
|
if (*uri != ' ' && i < sizeof(parsed_port) - 1)
|
|
|
|
|
parsed_port[i++] = *uri;
|
|
|
|
|
parsed_port[i] = 0;
|
|
|
|
|
} else snprintf(parsed_port, sizeof(parsed_port), "%d", 70);
|
|
|
|
|
/* parse selector (ignore slash and selector type) */
|
|
|
|
|
if (*uri) ++uri;
|
|
|
|
|
if (*uri) ++uri;
|
|
|
|
|
for (i = 0; *uri && i < sizeof(parsed_selector) - 1; ++uri, ++i)
|
|
|
|
|
parsed_selector[i] = *uri;
|
|
|
|
|
parsed_selector[i] = '\0';
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
char line[1024], *uri;
|
|
|
|
|
|
|
|
|
|
/* copy defaults */
|
|
|
|
|
init_config();
|
|
|
|
|
uri = &config.start_uri[0];
|
|
|
|
|
|
|
|
|
|
/* parse command line */
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
if (argv[i][0] == '-') switch(argv[i][1]) {
|
|
|
|
|
case 'H':
|
|
|
|
|
usage();
|
|
|
|
|
break;
|
|
|
|
|
case 'v':
|
|
|
|
|
banner(stdout);
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
default:
|
|
|
|
|
usage();
|
|
|
|
|
} else {
|
|
|
|
|
uri = argv[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse uri */
|
|
|
|
|
if (! parse_uri(uri)) {
|
|
|
|
|
banner(stderr);
|
|
|
|
|
fprintf(stderr, "invalid gopher URI: %s", argv[i]);
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* main loop */
|
|
|
|
|
view_directory(parsed_host, parsed_port, parsed_selector, 0);
|
|
|
|
|
for (;;) {
|
|
|
|
|
printf("\033[%sm%s:%s%s\033[0m ", config.color_prompt,
|
|
|
|
|
current_host, current_port, current_selector);
|
|
|
|
|
fflush(stdout); /* to display the prompt */
|
|
|
|
|
if (! read_line(0, line, sizeof(line))) {
|
|
|
|
|
puts("QUIT");
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
i = strlen(line);
|
|
|
|
|
switch (line[0]) {
|
|
|
|
|
case '?':
|
|
|
|
|
puts(
|
|
|
|
|
"? - help\n"
|
|
|
|
|
"* - reload directory\n"
|
|
|
|
|
"< - go back in history\n"
|
|
|
|
|
".[LINK] - download the given link\n"
|
|
|
|
|
"H - show history\n"
|
|
|
|
|
"H[LINK] - jump to the specified history item\n"
|
|
|
|
|
"G[URI] - jump to the given gopher URI\n"
|
|
|
|
|
"B - show bookmarks\n"
|
|
|
|
|
"B[LINK] - jump to the specified bookmark item\n"
|
|
|
|
|
"C^d - quit");
|
|
|
|
|
break;
|
|
|
|
|
case '<':
|
|
|
|
|
pop_history();
|
|
|
|
|
break;
|
|
|
|
|
case '*':
|
|
|
|
|
view_directory(current_host, current_port,
|
|
|
|
|
current_selector, 0);
|
|
|
|
|
break;
|
|
|
|
|
case '.':
|
|
|
|
|
download_link(make_key(line[1], line[2], line[3]));
|
|
|
|
|
break;
|
|
|
|
|
case 'H':
|
|
|
|
|
if (i == 1 || i == 3 || i == 4) view_history(make_key(line[1], line[2], line[3]));
|
|
|
|
|
break;
|
|
|
|
|
case 'G':
|
|
|
|
|
if (parse_uri(&line[1])) view_directory(parsed_host, parsed_port, parsed_selector, 1);
|
|
|
|
|
else puts("invalid gopher URI");
|
|
|
|
|
break;
|
|
|
|
|
case 'B':
|
|
|
|
|
if (i == 1 || i == 3 || i == 4) view_bookmarks(make_key(line[1], line[2], line[3]));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
follow_link(make_key(line[0], line[1], line[2]));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return EXIT_SUCCESS; /* never get's here but stops cc complaining */
|
|
|
|
|
}
|