#include #include #include #include #include #include #include #include #include "dpi.h" #include "io.h" static void respond_errstr(const char *errstr, const char *fmt, ...) { va_list ap; int err = errno; va_start(ap, fmt); dpi_send_header(NULL, "text/plain"); vprintf(fmt, ap); printf(": %s", errstr ? errstr : strerror(err)); va_end(ap); } static void respond_err(const char *fmt, ...) { va_list ap; int err = errno; va_start(ap, fmt); dpi_send_header(NULL, "text/plain"); vprintf(fmt, ap); printf(": %s", strerror(err)); va_end(ap); } static void respond_errx(const char *fmt, ...) { va_list ap; int err = errno; va_start(ap, fmt); dpi_send_header(NULL, "text/plain"); vprintf(fmt, ap); va_end(ap); } static int parse_type_path(const char *url, char *type, char *path, size_t path_len) { if (!*url) { *path = '\0'; *type = '\0'; return 0; } *type = *url++; while (path_len > 0 && *url != '\0') { *path++ = *url++; path_len--; } if (path_len <= 0) { errno = EMSGSIZE; return -1; } *path = '\0'; return 0; } static int parse_port_type_path(const char *url, char *port, size_t port_len, char *type, char *path, size_t path_len) { while (port_len > 0 && *url != '\0' && *url != '/') { *port++ = *url++; port_len--; } if (port_len <= 0) { errno = EMSGSIZE; return -1; } *port = '\0'; if (*url == '/') return parse_type_path(url + 1, type, path, path_len); *path = '\0'; *type = '\0'; return 0; } static int parse_url(const char *url, char *host, size_t host_len, char *port, size_t port_len, char *type, char *path, size_t path_len) { if (!strncmp(url, "gopher:", 7)) url += 7; if (!strncmp(url, "//", 2)) url += 2; while (host_len > 0 && *url != '\0' && *url != '/' && *url != ':') { *host++ = *url++; host_len--; } if (host_len <= 0) { errno = EMSGSIZE; return -1; } *host = '\0'; if (*url == ':') return parse_port_type_path(url + 1, port, port_len, type, path, path_len); *port = '\0'; if (*url == '/') return parse_type_path(url + 1, type, path, path_len); *path = '\0'; return 0; } static void parse_line(char *line, char *type, char **title, char **selector, char **host, char **port) { *type = *line; if (!*line) { *title = *selector = *host = *port = NULL; return; } *title = ++line; while (*line && *line != '\t') line++; if (!*line) { *selector = *host = *port = NULL; return; } *line++ = '\0'; *selector = line; while (*line && *line != '\t') line++; if (!*line) { *host = *port = NULL; return; } *line++ = '\0'; *host = line; while (*line && *line != '\t') line++; if (!*line) { *port = NULL; return; } *line++ = '\0'; *port = line; while (*line && *line >= '0' && *line <= '9') line++; *line = '\0'; } static int putchar_htmlenc(char c) { switch (c) { case '<': return printf("<"); case '>': return printf(">"); case '"': return printf("""); default: return putchar(c); } } static void print_htmlenc(const char *str) { char c; while ((c = *str++)) putchar_htmlenc(c); } static void render_line_info(const char *title) { printf("
");
	print_htmlenc(title);
	printf("
\n"); } static void render_line_unknown(char type, const char *line) { printf(""); putchar_htmlenc(type); printf("
");
	print_htmlenc(line + 1);
	printf("
\n"); } static void render_line_empty() { printf("\n"); } static void render_line_line() { printf("
\n"); } static const char *icon(char type) { switch (type) { case '0': // text return ""; case '1': // menu return ""; case '7': // search return ""; case '5': case '9': // binary return ""; case '8': // telnet return ""; case 'g': case 'p': case 'I': // image return ""; case 's': // audio return ""; case 'h': // web return ""; case ';': // movie return ""; default: return ""; } } static void print_gopher_url_htmlenc(char type, const char *selector, const char *host, const char *port) { printf("gopher://"); if (host) { print_htmlenc(host); } if (port && strcmp(port, "70")) { putchar(':'); print_htmlenc(port); } if (type) { putchar('/'); putchar_htmlenc(type); } if (selector) { print_htmlenc(selector); } } static void render_line_link(char type, const char *title, const char *selector, const char *host, const char *port) { size_t indent = 0; while (*title == ' ') { title++; indent++; } printf("%s
%*s");
	if (title) {
		print_htmlenc(title);
	}
	printf("
\n"); } static void render_line_search(char type, const char *title, const char *selector, const char *host, const char *port) { size_t indent = 0; while (*title == ' ') { title++; indent++; } printf("%s
%*s", indent, "");
	printf("\n%*s
\n"); } static void render_line_telnet(char type, const char *title, const char *host, const char *port) { size_t indent = 0; while (*title == ' ') { title++; indent++; } printf("%s
%*s");
	if (title) {
		print_htmlenc(title);
	}
	printf("
\n"); } static void render_line(char *line, size_t len) { char type, *title, *selector, *host, *port; if (len < 1) return; line[len] = '\0'; printf("\n"); parse_line(line, &type, &title, &selector, &host, &port); switch (type) { case '0': case '1': case '5': case '9': case 'p': case 'I': case 'g': case 's': case 'h': case ';': return render_line_link(type, title, selector, host, port); case '7': return render_line_search(type, title, selector, host, port); case '8': return render_line_telnet(type, title, host, port); case 'i': case '3': return render_line_info(title); case 'E': return render_line_info(line); case '.': return render_line_empty(); case '_': return render_line_line(); default: return render_line_unknown(type, line); } } static void compact_buf(char *buf, size_t start, size_t end) { size_t i; for (i = start; i < end; i++) { buf[i - start] = buf[i]; } } static size_t read_line(char *buf, size_t len) { size_t i; for (i = 0; i < len && buf[i] != '\r' && buf[i] != '\n'; i++); if (buf[i] == '\r' || buf[i] == '\n') { if (i+1 < len && buf[i+1] == '\n') i++; render_line(buf, i); return i+1; } return 0; } static void read_response(int s) { char buf[4096]; size_t len; size_t start = 0; size_t end = 0; int rc; while (1) { /* fill up the buffer */ len = sizeof(buf) - end; rc = read_some(s, buf + end, &len); if (rc < 0 && errno == EPIPE) break; if (rc < 0) return warn("read_some"); end += len; /* read full lines */ while ((len = read_line(buf + start, end - start))) start += len; compact_buf(buf, start, end); end -= start; start = 0; } /* read the partial line */ if (start < end) render_line(buf + start, end - start); } static void read_data(int s) { char buf[4096]; size_t len = sizeof(buf); int rc; while (1) { rc = read_some(s, buf, &len); if (rc < 0 && errno == EPIPE) return; if (rc < 0) return warn("read response"); rc = write_all(0, buf, len); if (rc < 0) return warn("write response"); } } static void render_dir(int s, const char *url) { dpi_send_header(url, "text/html"); printf(""); printf(""); print_htmlenc(url); printf(""); read_response(s); printf("
"); } static void render_text(int s, const char *url) { dpi_send_header(url, "text/plain"); read_data(s); } static void render_binary(int s, const char *url) { // dpi_send_header(url, "application/octet-stream"); dpi_send_header(url, "text/plain"); read_data(s); } static void render_html(int s, const char *url) { dpi_send_header(url, "text/html"); read_data(s); } static const char *detect_image_content_type(const char buf[4]) { if (!strncmp(buf, "GIF8", 4)) return "image/gif"; if (!strncmp(buf, "\x89PNG", 4)) return "image/png"; if (!strncmp(buf, "\xff\xd8", 2)) return "image/jpeg"; return "text/plain"; } static void render_image(int s, const char *url) { int rc; char buf[4]; rc = read_all(s, buf, sizeof(buf)); if (rc < 0) return respond_err("read image"); dpi_send_header(url, detect_image_content_type(buf)); rc = write_all(0, buf, 4); if (rc < 0) return warn("write image"); read_data(s); } static void fix_path(char *path) { char *query, *start, *end; char buf[3]; /* change form input name to query */ start = query = strstr(path, "__gopher__query__="); if (!start) return; end = start + 18; while (*end) { if (*end == '%') { strncpy(buf, end + 1, 2); *start++ = strtoul(buf, NULL, 16); end += 2; } else if (*end == '+') *start++ = ' '; else *start++ = *end; end++; } *start = '\0'; query[-1] = '\t'; } static void respond(const char *url) { int rc; const char *errstr = NULL; char host[1024]; char port[1024]; char type; char path[4096]; int s; rc = parse_url(url, host, sizeof(host), port, sizeof(port), &type, path, sizeof(path)); if (rc < 0) return respond_err("parse_url"); if (!*host) strcpy(host, "127.0.0.1"); if (!*port) strcpy(port, "70"); if (!type) type = '1'; if (*path) fix_path(path); s = tcp_connect(host, port, &errstr); if (s < 0) return respond_errstr(errstr, "tcp_connect"); rc = write_all(s, path, strlen(path)); if (rc < 0) return respond_err("write request"); rc = write_all(s, "\r\n", 2); if (rc < 0) return respond_err("write request end"); switch (type) { case '1': case '7': return render_dir(s, url); case 'g': case 'p': case 'I': return render_image(s, url); case ';': case '9': return render_binary(s, url); case 'h': return render_html(s, url); default: return render_text(s, url); } } int main() { char url[1024]; setvbuf(stdout, NULL, _IONBF, BUFSIZ); dpi_read_request(url, sizeof(url)); respond(url); }