/****************************************************************************
Simple gopher protocol client for the PANDA TOPS-20 operating system.
Copyright (C) 2024-2025 Robert Cunnings NW8L
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Usage: gc [-c [cache_dir]] [-k] -g hostname [port] [selector]
gc [-c [cache_dir]] [-k] -m [filename] -l [filename]
gc -V
The "-g" switch commands the program to connect to a gopher server.
The host name can be followed by optional port number and gopher
selector arguments. The default port number is 70. Note: any other
switches used in combination with the "-g" switch must precede the "-g"
switch on the command line. The "-m" switch has no effect other than
setting the name of the menu file when used with "-g" switch.
Alternatively, the optional "-m" switch tells the program to open a gopher
menu file stored on disk. The 'filename' argument is optional; the default
is "gc-favs.gcm".
The optional "-c" switch enables automatic caching of menu and text items
downloaded from the server. This greatly speeds up moving back and forth
through a series of previously visited links. The optional 'cache_dir'
argument specifies a subdirectory for cache files, e.g. "gc -c <.cache>"
to keep them from cluttering up your login directory.
The optional "-k" switch enables the cache "auto kill" feature. This is
meant to be used together with the "-c" switch. It automatically kills
the cache file for a page when backing up from it with the 'b' command at
the "gc>" prompt. As you move forward through a series of pages with the
'f' and 'g' commands, cache files are created to speed up traversing the
series in the reverse direction. Then, when moving back to the starting
point with the 'b' command, the cache files are deleted along the way to
conserve disk space. When moving forward with the 'f' command, the oldest
page in the circular history buffer is evicted to make room for the new
one when the buffer is full. In this case the oldest page's cache file is
deleted to conserve disk space.
The optional "-l" switch enables automatic recording of links visited to
a disk file, the "history list". This file is structured as a gopher menu
and can be opened with the 'l' command at the "gc>" prompt. You can view
your browse history and follow any of the links in the list. The default
history list filename is "gc-hlist.gcm".
The "-V" switch prints the gc version number and exits.
After a gopher menu or text item is fetched and displayed, the following
keyboard commands are available at the "gc>" prompt. This appears after
paging to the end of the text or menu. You can also press CTRL/O before
reaching the end to stop paging, followed by ENTER to show the prompt.
'q' - (q)uit the program.
'd' - re(d)raw the current page starting over from the top.
'r' - (r)eload the current page. Downloads the page from the server,
overwriting the copy in the cache, then redraws it.
'b' - move (b)ack to the previous page. The cached copy will be loaded.
'h' - open the current host's (h)ome menu.
'i' - Show (i)nfo for current page. Host, port, selector, item type and
number of bytes are displayed. If the page is cached, the name of
the cache file is displayed.
'iN' - Show (i)nfo for menu item where N is the link number of the item.
Host, port, selector and item type are displayed. If the page is
cached, the name of the cache file is displayed.
'fN' - move (f)orward where N is a link number. Downloads and displays
the menu item. Works only for links with displayable item types.
's' - (s)ave to disk the content of the current page. Enter a file name
at the prompt. An empty name cancels the operation.
'sN' - (s)ave to disk where N is the link number of the item to save.
Enter a file name at the prompt. An empty name cancels the
operation.
'c' - (c)ache the current page, useful if gc was started with caching
disabled. Overwrites any existing cache file for the page.
'k' - (k)ill cached copy of current page. When caching is enabled, use
this to save disk space if you don't plan to revisit the page.
'g host [port] [selector]' - (g)o to gopher server "host". The host name
may be followed by an optional port number and optional selector.
The default port number is 70.
'a [filename]' - (a)dd the current page to the favorites menu. An optional
file name argument can be used to specify a favorites file to store
it to. Enter a title for the favorites menu link at the prompt.
'v [filename]' - (v)iew the favorites menu. An optional file name argument
can be given to specify a favorites menu file to open.
'l [filename]' - view the browsing history (l)ist. An optional file name
argument can be given to specify a history list file to open. Only
available if the program is invoked with the "-l" option.
'?' - show list of available commands.
The (b)ack command cycles through circular list of recently visited links
kept in memory. The size of the list is defined by the HISTORY_SIZE macro.
When it's full, backing up past the oldest link takes you to the most
recent one.
The (l)ist command opens the "history list", a gopher menu stored in a disk
file containing a list of the most recently visited links. This feature is
available only when the program is invoked with the "-l" option. The most
recently visited link appears at the top; duplicates are removed when a link
is added. You can follow any link in the history list to visit it again.
The growth of the history list file is limited; the maximum size is defined
by the MAX_HLIST_LINES macro. History menu lines look like this:
[004] (DIR) The Gopherspace Wayback Machine [mozz.us /wayback]
with the host name and selector printed after the link description text.
When item types '0' (text) and '1' (menu) are downloaded, they are cached
on disk in files with extension ".gcc". Subsequent views of the page are
served from the cache to save time and bandwidth. When viewing a page, you
can use the 'r' (reload) command to force a download from the server to over-
write the cached copy. You can use the 'k' (kill) command to delete the page
from the cache to save disk space if you won't be visiting it again. All
cache files can be deleted with "del *.gcc" to free up disk space.
Note: this program is compatible with the KCC-6 C compiler on Panda TOPS-20.
Build it with:
cc -i=extend gc trmcap
to create an executable with extended addressing enabled. This is necessary
to make enough dynamic memory available for buffering downloaded text and
gopher menus. You can ignore the "Missing function prototype" advisories
generated when the "trmcap" file is compiled.
****************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PAGE_BLOCK_SIZE 2048
#define MENU_BLOCK_SIZE 512
#define MAX_MENU_LINK 9999
#define MAX_CACHE_DIR_SIZE 32
#define MAX_FILENAME_SIZE 64
#define MAX_FAV_TITLE_SIZE 40
#define MAX_HLIST_LINES 100
#define HISTORY_SIZE 10
#define INPUT_STR_SIZE 128
#define SELECTOR_STR_SIZE 128
#define HOST_STR_SIZE 64
#define QUERY_STR_SIZE 64
#define DESC_STR_SIZE 64
#define PORT_STR_SIZE 8
#define HL_DESC_FIELD_SIZE 40
#define VERSION "1.05"
#define DEF_FAVS_FNAME "gc-favs.gcm"
#define DEF_HLIST_FNAME "gc-hlist.gcm"
#define TMP_HLIST_FNAME "gc-tmp.gcm"
typedef struct hist_t {
short port_num;
short item_type;
short is_query;
short is_fav_menu;
short is_hlist_menu;
short is_ip_addr;
short save_to_disk;
short force_request;
short use_selector;
char selector[SELECTOR_STR_SIZE+1];
char query[QUERY_STR_SIZE+1];
char host[HOST_STR_SIZE+1];
} HISTORY;
typedef struct mline_t {
short item_type;
short link_num;
char *desc;
char *selector;
char *host;
char *port_num;
} MENU_LINE;
typedef struct page_t {
int num_bytes;
int buf_size;
int num_lines;
int max_linknum;
int menu_size;
char *buf;
char *temp;
MENU_LINE *menu;
} PAGE;
typedef struct hl_mline_t {
short is_valid;
short item_type;
char host[HOST_STR_SIZE+1];
char port_num[PORT_STR_SIZE+1];
char desc[DESC_STR_SIZE+1];
char selector[SELECTOR_STR_SIZE+1];
} HLIST_MENU_LINE;
/* the one and only page buffer with text of current page */
PAGE page;
/* copy of gopher menu line to store in browse history file */
HLIST_MENU_LINE hl_menu_line;
/* termcap variables */
char PC, *BC, *UP, *CL, *CE, *CM, *tg;
short ospeed, CO, LI;
char cfile_dir[MAX_CACHE_DIR_SIZE+1];
char input_str[INPUT_STR_SIZE+1];
char hlist_fname[MAX_FILENAME_SIZE+1];
char favs_fname[MAX_FILENAME_SIZE+1];
char cap_buf[512], *table;
char term_buf[2048];
/* data buffers */
char req_buffer[384];
char resp_buffer[128];
/* in-memory circular buffer for browse history */
HISTORY hist[HISTORY_SIZE];
short cur_page;
short prev_page;
short max_page;
int fletch16(char *data, int cnt)
{
/* compute fletcher16 checksum. Using 9-bit chars so it's mod (512-1) */
int i, sum1, sum2;
sum1 = 0;
sum2 = 0;
for (i = 0; i < cnt; i++) {
sum1 = (sum1 + data[i]) % 511;
sum2 = (sum2 + sum1) % 511;
}
return (sum2 << 9) | sum1;
}
int test_cache(char *host, char *selector, char *query, int is_query)
{
int check;
struct stat st;
if (is_query)
sprintf(req_buffer, "%s:%s\t%s", host, selector, query);
else
sprintf(req_buffer, "%s:%s", host, selector);
check = fletch16(req_buffer, strlen(req_buffer));
sprintf(req_buffer, "%s%06d.gcc", cfile_dir, check);
if (stat(req_buffer, &st) != 0)
return -1; /* no cache file found */
return check;
}
void pr_text(void)
{
int i;
for (i = 0; i < page.num_bytes; i++)
putchar(page.buf[i]);
putchar('\n');
}
int is_item_type(int c)
{
switch (c) {
case 'i': case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': case '8':
case 'T': case '9': case 'g': case 'I': case 'h':
case 'p': case 'P': case 'd': case 's': case 'X':
case 'r': case ':': case ';': case '<':
break;
default:
return 0;
break;
}
return 1;
}
int mk_menu(void)
{
int i, c, cnt, linknum, state;
MENU_LINE *temp_ptr;
page.menu_size = MENU_BLOCK_SIZE;
page.menu = (MENU_LINE *)malloc(page.menu_size * sizeof(MENU_LINE));
if (page.menu == NULL) {
printf("Error: out of memory! Exiting.\n");
exit(1);
}
cnt = 0;
state = 0;
linknum = 1;
for (i = 0; i < page.num_bytes; i++) {
c = page.buf[i];
if (c == '\0') /* end of buffer */
break;
if (state == -1) /* "period on a line by itself" detected, stop */
break;
switch(state) {
case 0:
if (c == '.') {
if (page.buf[i + 1] == '\r' || page.buf[i + 1] == '\n') {
/* "period on a line by itself" indicates EOT */
page.buf[i] = '\0';
state = -1;
continue;
}
}
if (c == '\r' || c == '\n') {
/* unexpected EOL */
page.buf[i] = '\0';
continue;
}
if (is_item_type(c))
page.menu[cnt].item_type = c;
else
continue;
page.menu[cnt].desc = &page.buf[i + 1];
++state;
break;
case 1:
if (c == '\r' || c == '\n') {
/* unexpected EOL */
page.buf[i] = '\0';
state = 4;
continue;
}
if (c == '\t') {
page.buf[i] = '\0';
page.menu[cnt].selector = &page.buf[i + 1];
++state;
}
break;
case 2:
if (c == '\r' || c == '\n') {
/* unexpected EOL */
page.buf[i] = '\0';
state = 4;
continue;
}
if (c == '\t') {
page.buf[i] = '\0';
page.menu[cnt].host = &page.buf[i + 1];
++state;
}
break;
case 3:
if (c == '\r' || c == '\n') {
/* unexpected EOL */
page.buf[i] = '\0';
state = 4;
continue;
}
if (c == '\t') {
page.buf[i] = '\0';
page.menu[cnt].port_num = &page.buf[i + 1];
++state;
}
break;
case 4:
if (c == '\t') {
/* discard any gopher+ field following port number */
page.buf[i] = '\0';
continue;
}
if (c == '\r' || c == '\n') {
page.buf[i] = '\0';
/* skip the next CR or LF */
if (page.buf[i + 1] == '\r' || page.buf[i + 1] == '\n')
++i;
switch (page.menu[cnt].item_type) {
case 'i':
case '2':
case '3':
case '8':
case 'T':
case '+':
case 'h':
page.menu[cnt].link_num = 0;
break;
default:
page.menu[cnt].link_num = linknum++;
break;
}
++cnt;
state = 0;
}
break;
}
if (linknum > MAX_MENU_LINK)
break;
if (cnt == page.menu_size) {
/* need to allocate more memory. Normally realloc() would be
used in this situation but the version in the KCC standard
library doesn't work reliably - it often fails with message
"Error: old block not in use (flag == FREE)". However, the
workaround using malloc() and free() always works.
*/
page.menu_size += MENU_BLOCK_SIZE;
temp_ptr = (MENU_LINE *)malloc(page.menu_size * sizeof(MENU_LINE));
if (temp_ptr == NULL) {
printf("Error: out of memory! Exiting.\n");
exit(1);
}
memcpy(temp_ptr, page.menu, cnt * sizeof(MENU_LINE));
sleep(1); /* seems necessary to prevent error on free() */
free((MENU_LINE *)page.menu);
page.menu = temp_ptr;
}
}
page.max_linknum = linknum - 1;
return cnt;
}
void pr_menu(void)
{
int i;
char *it;
for (i = 0; i < page.num_lines; i++) {
switch (page.menu[i].item_type) {
case '0':
it = "TXT";
break;
case '1':
it = "DIR";
break;
case '2':
it = "CSO";
break;
case '3':
it = "ERR";
break;
case '4':
it = "MAC";
break;
case '5':
it = "DOS";
break;
case '6':
it = "UUE";
break;
case '7':
it = "QRY";
break;
case '8':
case 'T':
it = "TEL";
break;
case '9':
it = "BIN";
break;
case 'g':
it = "GIF";
break;
case 'I':
it = "IMG";
break;
case 'h':
it = "HTM";
break;
case 'p':
it = "PNG";
break;
case 'P':
it = "PDF";
break;
case 'd':
it = "DOC";
break;
case 's':
it = "WAV";
break;
case 'X':
it = "XML";
break;
case 'r':
it = "RTF";
break;
case ':':
it = "BMP";
break;
case ';':
it = "MOV";
break;
case '<':
it = "SND";
break;
default:
it = "---";
break;
}
switch (page.menu[i].item_type) {
case 'i':
printf("%s\n", page.menu[i].desc);
break;
case '2':
case '3':
case '8':
case 'T':
case '+':
case 'h':
printf(" (%s) %s\n", it, page.menu[i].desc);
break;
default:
if (page.menu[i].link_num < 1000)
printf("[%03d] (%s) %s\n",
page.menu[i].link_num, it, page.menu[i].desc);
else
printf("[%04d] (%s) %s\n",
page.menu[i].link_num, it, page.menu[i].desc);
break;
}
}
}
void clr_screen(void)
{
tputs(CL, 1, putchar);
}
void clr_to_eol(void)
{
tputs(CE, 1, putchar);
}
void goto_xy(int x, int y)
{
char *tg;
tg = tgoto(CM, x, y, putchar);
tputs(tg, 1, putchar);
}
int init_term(void)
{
int result;
char *temp_str;
temp_str = getenv("TERM");
if (temp_str == NULL) {
printf("Error: failed to get terminal type!\n");
return -1;
}
result = tgetent(term_buf, temp_str);
if (result < 0) {
printf("Error: failed to get caps for %s!\n", temp_str);
return -1;
}
table = cap_buf;
BC = tgetstr("bc", &table);
CL = tgetstr("cl", &table);
CE = tgetstr("ce", &table);
CM = tgetstr("cm", &table);
UP = tgetstr("up", &table);
CO = tgetnum("co");
LI = tgetnum("li");
temp_str = tgetstr("pc", &table);
PC = temp_str ? temp_str[0] : '\0';
return 0;
}
int get_input_str(char buf[], int maxlen)
{
int c, i;
i = 0;
c = getchar();
while (c != '\n' && c != EOF) {
if (isprint(c) && i < maxlen)
buf[i++] = c;
c = getchar();
}
buf[i] = '\0';
return i;
}
int guess_item_type(char *selector)
{
int i, j, type;
char *p, temp[64];
type = 0;
for (i = strlen(selector) - 1; i >= 0; i--) {
if (selector[i] == '.') {
p = &selector[i];
j = 0;
while (*p)
temp[j++] = tolower(*p++);
temp[j] = '\0';
if (!strcmp(temp, ".txt"))
type = '0';
else if (!strcmp(temp, ".gif"))
type = 'g';
else if (!strcmp(temp, ".htm"))
type = 'h';
else if (!strcmp(temp, ".html"))
type = 'h';
else if (!strcmp(temp, ".pdf"))
type = 'P';
else if (!strcmp(temp, ".xml"))
type = 'X';
else if (!strcmp(temp, ".rtf"))
type = 'r';
else if (!strcmp(temp, ".png"))
type = 'p';
else if (!strcmp(temp, ".doc"))
type = 'd';
else if (!strcmp(temp, ".hex"))
type = '4';
else if (!strcmp(temp, ".jpg"))
type = 'I';
else if (!strcmp(temp, ".jpeg"))
type = 'I';
else if (!strcmp(temp, ".tif"))
type = 'I';
else if (!strcmp(temp, ".bmp"))
type = ':';
else if (!strcmp(temp, ".wav"))
type = 's';
else if (!strcmp(temp, ".mp3"))
type = '<';
else if (!strcmp(temp, ".ogg"))
type = '<';
else if (!strcmp(temp, ".gz"))
type = '9';
else if (!strcmp(temp, ".tgz"))
type = '9';
else if (!strcmp(temp, ".tar"))
type = '9';
else if (!strcmp(temp, ".zip"))
type = '9';
else if (!strcmp(temp, ".c"))
type = '0';
else if (!strcmp(temp, ".h"))
type = '0';
break;
}
if (selector[i] == '/') {
type = '1';
break;
}
}
return type;
}
int cache_page(int which)
{
int i, check;
char fname_str[16];
FILE *file;
if (hist[which].is_query)
sprintf(req_buffer, "%s:%s\t%s", hist[which].host,
hist[which].selector, hist[which].query);
else
sprintf(req_buffer, "%s:%s",
hist[which].host, hist[which].selector);
check = fletch16(req_buffer, strlen(req_buffer));
sprintf(fname_str, "%s%06d.gcc", cfile_dir, check);
file = fopen(fname_str, "w");
if (file == NULL)
return -1;
for (i = 0; i < page.num_bytes; i++) {
if (isprint(page.buf[i]) || isspace(page.buf[i]))
fputc(page.buf[i], file);
}
fputc('\0', file);
fclose(file);
return check;
}
int load_page(int check)
{
int c, cnt, numbytes;
char fname_str[16];
FILE *file;
struct stat st;
sprintf(fname_str, "%s%06d.gcc", cfile_dir, check);
if (stat(fname_str, &st) != 0)
return 0;
numbytes = st.st_size;
file = fopen(fname_str, "r");
if (file == NULL)
return 0;
cnt = 0;
while ((c = fgetc(file)) && cnt < numbytes) {
page.buf[cnt++] = c;
if (cnt >= page.buf_size - 1) {
/* need to allocate more memory. Normally realloc() would be
used in this situation but the version in the KCC standard
library doesn't work reliably - it often fails with message
"Error: old block not in use (flag == FREE)". However, the
workaround using malloc() and free() always works.
*/
page.buf_size += PAGE_BLOCK_SIZE;
page.temp = (char *)malloc(page.buf_size);
if (page.temp == NULL) {
printf("Error: out of memory! Exiting.\n");
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
exit(1);
}
page.buf[cnt] = '\0';
strcpy(page.temp, page.buf);
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
page.buf = page.temp;
page.temp = NULL;
}
}
page.buf[cnt] = '\0';
page.num_bytes = cnt;
fclose(file);
return 1;
}
int load_menu(void)
{
int c, cnt, numbytes;
FILE *file;
struct stat st;
/* if file doesn't exist, create it */
if (stat(hist[cur_page].selector, &st) != 0) {
file = fopen(hist[cur_page].selector, "w");
if (file != NULL)
fclose(file);
else
return 0;
}
numbytes = st.st_size;
file = fopen(hist[cur_page].selector, "r");
if (file == NULL)
return 0;
cnt = 0;
while ((c = fgetc(file)) && cnt < numbytes) {
page.buf[cnt++] = c;
if (cnt >= page.buf_size - 1) {
/* need to allocate more memory. Normally realloc() would be
used in this situation but the version in the KCC standard
library doesn't work reliably - it often fails with message
"Error: old block not in use (flag == FREE)". However, the
workaround using malloc() and free() always works.
*/
page.buf_size += PAGE_BLOCK_SIZE;
page.temp = (char *)malloc(page.buf_size);
if (page.temp == NULL) {
printf("Error: out of memory! Exiting.\n");
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
exit(1);
}
page.buf[cnt] = '\0';
strcpy(page.temp, page.buf);
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
page.buf = page.temp;
page.temp = NULL;
}
}
page.num_bytes = cnt;
fclose(file);
return 1;
}
int hist_init(void)
{
prev_page = 0;
cur_page = 1;
max_page = cur_page;
memcpy(&hist[prev_page], &hist[cur_page], sizeof(HISTORY));
return cur_page;
}
int hist_next(int autokill)
{
int temp;
prev_page = cur_page;
++cur_page;
if (max_page < HISTORY_SIZE)
++max_page;
if (cur_page == HISTORY_SIZE)
cur_page = 0;
/* is "auto kill" of cache files enabled? If so, and history buffer
is full, delete cache file for the item that is about to be replaced */
if (autokill && max_page == HISTORY_SIZE &&
!hist[cur_page].is_fav_menu && !hist[cur_page].is_hlist_menu) {
temp = test_cache(hist[cur_page].host, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].is_query);
if (temp != -1) {
sprintf(req_buffer, "%s%06d.gcc", cfile_dir, temp);
unlink(req_buffer);
printf("auto kill: deleting [%s]\n", req_buffer);
fflush(stdout);
}
}
/* copy previous contents, important for commands like "home"
and "save" which do not overwrite all fields
*/
memcpy(&hist[cur_page], &hist[prev_page], sizeof(HISTORY));
/* clear special flags */
hist[cur_page].is_query = 0;
hist[cur_page].is_fav_menu = 0;
hist[cur_page].is_hlist_menu = 0;
hist[cur_page].save_to_disk = 0;
hist[cur_page].force_request = 0;
return cur_page;
}
int hist_prev(int autokill)
{
int temp;
if (max_page < HISTORY_SIZE && prev_page == 0)
return cur_page; /* at start of history; can't go back */
/* is "auto kill" of cache files enabled? If so delete cache file */
if (autokill &&
!hist[cur_page].is_fav_menu && !hist[cur_page].is_hlist_menu) {
temp = test_cache(hist[cur_page].host, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].is_query);
if (temp != -1) {
sprintf(req_buffer, "%s%06d.gcc", cfile_dir, temp);
unlink(req_buffer);
printf("auto kill: deleting [%s]\n", req_buffer);
fflush(stdout);
}
}
--cur_page;
if (cur_page < 0)
cur_page = HISTORY_SIZE - 1;
prev_page = cur_page - 1;
if (prev_page < 0)
prev_page = HISTORY_SIZE - 1;
return cur_page;
}
void reset_page(void)
{
if (page.menu != NULL) {
free((MENU_LINE *)page.menu);
page.menu = NULL;
}
free((char *)page.buf);
/* initialize the page structure */
memset(&page, 0, sizeof(PAGE));
page.buf_size = PAGE_BLOCK_SIZE;
page.buf = (char *)malloc(page.buf_size);
if (page.buf == NULL) {
printf("Error: out of memory! Exiting.\n");
exit(1);
}
memset(page.buf, 0, page.buf_size);
}
FILE* open_file(char *fname, int type)
{
int temp;
struct stat st;
FILE *file;
temp = stat(fname, &st);
if (temp == 0) {
printf("\nFile %s exists. Overwrite it? [y/N]\n", fname);
get_input_str(input_str, 2);
if (input_str[0] != 'y' && input_str[0] != 'Y')
return NULL;
}
switch (type) {
case '0':
case '1':
case '4':
case '6':
case 'h':
file = fopen(fname, "w");
break;
default:
file = fopen(fname, "wb8");
break;
}
return file;
}
int write_file(FILE *file, char *buf, int numbytes, int type)
{
int i, cnt;
cnt = 0;
switch (type) {
case '0':
case '1':
case '4':
case '6':
case 'h':
cnt = 0;
for (i = 0; i < numbytes; i++) {
if (isprint(buf[i]) || isspace(buf[i])) {
fputc(buf[i], file);
++cnt;
}
}
break;
default:
cnt = fwrite(buf, 1, numbytes, file);
break;
}
return cnt;
}
int add2favs(char *fname)
{
FILE *file;
char buf[MAX_FAV_TITLE_SIZE+1];
/* append line for current page to favorites menu */
printf("\nAdd to favorites. Enter title: ");
if (get_input_str(buf, MAX_FAV_TITLE_SIZE) == 0) {
printf("Add to favorites canceled!\n");
fflush(stdout);
sleep(1);
return 0;
}
file = fopen(fname, "a");
if (file == NULL)
return 0;
/* although not sanctioned by RFC 1436, some gopher holes
have dotted-quad IP addresses in menus vice host names
*/
sprintf(req_buffer, "%c%s\t%s\t%s\t%d\r\n",
hist[cur_page].item_type, buf,
hist[cur_page].selector, hist[cur_page].host,
hist[cur_page].port_num);
fputs(req_buffer, file);
fclose(file);
return 1;
}
int add2hlist(void)
{
FILE *in_file, *out_file;
int cnt;
struct stat st;
char buf[HL_DESC_FIELD_SIZE+SELECTOR_STR_SIZE*2+
HOST_STR_SIZE*2+PORT_STR_SIZE+10];
/* is this data valid? If not, do nothing */
if (!hl_menu_line.is_valid)
return 1;
hl_menu_line.is_valid = 0; /* only used once */
if (stat(TMP_HLIST_FNAME, &st) != 0) {
unlink(TMP_HLIST_FNAME);
sleep(1);
}
out_file = fopen(TMP_HLIST_FNAME, "a");
if (out_file == NULL)
return 0;
/* although not sanctioned by RFC 1436, some gopher holes
have dotted-quad IP addresses in menus vice host names
*/
if (strlen(hl_menu_line.desc) > HL_DESC_FIELD_SIZE)
hl_menu_line.desc[HL_DESC_FIELD_SIZE] = '\0';
sprintf(req_buffer, "%c%-*s [%s %s]\t%s\t%s\t%s\r\n",
hl_menu_line.item_type, HL_DESC_FIELD_SIZE, hl_menu_line.desc,
hl_menu_line.host, hl_menu_line.selector, hl_menu_line.selector,
hl_menu_line.host, hl_menu_line.port_num);
fputs(req_buffer, out_file);
if (stat(hlist_fname, &st) != 0) {
in_file = fopen(hlist_fname, "w");
if (in_file == NULL) {
fclose(out_file);
unlink(TMP_HLIST_FNAME);
return 0;
}
fclose(in_file);
sleep(1);
}
in_file = fopen(hlist_fname, "r");
if (in_file == NULL) {
fclose(out_file);
unlink(TMP_HLIST_FNAME);
return 0;
}
cnt = 1;
while (fgets(buf, sizeof(buf), in_file) != NULL) {
/* check for dups - compare the hostname and selector */
if (!strcmp(&req_buffer[HL_DESC_FIELD_SIZE+2],
&buf[HL_DESC_FIELD_SIZE+2]))
continue;
fputs(buf, out_file);
if (++cnt == MAX_HLIST_LINES)
break;
}
fclose(out_file);
fclose(in_file);
unlink(hlist_fname);
rename(TMP_HLIST_FNAME, hlist_fname);
/* clear the menu list copy */
memset(&hl_menu_line, 0, sizeof(hl_menu_line));
return 1;
}
void hl_copy_ml(int linknum)
{
strcpy(hl_menu_line.desc, page.menu[linknum].desc);
strcpy(hl_menu_line.host, page.menu[linknum].host);
strcpy(hl_menu_line.selector, page.menu[linknum].selector);
strcpy(hl_menu_line.port_num, page.menu[linknum].port_num);
hl_menu_line.item_type = page.menu[linknum].item_type;
hl_menu_line.is_valid = 1;
}
void hl_new_ml(void)
{
strcpy(hl_menu_line.desc, "");
strcpy(hl_menu_line.host, hist[cur_page].host);
strcpy(hl_menu_line.selector, hist[cur_page].selector);
sprintf(hl_menu_line.port_num, "%d", hist[cur_page].port_num);
hl_menu_line.item_type = hist[cur_page].item_type;
hl_menu_line.is_valid = 1;
}
int is_saveable(int item_type)
{
switch (item_type) {
case '2':
case '3':
case '7':
case '8':
case 'T':
case '+':
break;
default:
return 1;
}
return 0;
}
int is_displayable(int item_type)
{
switch (item_type) {
case '0':
case '1':
case '7':
break;
default:
return 0;
}
return 1;
}
int fav_menu_init(void)
{
FILE *file;
struct stat st;
/* if file doesn't exist, create it */
if (stat(favs_fname, &st) != 0) {
file = fopen(favs_fname, "w");
if (file != NULL)
fclose(file);
else
return 0;
}
strcpy(hist[cur_page].selector, favs_fname);
strcpy(hist[cur_page].host, "localhost");
hist[cur_page].port_num = 70;
hist[cur_page].item_type = '1';
hist[cur_page].is_ip_addr = 0;
hist[cur_page].is_fav_menu = 1;
hist[cur_page].is_hlist_menu = 0;
/* initialize page buffer */
page.buf_size = PAGE_BLOCK_SIZE;
page.buf = (char *)malloc(page.buf_size);
if (page.buf == NULL) {
printf("Error: out of memory! Exiting.\n");
exit(1);
}
/* copy settings to prev page buffer */
memcpy(&hist[prev_page], &hist[cur_page], sizeof(HISTORY));
return 1;
}
int is_ip_addr(char *host)
{
char *temp;
temp = host;
while (*temp) {
if (!isdigit(*temp) && *temp != '.')
return 0;
++temp;
}
return 1;
}
void goph_srv_init(int argc, char **argv, int index)
{
char *temp_str;
int i;
/* Capture the host name or IP address */
hist[cur_page].is_ip_addr = is_ip_addr(argv[index]);
strncpy(hist[cur_page].host, argv[index], HOST_STR_SIZE);
hist[cur_page].host[HOST_STR_SIZE] = '\0';
++index;
/* is a port number specified? If so, capture it */
if (argc > index) {
temp_str = argv[index];
if (isdigit(temp_str[0])) {
i = atoi(temp_str);
if (i > 0)
hist[cur_page].port_num = i;
++index;
}
}
hist[cur_page].selector[0] = '\0';
hist[cur_page].use_selector = 0;
/* is a selector specified? If so, capture it */
if (argc > index) {
temp_str = argv[index];
strncpy(hist[cur_page].selector, temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
if (temp_str[0] == '"') {
for (i = 1; i < SELECTOR_STR_SIZE && temp_str[i]; i++) {
if (temp_str[i] != '"') {
hist[cur_page].selector[i - 1] = temp_str[i];
} else {
hist[cur_page].selector[i - 1] = '\0';
break;
}
}
} else {
strncpy(hist[cur_page].selector, temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
}
hist[cur_page].use_selector = 1;
}
/* initialize the page structure */
memset(&page, 0, sizeof(PAGE));
/* initialize the history record */
if (hist[cur_page].use_selector)
hist[cur_page].item_type = guess_item_type(hist[cur_page].selector);
else
hist[cur_page].item_type = '1';
/* initialize page buffer */
page.buf_size = PAGE_BLOCK_SIZE;
page.buf = (char *)malloc(page.buf_size);
if (page.buf == NULL) {
printf("Error: out of memory! Exiting.\n");
exit(1);
}
page.buf[0] = '\0';
/* copy settings to prev page buffer */
memcpy(&hist[prev_page], &hist[cur_page], sizeof(HISTORY));
}
void pr_usage(void)
{
printf("Usage: gc [-c [cache_dir]] -g hostname [port] [selector]\n");
printf(" gc [-c [cache_dir]] -m [filename] -l [filename]\n");
printf(" gc -V\n");
}
int main(int argc, char **argv)
{
char *temp_str;
unsigned long addr;
int i, temp, sock, status, cnt;
int is_redraw, cache_enabled, auto_kill, menu_enabled, hlist_enabled;
struct sockaddr_in sa;
struct hostent *hent;
FILE *file;
if (argc == 1) {
pr_usage();
exit(1);
}
/* initialize terminal control */
if (init_term() < 0)
exit(1);
/* initialize browsing history buffer */
hist_init();
hist[cur_page].port_num = 70;
/* initialize history list file name */
strcpy(hlist_fname, DEF_HLIST_FNAME);
hlist_enabled = 0;
/* initialize favorites menu file name */
strcpy(favs_fname, DEF_FAVS_FNAME);
menu_enabled = 0;
cache_enabled = 0;
auto_kill = 0;
/* process command line arguments */
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-V")) {
printf("gc %s\n", VERSION);
exit(0);
} else if (!strcmp(argv[i], "-c")) {
cache_enabled = 1;
/* look for cache directory arg - default is cwd */
if (argc > i + 1) {
temp_str = argv[i + 1];
if (temp_str[0] != '-') {
strncpy(cfile_dir, temp_str, MAX_CACHE_DIR_SIZE);
cfile_dir[MAX_CACHE_DIR_SIZE] = '\0';
++i;
}
}
} else if (!strcmp(argv[i], "-k")) {
/* enable automatic killing of cache file when moving "back" */
auto_kill = 1;
} else if (!strcmp(argv[i], "-l")) {
hlist_enabled = 1;
/* capture history list file name if present */
if (argc > i + 1) {
temp_str = argv[i + 1];
if (temp_str[0] != '-') {
strncpy(hlist_fname, temp_str, MAX_FILENAME_SIZE);
hlist_fname[MAX_FILENAME_SIZE] = '\0';
++i;
}
}
} else if (!strcmp(argv[i], "-m")) {
menu_enabled = 1;
/* capture menu file name if present */
if (argc > i + 1) {
temp_str = argv[i + 1];
if (temp_str[0] != '-') {
strncpy(favs_fname, temp_str, MAX_FILENAME_SIZE);
favs_fname[MAX_FILENAME_SIZE] = '\0';
++i;
}
}
} else if (!strcmp(argv[i], "-g")) {
/* "go" to gopher server */
goph_srv_init(argc, argv, i + 1);
if (hlist_enabled)
hl_new_ml();
goto restart;
} else {
pr_usage();
exit(1);
}
}
/* auto_kill makes sense only if caching is enabled */
if (auto_kill) {
if (!cache_enabled)
auto_kill = 0;
}
if (menu_enabled) {
/* load the menu */
if (!fav_menu_init()) {
printf("Error: cannot open favorites file!\n");
exit(1);
}
goto favorites;
} else {
goto get_input;
}
restart:
reset_page();
if (hist[cur_page].is_fav_menu)
goto favorites; /* take shortcut for favorites menu */
else if (hist[cur_page].is_hlist_menu)
goto hlist; /* take shortcut for history list menu */
else if (!hist[cur_page].host[0])
goto get_input; /* nothing to print */
save_page_buf:
/* is this a save to disk operation? If so, get file name */
if (hist[cur_page].save_to_disk) {
printf("\nSave to disk. Enter file name: ");
temp = get_input_str(input_str, MAX_FILENAME_SIZE);
if (temp) {
file = open_file(input_str, hist[cur_page].item_type);
} else {
printf("Save to disk canceled!\n");
fflush(stdout);
sleep(1);
/* go back to previous page */
hist_prev(auto_kill);
goto restart;
}
if (file == NULL) {
printf("Error: save to disk failed!\n");
fflush(stdout);
sleep(1);
/* go back to previous page */
hist_prev(auto_kill);
goto restart;
}
/* is data already in page buffer? If so, store it now */
if (hist[cur_page].save_to_disk == 2) {
for (i = 0; i < page.num_bytes; i++)
fputc(page.buf[i], file);
fputc('\0', file);
fclose(file);
hist[cur_page].save_to_disk = 0;
goto redraw;
}
}
/* is this a query? If so, get query string */
else if (hist[cur_page].is_query == 1) {
printf("\nEnter query: ");
temp = get_input_str(input_str, QUERY_STR_SIZE);
if (temp == 0) {
printf("Query canceled!\n");
fflush(stdout);
sleep(1);
/* go back to previous page */
hist_prev(auto_kill);
goto restart;
}
strncpy(hist[cur_page].query, input_str, QUERY_STR_SIZE);
hist[cur_page].query[QUERY_STR_SIZE] = '\0';
hist[cur_page].item_type = '1'; /* expect a gopher menu */
/* increment to prevent repeat of prompt when moving 'back' etc. */
++hist[cur_page].is_query;
}
/* is this page cached? If so, reload from cache if request not forced */
if (!hist[cur_page].save_to_disk && !hist[cur_page].force_request) {
temp = test_cache(hist[cur_page].host, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].is_query);
if (temp != -1) {
is_redraw = 0;
if (load_page(temp))
goto redraw;
}
}
/* resolve ip address and create client socket */
if (hist[cur_page].is_ip_addr) {
addr = inet_addr(hist[cur_page].host);
} else {
hent = gethostbyname(hist[cur_page].host);
}
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(hist[cur_page].port_num);
if (hist[cur_page].is_ip_addr)
sa.sin_addr.s_addr = (unsigned int)addr;
else
sa.sin_addr.s_addr = *((unsigned long *)hent->h_addr);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("Error: failed to allocate socket! Exiting.\n");
exit(1);
}
/* attempt to connect to the server */
status = connect(sock, (struct sockaddr *)&sa, sizeof(sa));
if (status < 0)
{
printf("Error: failed to connect to [%s]!\n"
"Press any key to proceed...",
hist[cur_page].host);
fflush(stdout);
get_input_str(input_str, 1);
/* go back to previous page */
hist_prev(auto_kill);
hl_menu_line.is_valid = 0;
/* is this a case of failure to connect for host
specified with -g switch on command line? */
if (max_page == 1)
goto get_input; /* no previous page exists */
goto restart;
}
/* clear the screen and transmit the selector */
clr_screen();
goto_xy(0, 0);
clr_to_eol();
if (hist[cur_page].use_selector && hist[cur_page].is_query)
sprintf(req_buffer, "%s\t%s\r\n",
hist[cur_page].selector, hist[cur_page].query);
else if (hist[cur_page].use_selector)
sprintf(req_buffer, "%s\r\n", hist[cur_page].selector);
else
sprintf(req_buffer, "\r\n");
cnt = write(sock, req_buffer, strlen(req_buffer));
if (cnt < 0) {
printf("Error: could not send selector! Exiting.\n");
close(sock);
exit(0);
}
if (hist[cur_page].save_to_disk) {
printf("\n%s:%d %s '%c'\n",
hist[cur_page].host, hist[cur_page].port_num,
hist[cur_page].selector, hist[cur_page].item_type);
}
/* receive response from server */
printf("\nReceiving from [%s]: ", hist[cur_page].host);
fflush(stdout);
temp = 0;
while (1) {
cnt = read(sock, resp_buffer, sizeof(resp_buffer) - 1);
if (++temp == 10) {
temp = 0;
putchar('#');
}
fflush(stdout);
if (cnt <= 0)
break;
if (hist[cur_page].save_to_disk) {
if (write_file(file, resp_buffer,
cnt, hist[cur_page].item_type) < 0) {
printf("Error: save to disk failed!\n");
close(sock);
exit(1);
}
} else {
resp_buffer[cnt] = '\0';
if (page.num_bytes + cnt >= page.buf_size) {
/* need to allocate more memory. Normally realloc() would be
used in this situation but the version in the KCC standard
library doesn't work reliably - it often fails with message
"Error: old block not in use (flag == FREE)". However, the
workaround using malloc() and free() always works.
*/
page.buf_size += PAGE_BLOCK_SIZE;
page.temp = (char *)malloc(page.buf_size);
if (page.temp == NULL) {
printf("Error: out of memory! Exiting.\n");
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
close(sock);
exit(1);
}
strcpy(page.temp, page.buf);
sleep(1); /* seems necessary to prevent error on free() */
free((char *)page.buf);
page.buf = page.temp;
page.temp = NULL;
}
page.num_bytes += cnt;
strcat(page.buf, resp_buffer);
}
}
close(sock);
sock = NULL;
/* add to history list if enabled */
if (hlist_enabled) {
if (!add2hlist()) {
printf("Error: save to history file failed!\n");
fflush(stdout);
sleep(1);
}
}
/* is this a save to disk operation? If so, close file and restart */
if (hist[cur_page].save_to_disk) {
fclose(file);
file = NULL;
/* saved to disk, go back to previous page */
hist_prev(auto_kill);
goto restart;
}
/* if this is a displayable item type, cache it a in disk file */
else if (cache_enabled) {
if (is_displayable(hist[cur_page].item_type)) {
if (cache_page(cur_page) != -1)
hist[cur_page].force_request = 0;
}
}
favorites:
/* is this the favorites menu? If so, load data from favorites file */
if (hist[cur_page].is_fav_menu) {
if (!load_menu()) {
/* file not found, go back to previous page */
hist_prev(auto_kill);
goto restart;
}
}
hlist:
/* is this the history list menu? If so, load data from history file */
if (hist[cur_page].is_hlist_menu) {
if (!load_menu()) {
/* file not found, go back to previous page */
hist_prev(auto_kill);
goto restart;
}
}
/* display the response, whether a gopher menu or a text file */
is_redraw = 0;
redraw:
clr_screen();
goto_xy(0, 0);
clr_to_eol();
if (hist[cur_page].is_query)
printf("\n%s:%d %s?%s '%c' (%d bytes)", hist[cur_page].host,
hist[cur_page].port_num, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].item_type,
page.num_bytes);
else
printf("\n%s:%d %s '%c' (%d bytes)", hist[cur_page].host,
hist[cur_page].port_num, hist[cur_page].selector,
hist[cur_page].item_type, page.num_bytes);
printf("\n\n");
switch (hist[cur_page].item_type) {
case '1':
if (!is_redraw)
page.num_lines = mk_menu();
pr_menu();
break;
default:
pr_text();
break;
}
get_input:
printf("\ngc> ");
get_input_str(input_str, INPUT_STR_SIZE);
if (input_str[0] == 'q') {
/* "quit" command, exit the program */
goto end;
} else if (input_str[0] == 'd') {
/* "(re)draw" page, starting over from the top */
is_redraw = 1;
goto redraw;
} else if (input_str[0] == 'r') {
/* is this a favorites menu or history list menu? If so, redraw it */
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
is_redraw = 1;
goto redraw;
}
/* "refresh" page by downloading it from server again */
hist[cur_page].force_request = 1;
goto restart;
} else if (input_str[0] == 'b') {
/* "back" command, move to previous page */
temp = cur_page;
hist_prev(auto_kill);
/* cur_page unchanged if at start when history buffer not yet full */
if (temp == cur_page) {
printf("At start, cannot go back!\n");
goto get_input;
}
goto restart;
} else if (input_str[0] == 'h') {
/* "home" command, move to root menu */
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
printf("Cannot go 'home' from a favorites or history menu!\n");
goto get_input;
}
hist_next(auto_kill);
hist[cur_page].item_type = '1';
hist[cur_page].selector[0] = '\0';
hist[cur_page].use_selector = 0;
if (hlist_enabled)
hl_new_ml();
goto restart;
} else if (input_str[0] == 's' && input_str[1] == '\0') {
/* "save" command to store the currently displayed page */
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
printf("Favorites and history menus already saved as file!\n");
goto get_input;
}
if (hist[cur_page].item_type == '0') {
/* save text directly from current page buffer */
hist[cur_page].save_to_disk = 2;
goto save_page_buf;
}
/* can't save from buffer, download from server */
hist_next(auto_kill);
hist[cur_page].save_to_disk = 1;
if (hist[cur_page].selector[0] == '\0')
hist[cur_page].use_selector = 0;
else
hist[cur_page].use_selector = 1;
goto restart;
} else if (input_str[0] == 's' && hist[cur_page].item_type == '1') {
/* "save" command with link number argument */
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
printf("Error: no link number found!\n", i);
goto get_input;
}
temp = atoi(temp_str);
if (temp > 0 && temp <= page.max_linknum) {
for (i = 0; i < page.num_lines; i++) {
if (page.menu[i].link_num == temp)
break;
}
if (!is_saveable(page.menu[i].item_type)) {
printf("Error: item type '%c' cannot be saved to disk!\n",
page.menu[i].item_type);
goto get_input;
}
hist_next(auto_kill);
/* although not sanctioned by RFC 1436, some gopher holes
have dotted-quad IP addresses in menus vice host names
*/
hist[cur_page].is_ip_addr = is_ip_addr(page.menu[i].host);
strcpy(hist[cur_page].host, page.menu[i].host);
strcpy(hist[cur_page].selector, page.menu[i].selector);
hist[cur_page].port_num = atoi(page.menu[i].port_num);
hist[cur_page].item_type = page.menu[i].item_type;
hist[cur_page].save_to_disk = 1;
if (hist[cur_page].selector[0] == '\0')
hist[cur_page].use_selector = 0;
else
hist[cur_page].use_selector = 1;
if (hlist_enabled)
hl_copy_ml(i);
goto restart;
} else {
printf("Error: invalid link number!\n");
goto get_input;
}
} else if (input_str[0] == 'f' && hist[cur_page].item_type == '1') {
/* "foward" command with a link number argument */
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
printf("Error: no link number found!\n", i);
goto get_input;
}
temp = atoi(temp_str);
if (temp > 0 && temp <= page.max_linknum) {
for (i = 0; i < page.num_lines; i++) {
if (page.menu[i].link_num == temp)
break;
}
if (!is_displayable(page.menu[i].item_type)) {
printf("Error: item type '%c' cannot be displayed!\n",
page.menu[i].item_type);
goto get_input;
}
hist_next(auto_kill);
/* although not sanctioned by RFC 1436, some gopher holes
have dotted-quad IP addresses in menus vice host names
*/
hist[cur_page].is_ip_addr = is_ip_addr(page.menu[i].host);
strcpy(hist[cur_page].host, page.menu[i].host);
strcpy(hist[cur_page].selector, page.menu[i].selector);
hist[cur_page].port_num = atoi(page.menu[i].port_num);
hist[cur_page].item_type = page.menu[i].item_type;
if (page.menu[i].item_type == '7')
hist[cur_page].is_query = 1;
if (hist[cur_page].selector[0] == '\0')
hist[cur_page].use_selector = 0;
else
hist[cur_page].use_selector = 1;
/* if history list is enabled, make a copy of the menu line */
if (hlist_enabled)
hl_copy_ml(i);
goto restart;
} else {
printf("Error: invalid link number!\n");
goto get_input;
}
} else if (input_str[0] == 'g') {
/* "go" command with host arg. May be followed by optional
port number and selector arguments
*/
hist_next(auto_kill);
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
printf("Error: host name not found!\n");
goto get_input;
} else {
hist[cur_page].is_ip_addr = is_ip_addr(temp_str);
strncpy(hist[cur_page].host, temp_str, HOST_STR_SIZE);
hist[cur_page].host[HOST_STR_SIZE] = '\0';
hist[cur_page].port_num = 70;
hist[cur_page].selector[0] = '\0';
hist[cur_page].use_selector = 0;
hist[cur_page].item_type = '1';
/* is a port number given? */
temp_str = strtok(NULL, " \t");
if (temp_str != NULL) {
if (isdigit(*temp_str)) {
temp = atoi(temp_str);
if (temp > 0)
hist[cur_page].port_num = temp;
/* does a selector follow the port number? */
temp_str = strtok(NULL, " \t");
if (temp_str != NULL) {
strncpy(hist[cur_page].selector,
temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
hist[cur_page].use_selector = 1;
hist[cur_page].item_type =
guess_item_type(hist[cur_page].selector);
}
} else {
/* not a port number so assume it's a selector */
strncpy(hist[cur_page].selector,
temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
hist[cur_page].use_selector = 1;
hist[cur_page].item_type =
guess_item_type(hist[cur_page].selector);
}
}
}
/* if history list is enabled, create a menu line */
if (hlist_enabled)
hl_new_ml();
goto restart;
} else if (input_str[0] == 'a') {
/* "add" command to add page to favorites with optional file name
argument to specify the menu file. Default is DEF_FAVS_NAME.
*/
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
printf("Cannot add favorites or history menu to favorites!\n");
goto get_input;
}
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
add2favs(favs_fname);
} else {
if (strlen(temp_str) > MAX_FILENAME_SIZE)
temp_str[MAX_FILENAME_SIZE] = '\0';
add2favs(temp_str);
}
is_redraw = 1;
goto redraw;
} else if (input_str[0] == 'v') {
/* "view" command to open favorites menu with optional file name
argument to specify the menu file. Default is DEF_FAVS_FNAME.
*/
hist_next(auto_kill);
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
strcpy(hist[cur_page].selector, favs_fname);
} else {
strncpy(hist[cur_page].selector, temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
}
strcpy(hist[cur_page].host, "localhost");
hist[cur_page].port_num = 70;
hist[cur_page].item_type = '1';
hist[cur_page].is_ip_addr = 0;
hist[cur_page].is_fav_menu = 1;
hist[cur_page].is_hlist_menu = 0;
goto restart;
} else if (input_str[0] == 'l') {
/* History "list" command to open history menu with optional file name
argument to specify the menu file. Default is DEF_HLIST_FNAME.
*/
if (!hlist_enabled) {
printf("History list is disabled!\n");
goto get_input;
}
hist_next(auto_kill);
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
strcpy(hist[cur_page].selector, hlist_fname);
} else {
strncpy(hist[cur_page].selector, temp_str, SELECTOR_STR_SIZE);
hist[cur_page].selector[SELECTOR_STR_SIZE] = '\0';
}
strcpy(hist[cur_page].host, "localhost");
hist[cur_page].port_num = 70;
hist[cur_page].item_type = '1';
hist[cur_page].is_ip_addr = 0;
hist[cur_page].is_fav_menu = 0;
hist[cur_page].is_hlist_menu = 1;
goto restart;
} else if (input_str[0] == 'i' && input_str[1] == '\0') {
/* "info" command to print info about currently displayed page */
if (hist[cur_page].is_query)
printf("%s:%d %s?%s '%c' (%d bytes)", hist[cur_page].host,
hist[cur_page].port_num, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].item_type,
page.num_bytes);
else
printf("%s:%d %s '%c' (%d bytes)", hist[cur_page].host,
hist[cur_page].port_num, hist[cur_page].selector,
hist[cur_page].item_type, page.num_bytes);
if (!hist[cur_page].is_fav_menu && !hist[cur_page].is_hlist_menu) {
/* print cache file name */
temp = test_cache(hist[cur_page].host, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].is_query);
if (temp != -1)
printf(" [%s%06d.gcc]", cfile_dir, temp);
else
printf("[----------]");
}
printf("\n");
goto get_input;
} else if (input_str[0] == 'i' && hist[cur_page].item_type == '1') {
/* "info" command with link number argument */
temp_str = strtok(&input_str[1], " \t");
if (temp_str == NULL) {
printf("Error: no link number found!\n", i);
goto get_input;
}
temp = atoi(temp_str);
if (temp > 0 && temp <= page.max_linknum) {
for (i = 0; i < page.num_lines; i++) {
if (page.menu[i].link_num == temp)
break;
}
printf("%s:%d %s '%c'",
page.menu[i].host, atoi(page.menu[i].port_num),
page.menu[i].selector, page.menu[i].item_type);
} else {
printf("Error: invalid link number!\n");
goto get_input;
}
if (page.menu[i].item_type == '0' || page.menu[i].item_type == '1') {
/* if menu item is text or menu type, print cache file name */
temp = test_cache(page.menu[i].host, page.menu[i].selector, "", 0);
if (temp != -1)
printf(" [%s%06d.gcc]", cfile_dir, temp);
else
printf("[----------]");
}
printf("\n");
goto get_input;
} else if (input_str[0] == 'c') {
/* "cache" command to force caching of page */
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
printf("Cannot cache favorites or history menu!\n");
goto get_input;
}
/* cache current page; useful when gc not run with cache enabled */
temp = cache_page(cur_page);
if (temp != -1) {
sprintf(req_buffer, "%s%06d.gcc", cfile_dir, temp);
printf("Cache file %s created!\n", req_buffer);
hist[cur_page].force_request = 0;
} else {
printf("Error %d: failed to create cache file %s!\n",
errno, req_buffer);
}
goto get_input;
} else if (input_str[0] == 'k') {
/* "kill" command to delete cached copy of page */
if (hist[cur_page].is_fav_menu || hist[cur_page].is_hlist_menu) {
printf("Cannot kill favorites or history menu!\n");
goto get_input;
}
/* "kill" command to delete cache file from disk */
temp = test_cache(hist[cur_page].host, hist[cur_page].selector,
hist[cur_page].query, hist[cur_page].is_query);
if (temp != -1) {
sprintf(req_buffer, "%s%06d.gcc", cfile_dir, temp);
printf("Delete cache file %s. Are you sure? [y/N]\n",
req_buffer);
get_input_str(input_str, 2);
if (input_str[0] != 'y' && input_str[0] != 'Y') {
printf("Cache file deletion canceled!\n");
goto get_input;
}
if (unlink(req_buffer) == -1)
printf("Error %d: failed to delete cache file %s!\n",
errno, req_buffer);
else
printf("Cache file %s deleted!\n", req_buffer);
} else {
printf("No cache file found for this page!\n");
}
goto get_input;
} else if (input_str[0] == '?') {
/* print help text */
printf("q (q)uit d re(d)raw page ");
printf("r (r)eload page\n");
printf("h go to (h)ome page b go (b)ack one page ");
printf("fN go (f)orward to link N\n");
printf("g host [port] [sel] (g)o i (i)nfo this page ");
printf("iN (i)nfo link N\n");
printf("a [file] (a)dd to favs v [file] (v)iew favs ");
printf("l [file] view history (l)ist\n");
printf("s (s)ave current page sN (s)ave page link N ");
printf("k (k)ill current page cache\n");
printf("? print this message\n");
goto get_input;
} else {
goto get_input;
}
end:
if (page.menu != NULL)
free((MENU_LINE *)page.menu);
if (page.buf != NULL)
free((char *)page.buf);
exit(0);
}
.