..
/
download
/* Bucky: Gopher <-> HTTP proxy for Bucktooth server */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <resolv.h>
#include "gopher.h"
#include "config.h"
#define BUCKDCOM BUCKD" -isproxy -proxy=2F%s -proxyip=%s"
#define BUCKDCOM_MAXLEN 1024
#define HTMLSTR_MAXLEN 512
#define URLSTR_MAXLEN 512
/* Copy and hex encode a string.
* This will always require hs to be able to hold a string of at least strlen(str) * 2 chars.
*/
void hexstrcpy(char *hs, char *str)
{
size_t i;
for (i = 0; str[i]; i++) {
sprintf(hs + i * 2, "%.2x", str[i]);
}
}
/* Copy a string, making it HTML-safe */
void htmlstrncpy(char *html, char *str, size_t num)
{
size_t i, j;
for (i = 0, j = 0; str[i] && j < num; i++, j++) {
if (str[i] == '<') {
strncpy(html + j, "<", 4);
j += 3;
} else if (str[i] == '>') {
strncpy(html + j, ">", 4);
j += 3;
} else if (str[i] == ' ') {
strncpy(html + j, " ", 6);
j += 5;
} else if (str[i] == '"') {
strncpy(html + j, """, 6);
j += 5;
} else {
html[j] = str[i];
}
}
html[j] = '\0';
}
/* Copy a string, making it URL-safe */
void urlstrncpy(char *url, char *str, size_t num)
{
size_t i, j;
for (i = 0, j = 0; str[i] && j < num; i++, j++) {
if (str[i] == '?') {
strncpy(url + j, "%3F", 3);
j += 2;
} else if (str[i] == '\t') {
strncpy(url + j, "%09", 3);
j += 2;
} else if (str[i] == '#') {
strncpy(url + j, "%23", 3);
j += 2;
} else if (str[i] == '<') {
strncpy(url + j, "%3C;", 3);
j += 2;
} else if (str[i] == '>') {
strncpy(url + j, "%3E", 3);
j += 2;
} else if (str[i] == '"') {
strncpy(url + j, "%22", 3);
j += 2;
} else {
url[j] = str[i];
}
}
url[j] = '\0';
}
/* Copy a URL-safe string, unescaping it */
void unurlstrncpy(char *str, char *url, size_t num)
{
size_t i, j;
for (i = 0, j = 0; url[i] && j < num; ++i, ++j) {
if (url[i] == '+') {
str[j] = ' ';
} else if (strncmp(url + i, "%3A", 3) == 0) {
str[j] = ':';
i += 2;
} else if (strncmp(url + i, "%2F", 3) == 0) {
str[j] = '/';
i += 2;
} else {
str[j] = url[i];
}
}
str[j] = '\0';
}
/* Read up to next occurence of character */
int read_to(FILE *buckd, char str[], int n, char to)
{
int i, c;
for (i = 0; (c = fgetc(buckd)) != to && i < n; i++) {
str[i] = c;
}
str[i] = '\0';
return i;
}
/* Print a menu item as HTML */
void print_menu_item(char type, char *display, char *selector, char *host, unsigned port)
{
char html_display[HTMLSTR_MAXLEN], url_string[URLSTR_MAXLEN];
/* Escape naughty characters in the display string so it's HTML-friendly */
htmlstrncpy(html_display, display, HTMLSTR_MAXLEN);
/* Encode naughty characters in the URL */
urlstrncpy(url_string, selector, URLSTR_MAXLEN);
printf("<tr>");
/* Inline resources (info, error) */
if (type == GOPHER_ITEM_INFO) {
printf("<td class=\"info-icon\"></td><td class=\"info\">");
printf("<tt class=\"info\">");
if (strcmp(html_display, "") == 0) {
printf("<br>");
} else {
printf("<nobr>%s</nobr>", html_display);
}
printf("</tt>");
} else if (type == GOPHER_ITEM_ERROR) {
printf("<td class=\"error-icon\">");
#ifdef GOPHER_ICONS
printf("<img class=\"icon\" src=\"%s\" alt=\"%s\">", gopher_item_icon(type), gopher_item_icon_alt(type));
#else
#ifdef TT_LINKS
printf("<tt>%s</tt>", gopher_item_icon_alt(type));
#else
printf("<div>%s</div>", gopher_item_icon_alt(type));
#endif
#endif
printf("</td><td class=\"error\" valign=\"bottom\">");
#ifdef TT_LINKS
printf("<tt class=\"error\">");
#else
printf("<div class=\"error\">");
#endif
printf("<nobr>%s</nobr>", html_display);
#ifdef TT_LINKS
printf("</tt>");
#else
printf("</div>");
#endif
/* If the resource is external, provide a gopher:// URL */
} else if (strcmp(host, MY_HOST) != 0 || port != MY_PORT) {
printf("<td class=\"res-icon\">");
if (port != DEFAULT_GOPHER_PORT) {
printf("<a href=\"gopher://%s:%d/%c%s\">", host, port, type, url_string);
} else {
printf("<a href=\"gopher://%s/%c%s\">", host, type, url_string);
}
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"%s\">", gopher_item_icon(type), gopher_item_icon_alt(type));
#else
#ifdef TT_LINKS
printf("<tt>%s</tt>", gopher_item_icon_alt(type));
#else
printf("<div>%s</div>", gopher_item_icon_alt(type));
#endif
#endif
printf("</a></td><td class=\"res\" valign=\"bottom\">");
#ifdef TT_LINKS
printf("<tt class=\"res\">");
#else
printf("<div class=\"res\">");
#endif
if (port != DEFAULT_GOPHER_PORT) {
printf("<a href=\"gopher://%s:%d/%c%s\">", host, port, type, url_string);
} else {
printf("<a href=\"gopher://%s/%c%s\">", host, type, url_string);
}
printf("%s", html_display);
printf("</a>");
#ifdef TT_LINKS
printf("</tt>");
#else
printf("</div>");
#endif
/* Local resources get a relative URI.
* If USE_REWRITE is enabled, this is relative to REWRITE_ROOT
* otherwise, it is given as a QUERY_STRING to the CGI script
*/
} else {
printf("<td class=\"res-icon\">");
/* Resources with special URIs */
if (type == GOPHER_ITEM_TELNET) {
printf("<a href=\"telnet://%s:%d\">", host, port);
} else if (type == GOPHER_ITEM_TN3270) {
printf("<a href=\"tn3270://%s:%d\">", host, port);
} else if (type == GOPHER_ITEM_HTML && strncmp(url_string, "URL:", 4) == 0) {
printf("<a href=\"%s\">", url_string + 4);
} else {
#ifdef USE_REWRITE
printf("<a href=\"%s%c%s\">", REWRITE_ROOT, type, url_string);
#else
printf("<a href=\"?%c%s\">", type, url_string);
#endif
}
if (type == GOPHER_ITEM_HTML && strncmp(url_string, "URL:", 4) == 0) {
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"%s\">", GOPHER_ICON_ROOT"/"GOPHER_HTTP_ICON, GOPHER_HTTP_ALT);
#else
#ifdef TT_LINKS
printf("<tt>%s</tt>", GOPHER_HTTP_ALT);
#else
printf("<div>%s</div>", GOPHER_HTTP_ALT);
#endif
#endif
#ifdef INLINE_PICS
} else if (inline_pic(type)) {
/* No icon */
#endif
} else {
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"%s\">", gopher_item_icon(type), gopher_item_icon_alt(type));
#else
#ifdef TT_LINKS
printf("<tt>%s</tt>", gopher_item_icon_alt(type));
#else
printf("<div>%s</div>", gopher_item_icon_alt(type));
#endif
#endif
}
printf("</a></td><td class=\"res\" valign=\"bottom\">");
#ifdef TT_LINKS
printf("<tt class=\"res\">");
#else
printf("<div class=\"res\">");
#endif
/* Resources with special URIs */
if (type == GOPHER_ITEM_TELNET) {
printf("<a href=\"telnet://%s:%d\">", host, port);
} else if (type == GOPHER_ITEM_TN3270) {
printf("<a href=\"tn3270://%s:%d\">", host, port);
} else if (type == GOPHER_ITEM_HTML && strncmp(url_string, "URL:", 4) == 0) {
printf("<a href=\"%s\">", url_string + 4);
} else {
#ifdef USE_REWRITE
printf("<a href=\"%s%c%s\">", REWRITE_ROOT, type, url_string);
#else
printf("<a href=\"?%c%s\">", type, url_string);
#endif
}
#ifdef INLINE_PICS
if (inline_pic(type)) {
#ifdef USE_REWRITE
printf("<img title=\"%s\" src=\"%s%c%s\"", html_display, REWRITE_ROOT,
type, url_string);
#else
printf("<img title=\"%s\" src=\"?%c%s\"", html_display, type, url_string);
#endif
#ifdef INLINE_PIC_WIDTH
printf(" width=\"%d\"", INLINE_PIC_WIDTH);
#endif
printf(">");
} else {
printf("%s", html_display);
}
#else
printf("%s", html_display);
#endif
printf("</a>");
#ifdef INLINE_SEARCH
if (type == GOPHER_ITEM_SEARCH) {
printf(" ");
#ifdef USE_REWRITE
printf("<form class=\"search\" action=\"%s\">", REWRITE_ROOT);
#else
printf("<form class=\"search\">");
#endif
printf("<input type=\"hidden\" name=\"SEARCH\" value=\"%s\">", selector);
printf("<input type=\"text\" name=\"Q\"> ");
printf("<input type=\"submit\">");
printf("</form>");
}
#endif
#ifdef TT_LINKS
printf("</tt>");
#else
printf("</div>");
#endif
}
printf("</td></tr>\r\n");
}
/* Read output from buckd and parse a line of a menu.
* Returns 1 if there are more lines to parse,
* or 0 if the end of the menu has been reached.
*/
int handle_menu_line(FILE *buckd)
{
char display[DISPLAY_MAXLEN];
char selector[SELECTOR_MAXLEN];
char host[HOST_MAXLEN];
char port[PORT_MAXLEN];
int c;
char type;
c = fgetc(buckd);
if (c == '.' || c == EOF) {
return 0;
} else {
type = c;
}
/* Ignore empty/malformed lines */
if (type == '\n') {
return 1;
} else if (!isspace(type)) {
read_to(buckd, display, DISPLAY_MAXLEN, '\t');
read_to(buckd, selector, SELECTOR_MAXLEN, '\t');
read_to(buckd, host, HOST_MAXLEN, '\t');
read_to(buckd, port, PORT_MAXLEN, '\r');
print_menu_item(type, display, selector, host, atoi(port));
}
while ((c = fgetc(buckd)) != '\n') if (c == EOF) break;
return 1;
}
#ifdef SHOW_NAVBARS
/* Optional navigation bar */
void print_top_nav(char type, char *selector)
{
int len, c;
int is_root = strcmp(selector, "/") == 0;
if (is_root) return;
len = strlen(selector);
#ifdef TT_LINKS
printf("<tt class=\"nav\" id=\"topnav\">\r\n");
#else
printf("<div class=\"nav\" id=\"topnav\">\r\n");
#endif
if (selector[len - 1] == '/') {
c = len - 2;
} else {
c = len - 1;
}
while (selector[c] != '/' && c > 0) c--;
printf("<a id=\"navup\" title=\"go up a directory\" ");
#ifdef USE_REWRITE
if (strncmp(selector, "/", c) == 0) {
printf("href=\"%s\">", REWRITE_ROOT);
} else {
printf("href=\"%s1/%.*s\">", REWRITE_ROOT, c, selector);
}
#else
printf("href=\"?1/%.*s\">", c, selector);
#endif
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"/\"> ", gopher_item_icon(GOPHER_ITEM_DIRECTORY));
#endif
printf("..");
printf("</a>\r\n");
printf("<a id=\"navroot\" title=\"go to root of server\" ");
#ifdef USE_REWRITE
printf("href=\"%s\">", REWRITE_ROOT);
#else
printf("href=\"?\">");
#endif
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"/\"> ", gopher_item_icon(GOPHER_ITEM_DIRECTORY));
#endif
printf("/");
printf("</a>\r\n");
#ifdef HTML_TEXT
if (type == GOPHER_ITEM_PLAIN_TEXT) {
printf("<a id=\"navdl\" title=\"download\" ");
#ifdef USE_REWRITE
printf("href=\"%s?DOWNLOAD=%s\">", REWRITE_ROOT, selector);
#else
printf("href=\"?DOWNLOAD=%s\">", selector);
#endif
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"->\"> ", gopher_item_icon(type));
#endif
printf("download");
printf("</a>\r\n");
}
#endif
custom_navbar_top(type, selector);
#ifdef TT_LINKS
printf("</tt>\r\n");
#else
printf("</div>\r\n");
#endif
printf("<hr>\r\n");
}
void print_bottom_nav(char type, char *selector)
{
char url[URLSTR_MAXLEN];
int is_root = strcmp(selector, "/") == 0;
urlstrncpy(url, selector, URLSTR_MAXLEN);
printf("<hr>\r\n");
#ifdef TT_LINKS
printf("<tt class=\"nav\" id=\"bottomnav\">\r\n");
#else
printf("<div class=\"nav\" id=\"bottomnav\">\r\n");
#endif
if (is_root) {
if (MY_PORT == DEFAULT_GOPHER_PORT) {
printf("<a href=\"gopher://%s\" title=\"view with Gopher client\">", MY_HOST);
} else {
printf("<a href=\"gopher://%s:%d\" title=\"view with Gopher client\">", MY_HOST, MY_PORT);
}
} else {
if (MY_PORT == DEFAULT_GOPHER_PORT) {
printf("<a href=\"gopher://%s/%c/%s\" title=\"view with Gopher client\">", MY_HOST, type, url);
} else {
printf("<a href=\"gopher://%s:%d/%c/%s\" title=\"view with Gopher client\">", MY_HOST, MY_PORT, type, url);
}
}
#ifdef GOPHER_ICONS
printf("<img class=\"gicon\" src=\"%s\" alt=\"/\"> ", gopher_item_icon(GOPHER_ITEM_DIRECTORY));
#endif
if (is_root) {
if (MY_PORT == DEFAULT_GOPHER_PORT) {
printf("gopher://%s", MY_HOST);
} else {
printf("gopher://%s:%d", MY_HOST, MY_PORT);
}
} else {
if (MY_PORT == DEFAULT_GOPHER_PORT) {
printf("gopher://%s/%c/%s", MY_HOST, type, url);
} else {
printf("gopher://%s:%d/%c/%s", MY_HOST, MY_PORT, type, url);
}
}
printf("</a>\r\n");
custom_navbar_bottom(type, selector);
#ifdef TT_LINKS
printf("</tt>\r\n");
#else
printf("</div>\r\n");
#endif
}
#endif
/* Textfiles (plain text, type 0) will have a final full-stop (.) line */
void handle_textfile(FILE* buckd, int esc)
{
int c, i;
int next[3];
while ((c = fgetc(buckd)) != EOF) {
if (c == '.') {
for (i = 0; i < 3; i++) {
next[i] = fgetc(buckd);
}
if (next[0] == '\r' && next[1] == '\n' && next[2] == EOF) {
break;
} else {
for (i = 2; i >= 0; i--) {
ungetc(next[i], buckd);
}
}
}
if (esc) {
switch (c) {
case '<': printf("<"); break;
case '>': printf(">"); break;
default: putchar(c); break;
}
} else {
putchar(c);
}
}
}
/* Files should be sent as-is with an appropriate MIME type */
void handle_file(FILE *buckd, char response_type, char *selector, int download)
{
int c;
char *ext;
printf("Content-type: ");
/* for binary and Gopher+ Picture/Movie/Sound, attempt to guess the MIME type from the extension */
if (response_type == GOPHER_ITEM_BINARY ||
response_type == GOPHER_ITEM_MOVIE ||
response_type == GOPHER_ITEM_PICTURE ||
response_type == GOPHER_ITEM_SOUND)
{
ext = strrchr(selector, '.');
if (!ext) ext = "";
printf("%s; charset=binary", mime_type_from_ext(ext));
} else {
printf("%s; charset=utf-8", mime_type(response_type));
}
printf("\r\n");
if (download) {
char *fname;
fname = strrchr(selector, '/');
fname = fname ? fname + 1 : selector;
printf("Content-disposition: attachment; filename=\"%s\"\r\n", fname);
}
printf("\r\n");
if (response_type == GOPHER_ITEM_PLAIN_TEXT || response_type == GOPHER_ITEM_XML) {
/* Do not show the final terminating full-stop returned by the server */
handle_textfile(buckd, 0);
} else {
while ((c = fgetc(buckd)) != EOF) {
putchar(c);
}
}
}
/* Handle output from buckd based on the item type and selector requested */
void handle_buckd(FILE *buckd, char response_type, char *selector, int download)
{
#ifdef HTML_TEXT
if (response_type == GOPHER_ITEM_DIRECTORY || response_type == GOPHER_ITEM_SEARCH || (response_type == GOPHER_ITEM_PLAIN_TEXT && !download)) {
#else
if (response_type == GOPHER_ITEM_DIRECTORY || response_type == GOPHER_ITEM_SEARCH) {
#endif
printf("Content-type: text/html; charset=utf-8\r\n\r\n");
printf("<html>\r\n");
printf("<head>\r\n");
printf("<meta charset=\"utf-8\">\r\n");
printf("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n");
#ifdef EXTERNAL_CSS
printf("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">", EXTERNAL_CSS);
#endif
if (strcmp(selector, "/") == 0)
printf("<title>%s</title>\r\n", MY_HOST);
else
printf("<title>%s/%c/%s</title>\r\n", MY_HOST, response_type, selector);
printf("</head>\r\n");
printf("<body>\r\n");
#ifdef SHOW_NAVBARS
print_top_nav(response_type, selector);
#endif
#ifdef HTML_TEXT
if (response_type == GOPHER_ITEM_PLAIN_TEXT) {
printf("<pre>");
handle_textfile(buckd, 1);
printf("</pre>");
} else
#endif
if (response_type == GOPHER_ITEM_DIRECTORY || strstr(selector, "\t") || strstr(selector, "?")) {
printf("<table border=\"0\" cellpadding=\"1\" cellspacing=\"0\">\r\n");
while (handle_menu_line(buckd));
printf("</table>\r\n");
} else {
printf("<tt class=\"queryTitle\">Enter query:</tt>\r\n");
#ifdef USE_REWRITE
printf("<form action=\"%s\">\r\n", REWRITE_ROOT);
#else
printf("<form>\r\n");
#endif
printf("<input type=\"hidden\" name=\"SEARCH\" value=\"%s\">\r\n", selector);
printf("<input type=\"text\" name=\"Q\" autofocus>\r\n");
printf("<input type=\"submit\">\r\n");
printf("</form>\r\n");
}
#ifdef SHOW_NAVBARS
print_bottom_nav(response_type, selector);
#endif
printf("</body>\r\n");
printf("</html>\r\n");
} else {
handle_file(buckd, response_type, selector, download);
}
}
/* Call Bucktooth daemon and return a FILE pointer to the process */
FILE *popen_buckd(char *req)
{
char *ip = getenv("REMOTE_ADDR");
char com[BUCKDCOM_MAXLEN];
snprintf(com, BUCKDCOM_MAXLEN, BUCKDCOM, req, ip);
return popen(com, "r");
}
FILE *open_socket(char *req)
{
int sockfd;
struct sockaddr_in addr;
struct hostent *server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
server = gethostbyname(MY_HOST);
addr.sin_family = AF_INET;
addr.sin_port = htons(MY_PORT);
bcopy(server->h_addr, &addr.sin_addr.s_addr, server->h_length);
connect(sockfd, (struct sockaddr *) &addr, sizeof(addr));
if (write(sockfd, req, strlen(req)) == -1) {
return NULL;
}
if (write(sockfd, "\r\n", 2) == -1) {
return NULL;
}
return fdopen(sockfd, "r");
}
int main(void)
{
char *query_string, *type, *selector, *search_res, *search_q;
char search_sel[SELECTOR_MAXLEN], search_params[URLSTR_MAXLEN];
#ifndef USE_SOCKETS
char hs[SELECTOR_MAXLEN * 2];
#endif
FILE *fp;
int download = 0;
/* QUERY_STRING will be in the form of #/SELECTOR */
query_string = getenv("QUERY_STRING");
/* ...EXCEPT if its a special SEARCH query (needed to use standard HTML form for queries) */
if (strncmp(query_string, "SEARCH=", 7) == 0) {
type = "7";
search_res = strtok(query_string + 7, "&");
strtok(NULL, "=");
search_q = strtok(NULL, "");
if (!search_q) search_q = "";
unurlstrncpy(search_params, search_q, URLSTR_MAXLEN);
snprintf(search_sel, SELECTOR_MAXLEN, "%s\t%s", search_res, search_params);
selector = search_sel;
#ifdef HTML_TEXT
/* ... or the special DOWNLOAD action (for text files displayed as HTML) */
} else if (strncmp(query_string, "DOWNLOAD=", 9) == 0) {
type = "0";
search_res = strtok(query_string + 9, "");
unurlstrncpy(search_params, search_res, URLSTR_MAXLEN);
snprintf(search_sel, SELECTOR_MAXLEN, "%s", search_res);
selector = search_sel;
download = 1;
#endif
} else {
type = strtok(query_string, "/");
selector = strtok(NULL, "");
}
/* Default to root menu */
if (!type) type = "1";
if (!selector) selector = "/";
#ifdef USE_SOCKETS
fp = open_socket(selector);
handle_buckd(fp, *type, selector, download);
fclose(fp);
#else
/* buckd's -proxy flag takes a hex-encoded selector */
hexstrcpy(hs, selector);
fp = popen_buckd(hs);
handle_buckd(fp, *type, selector, download);
pclose(fp);
#endif
return 0;
}
gopher://khzae.net/0/bucky/bucky.c