From 1dcb9cf6567145a0d3705b95717ccbf37eac6140 Mon Sep 17 00:00:00 2001 From: bptato Date: Tue, 12 Dec 2023 20:03:44 +0100 Subject: adapter/: re-structure --- Makefile | 24 +- adapter/about/about.nim | 14 - adapter/data/data.nim | 29 -- adapter/finger/cha-finger | 39 --- adapter/format/gmi2html.c | 241 +++++++++++++++ adapter/format/gopher2html.nim | 134 ++++++++ adapter/gemini/gmi2html.c | 241 --------------- adapter/gemini/gmifetch.c | 688 ----------------------------------------- adapter/gopher/gopher2html.nim | 134 -------- adapter/protocol/about.nim | 14 + adapter/protocol/cha-finger | 39 +++ adapter/protocol/data.nim | 29 ++ adapter/protocol/gmifetch.c | 688 +++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1157 insertions(+), 1157 deletions(-) delete mode 100644 adapter/about/about.nim delete mode 100644 adapter/data/data.nim delete mode 100755 adapter/finger/cha-finger create mode 100644 adapter/format/gmi2html.c create mode 100644 adapter/format/gopher2html.nim delete mode 100644 adapter/gemini/gmi2html.c delete mode 100644 adapter/gemini/gmifetch.c delete mode 100644 adapter/gopher/gopher2html.nim create mode 100644 adapter/protocol/about.nim create mode 100755 adapter/protocol/cha-finger create mode 100644 adapter/protocol/data.nim create mode 100644 adapter/protocol/gmifetch.c diff --git a/Makefile b/Makefile index ea0512af..87b117ed 100644 --- a/Makefile +++ b/Makefile @@ -49,27 +49,27 @@ $(OUTDIR_BIN)/cha: lib/libquickjs.a src/*.nim src/**/*.nim res/* res/**/* $(FLAGS) -o:"$(OUTDIR_BIN)/cha" src/main.nim ln -sf "$(OUTDIR)/$(TARGET)/bin/cha" cha -$(OUTDIR_LIBEXEC)/gopher2html: adapter/gopher/gopher2html.nim +$(OUTDIR_LIBEXEC)/gopher2html: adapter/format/gopher2html.nim $(NIMC) $(FLAGS) -o:"$(OUTDIR_LIBEXEC)/gopher2html" \ - adapter/gopher/gopher2html.nim + adapter/format/gopher2html.nim GMIFETCH_CFLAGS = -Wall -Wextra -std=c89 -pedantic -lcrypto -lssl -g -O3 -$(OUTDIR_CGI_BIN)/gmifetch: adapter/gemini/gmifetch.c - $(CC) $(GMIFETCH_CFLAGS) adapter/gemini/gmifetch.c -o "$(OUTDIR_CGI_BIN)/gmifetch" +$(OUTDIR_CGI_BIN)/gmifetch: adapter/protocol/gmifetch.c + $(CC) $(GMIFETCH_CFLAGS) adapter/protocol/gmifetch.c -o "$(OUTDIR_CGI_BIN)/gmifetch" GMI2HTML_CFLAGS = -Wall -Wextra -std=c89 -pedantic -g -O3 -$(OUTDIR_LIBEXEC)/gmi2html: adapter/gemini/gmi2html.c - $(CC) $(GMI2HTML_CFLAGS) adapter/gemini/gmi2html.c -o "$(OUTDIR_LIBEXEC)/gmi2html" +$(OUTDIR_LIBEXEC)/gmi2html: adapter/format/gmi2html.c + $(CC) $(GMI2HTML_CFLAGS) adapter/format/gmi2html.c -o "$(OUTDIR_LIBEXEC)/gmi2html" -$(OUTDIR_CGI_BIN)/cha-finger: adapter/finger/cha-finger +$(OUTDIR_CGI_BIN)/cha-finger: adapter/protocol/cha-finger @mkdir -p $(OUTDIR_CGI_BIN) - cp adapter/finger/cha-finger $(OUTDIR_CGI_BIN) + cp adapter/protocol/cha-finger $(OUTDIR_CGI_BIN) -$(OUTDIR_CGI_BIN)/about: adapter/about/about.nim - $(NIMC) $(FLAGS) -o:"$(OUTDIR_CGI_BIN)/about" adapter/about/about.nim +$(OUTDIR_CGI_BIN)/about: adapter/protocol/about.nim + $(NIMC) $(FLAGS) -o:"$(OUTDIR_CGI_BIN)/about" adapter/protocol/about.nim -$(OUTDIR_CGI_BIN)/data: adapter/data/data.nim - $(NIMC) $(FLAGS) -o:"$(OUTDIR_CGI_BIN)/data" adapter/data/data.nim +$(OUTDIR_CGI_BIN)/data: adapter/protocol/data.nim + $(NIMC) $(FLAGS) -o:"$(OUTDIR_CGI_BIN)/data" adapter/protocol/data.nim CFLAGS = -g -Wall -O2 -DCONFIG_VERSION=\"$(shell cat lib/quickjs/VERSION)\" QJSOBJ = $(OBJDIR)/quickjs diff --git a/adapter/about/about.nim b/adapter/about/about.nim deleted file mode 100644 index 860294f3..00000000 --- a/adapter/about/about.nim +++ /dev/null @@ -1,14 +0,0 @@ -import std/envvars - -const chawan = staticRead"res/chawan.html" -const license = staticRead"res/license.html" - -proc main() = - stdout.write("Content-Type: text/html\n\n") - case getEnv("MAPPED_URI_PATH") - of "blank": stdout.write("") - of "chawan": stdout.write(chawan) - of "license": stdout.write(license) - else: stdout.write("Error: about page not found") - -main() diff --git a/adapter/data/data.nim b/adapter/data/data.nim deleted file mode 100644 index 7b022976..00000000 --- a/adapter/data/data.nim +++ /dev/null @@ -1,29 +0,0 @@ -import std/envvars -import std/base64 -import std/strutils - -import utils/twtstr - -proc main() = - let str = getEnv("MAPPED_URI_PATH") - const si = "data:".len # start index - var ct = str.until(',', si) - for c in ct: - if c notin AsciiAlphaNumeric and c != '/': - stdout.write("Cha-Control: ConnectionError -7 invalid data URL") - return - let sd = si + ct.len + 1 # data start - let body = percentDecode(str, sd) - if ct.endsWith(";base64"): - try: - let d = base64.decode(body) # decode from ct end + 1 - ct.setLen(ct.len - ";base64".len) # remove base64 indicator - stdout.write("Content-Type: " & ct & "\n\n") - stdout.write(d) - except ValueError: - stdout.write("Cha-Control: ConnectionError -7 invalid data URL") - else: - stdout.write("Content-Type: " & ct & "\n\n") - stdout.write(body) - -main() diff --git a/adapter/finger/cha-finger b/adapter/finger/cha-finger deleted file mode 100755 index e963b339..00000000 --- a/adapter/finger/cha-finger +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -# Finger protocol adapter for Chawan. Requires curl. -# (It does *not* work without the environment variables MAPPED_URI_*, so no -# w3m support.) -# -# Usage: put this script in your cgi-bin folder, then add the following line to -# your urimethodmap: -# -# finger: /cgi-bin/cha-finger?%s -# -# Note: the Chawan default configuration already does this, so normally you -# don't need to do anything to use the finger protocol. - -die() { - echo "$1" - exit 1 -} - -type curl >/dev/null || \ - die "curl must be installed on your computer to use finger." - -printf 'Content-Type: text/plain\n\n' - -PORT="${MAPPED_URI_PORT:-79}" -test "$PORT" = 79 || die "Invalid port" - -if test -n "$MAPPED_URI_USERNAME" -then USER="$MAPPED_URI_USERNAME" -else case "$MAPPED_URI_PATH" in - /w*) USER="/w ${MAPPED_URI_PATH#/w}" ;; - *) USER="$MAPPED_URI_PATH" ;; - esac -fi -URL="telnet://$MAPPED_URI_HOST:$PORT" - -printf '%s\r\n' "$USER" | if test -n "$ALL_PROXY" -then curl -x "$ALL_PROXY" -- "$URL" -else curl -- "$URL" -fi 2>/dev/null diff --git a/adapter/format/gmi2html.c b/adapter/format/gmi2html.c new file mode 100644 index 00000000..2c8c2bf6 --- /dev/null +++ b/adapter/format/gmi2html.c @@ -0,0 +1,241 @@ +/* This file is dedicated to the public domain. + * + * Convert gemtext to HTML. Only accepts input on stdin. + */ + +#include +#include + +typedef enum { + STATE_NORMAL, + STATE_BLOCKQUOTE, + STATE_NEWLINE, + STATE_NEWLINE_EQUALS, + STATE_NEWLINE_EQUALS_ARROW, + STATE_BEFORE_URL, + STATE_IN_URL, + STATE_BEFORE_URL_NAME, + STATE_URL_NAME, + STATE_SINGLE_BACKTICK, + STATE_DOUBLE_BACKTICK, + STATE_PRE_START, + STATE_IN_PRE, + STATE_PRE_SINGLE_BACKTICK, + STATE_PRE_DOUBLE_BACKTICK, + STATE_SKIP_LINE, + STATE_HASH, + STATE_DOUBLE_HASH, + STATE_AFTER_HASH, + STATE_AFTER_DOUBLE_HASH, + STATE_AFTER_TRIPLE_HASH +} ParseState; + +static ParseState state = STATE_NEWLINE; +static ParseState prev_state = STATE_NORMAL; + +int main(void) { + int c; +#define BUFSIZE 4096 + char urlbuf[BUFSIZE + 1]; + char *urlp; + + urlp = urlbuf; + printf(""); +#define SET_STATE(s) do { \ + prev_state = state; \ + state = s; \ + } while (0) +#define REDO_NORMAL do { \ + SET_STATE(STATE_NORMAL); \ + goto normal; \ + } while (0) + while ((c = getc(stdin)) != EOF) { + switch (state) { + case STATE_NORMAL: + case STATE_BLOCKQUOTE: + case STATE_IN_PRE: + case STATE_PRE_START: + case STATE_SKIP_LINE: + case STATE_URL_NAME: + case STATE_AFTER_HASH: + case STATE_AFTER_DOUBLE_HASH: + case STATE_AFTER_TRIPLE_HASH: +normal: switch (c) { + case '\r': break; + case '\n': + if (state == STATE_BLOCKQUOTE) { + fputs("", stdout); + } else if (state == STATE_PRE_START) { + fputs("\">", stdout); + SET_STATE(STATE_IN_PRE); + } else if (state == STATE_URL_NAME) { + fputs("", stdout); + fputs("
", stdout); + } else if (state == STATE_AFTER_HASH) { + fputs("", stdout); + } else if (state == STATE_AFTER_DOUBLE_HASH) { + fputs("", stdout); + } else if (state == STATE_AFTER_TRIPLE_HASH) { + fputs("", stdout); + } else if (state == STATE_SKIP_LINE) { + } else { + fputs("
", stdout); + } + SET_STATE(STATE_NEWLINE); + break; + case '<': + fputs("<", stdout); + break; + case '>': + fputs(">", stdout); + break; + case '&': + fputs("&", stdout); + break; + default: + if (state != STATE_SKIP_LINE) + putchar(c); + break; + } + break; + case STATE_NEWLINE: + if (prev_state == STATE_IN_PRE) { + if (c == '`') { + SET_STATE(STATE_PRE_SINGLE_BACKTICK); + break; + } else { + SET_STATE(STATE_IN_PRE); + goto normal; + } + } + switch (c) { + case '=': + SET_STATE(STATE_NEWLINE_EQUALS); + break; + case '>': + SET_STATE(STATE_BLOCKQUOTE); + printf("
"); + break; + case '`': + SET_STATE(STATE_SINGLE_BACKTICK); + break; + case '#': + SET_STATE(STATE_HASH); + break; + default: + REDO_NORMAL; + } + break; + case STATE_NEWLINE_EQUALS: + if (c == '>') { + SET_STATE(STATE_NEWLINE_EQUALS_ARROW); + } else { + putchar('='); + REDO_NORMAL; + } + break; + case STATE_NEWLINE_EQUALS_ARROW: + if (c == ' ') { + state = STATE_BEFORE_URL; + } else { + putchar('='); + REDO_NORMAL; + } + break; + case STATE_BEFORE_URL: + if (c == ' ') { + continue; + break; + } else { + fputs("", stdout); + *urlp = '\0'; + SET_STATE(STATE_BEFORE_URL_NAME); + break; + case '\n': + *urlp = '\0'; + fputs("\">", stdout); + fputs(urlbuf, stdout); + fputs("
", stdout); + SET_STATE(STATE_NEWLINE); + break; + default: + if (urlp < &urlbuf[BUFSIZE] && c != '>' + && c != '<') + *urlp++ = c; + putchar(c); + } + break; + case STATE_BEFORE_URL_NAME: + if (c != ' ' && c != '\t') { + SET_STATE(STATE_URL_NAME); + goto normal; + } + break; + case STATE_SINGLE_BACKTICK: + case STATE_PRE_SINGLE_BACKTICK: + if (c == '`') { + SET_STATE(state == STATE_SINGLE_BACKTICK ? + STATE_DOUBLE_BACKTICK : + STATE_PRE_DOUBLE_BACKTICK); + } else { + putchar('`'); + REDO_NORMAL; + } + break; + case STATE_DOUBLE_BACKTICK: + case STATE_PRE_DOUBLE_BACKTICK: + if (c == '`') { + if (state == STATE_DOUBLE_BACKTICK) { + SET_STATE(STATE_PRE_START); + fputs("
", stdout);
+					SET_STATE(STATE_SKIP_LINE);
+				}
+			} else {
+				fputs("``", stdout);
+				if (state == STATE_DOUBLE_BACKTICK) {
+					REDO_NORMAL;
+				} else {
+					SET_STATE(STATE_IN_PRE);
+					goto normal;
+				}
+			}
+			break;
+		case STATE_HASH:
+			if (c == '#') {
+				SET_STATE(STATE_DOUBLE_HASH);
+			} else {
+				fputs("

", stdout); + SET_STATE(STATE_AFTER_HASH); + goto normal; + } + break; + case STATE_DOUBLE_HASH: + if (c == '#') { + fputs("

", stdout); + SET_STATE(STATE_AFTER_TRIPLE_HASH); + } else { + fputs("

", stdout); + SET_STATE(STATE_AFTER_DOUBLE_HASH); + goto normal; + } + break; + } + } + exit(0); +} diff --git a/adapter/format/gopher2html.nim b/adapter/format/gopher2html.nim new file mode 100644 index 00000000..cfdeab1e --- /dev/null +++ b/adapter/format/gopher2html.nim @@ -0,0 +1,134 @@ +# This file is dedicated to the public domain. +# Gopher directory -> HTML converter for Chawan. + +import std/os +import std/streams +import std/strutils + +import utils/twtstr + +type GopherType = enum + UNKNOWN = "unsupported" + TEXT_FILE = "text file" + ERROR = "error" + DIRECTORY = "directory" + DOS_BINARY = "DOS binary" + SEARCH = "search" + MESSAGE = "message" + SOUND = "sound" + GIF = "gif" + HTML = "HTML" + INFO = "" + IMAGE = "image" + BINARY = "binary" + PNG = "png" + +func gopherType(c: char): GopherType = + return case c + of '0': TEXT_FILE + of '1': DIRECTORY + of '3': ERROR + of '5': DOS_BINARY + of '7': SEARCH + of 'm': MESSAGE + of 's': SOUND + of 'g': GIF + of 'h': HTML + of 'i': INFO + of 'I': IMAGE + of '9': BINARY + of 'p': PNG + else: UNKNOWN + +const ControlPercentEncodeSet = {char(0x00)..char(0x1F), char(0x7F)..char(0xFF)} +const QueryPercentEncodeSet = (ControlPercentEncodeSet + {' ', '"', '#', '<', '>'}) +const PathPercentEncodeSet = (QueryPercentEncodeSet + {'?', '`', '{', '}'}) +const HexCharsUpper = "0123456789ABCDEF" +proc percentEncode(s: string): string = + result = "" + for c in s: + if c notin PathPercentEncodeSet: + result &= c + else: + result &= '%' + result &= HexCharsUpper[uint8(c) shr 4] + result &= HexCharsUpper[uint8(c) and 0xF] + +# returns URL +proc parseParams(): string = + result = "" + let params = commandLineParams() + var was_u = false + for param in params: + if was_u: + result = param + was_u = false + elif param == "-h" or param == "--help": + stdout.write("Usage: gopher2html [-u URL]") + quit(0) + elif param == "-u": + was_u = true + else: + stdout.write("Usage: gopher2html [-u URL]") + quit(1) + +proc main() = + let url = parseParams() + let escapedURL = htmlEscape(url) + stdout.write(""" + + + + +Index of """ & escapedURL & """ + + +

Index of """ & escapedURL & """

""") + let ins = newFileStream(stdin) + var ispre = false + while not ins.atEnd: + let line = ins.readLine() + if line.len == 0: + break # invalid + let tc = line[0] + if tc == '.': + break # end + var i = 1 + template get_field(): string = + let s = line.until('\t', i) + i += s.len + if i >= line.len or line[i] != '\t': + break # invalid + inc i + s + let t = gopherType(tc) + let name = get_field() + var file = get_field() + let host = get_field() + let port = line.until('\t', i) # ignore anything after port + var outs = "" + if t == INFO: + if not ispre: + outs &= "
"
+        ispre = true
+      outs &= htmlEscape(name) & '\n'
+    else:
+      if ispre:
+        outs &= "
" + ispre = false + let ts = $t + var names = "" + if ts != "": + names &= '[' & ts & ']' + names &= htmlEscape(name) + let ourls = if not file.startsWith("URL:"): + if file.len == 0 or file[0] != '/': + file = '/' & file + let pefile = percentEncode(file) + "gopher://" & host & ":" & port & "/" & tc & pefile + else: + file.substr("URL:".len) + outs &= "" & names & "
\n" + stdout.write(outs) + +main() diff --git a/adapter/gemini/gmi2html.c b/adapter/gemini/gmi2html.c deleted file mode 100644 index 2c8c2bf6..00000000 --- a/adapter/gemini/gmi2html.c +++ /dev/null @@ -1,241 +0,0 @@ -/* This file is dedicated to the public domain. - * - * Convert gemtext to HTML. Only accepts input on stdin. - */ - -#include -#include - -typedef enum { - STATE_NORMAL, - STATE_BLOCKQUOTE, - STATE_NEWLINE, - STATE_NEWLINE_EQUALS, - STATE_NEWLINE_EQUALS_ARROW, - STATE_BEFORE_URL, - STATE_IN_URL, - STATE_BEFORE_URL_NAME, - STATE_URL_NAME, - STATE_SINGLE_BACKTICK, - STATE_DOUBLE_BACKTICK, - STATE_PRE_START, - STATE_IN_PRE, - STATE_PRE_SINGLE_BACKTICK, - STATE_PRE_DOUBLE_BACKTICK, - STATE_SKIP_LINE, - STATE_HASH, - STATE_DOUBLE_HASH, - STATE_AFTER_HASH, - STATE_AFTER_DOUBLE_HASH, - STATE_AFTER_TRIPLE_HASH -} ParseState; - -static ParseState state = STATE_NEWLINE; -static ParseState prev_state = STATE_NORMAL; - -int main(void) { - int c; -#define BUFSIZE 4096 - char urlbuf[BUFSIZE + 1]; - char *urlp; - - urlp = urlbuf; - printf(""); -#define SET_STATE(s) do { \ - prev_state = state; \ - state = s; \ - } while (0) -#define REDO_NORMAL do { \ - SET_STATE(STATE_NORMAL); \ - goto normal; \ - } while (0) - while ((c = getc(stdin)) != EOF) { - switch (state) { - case STATE_NORMAL: - case STATE_BLOCKQUOTE: - case STATE_IN_PRE: - case STATE_PRE_START: - case STATE_SKIP_LINE: - case STATE_URL_NAME: - case STATE_AFTER_HASH: - case STATE_AFTER_DOUBLE_HASH: - case STATE_AFTER_TRIPLE_HASH: -normal: switch (c) { - case '\r': break; - case '\n': - if (state == STATE_BLOCKQUOTE) { - fputs("
", stdout); - } else if (state == STATE_PRE_START) { - fputs("\">", stdout); - SET_STATE(STATE_IN_PRE); - } else if (state == STATE_URL_NAME) { - fputs("", stdout); - fputs("
", stdout); - } else if (state == STATE_AFTER_HASH) { - fputs("", stdout); - } else if (state == STATE_AFTER_DOUBLE_HASH) { - fputs("", stdout); - } else if (state == STATE_AFTER_TRIPLE_HASH) { - fputs("", stdout); - } else if (state == STATE_SKIP_LINE) { - } else { - fputs("
", stdout); - } - SET_STATE(STATE_NEWLINE); - break; - case '<': - fputs("<", stdout); - break; - case '>': - fputs(">", stdout); - break; - case '&': - fputs("&", stdout); - break; - default: - if (state != STATE_SKIP_LINE) - putchar(c); - break; - } - break; - case STATE_NEWLINE: - if (prev_state == STATE_IN_PRE) { - if (c == '`') { - SET_STATE(STATE_PRE_SINGLE_BACKTICK); - break; - } else { - SET_STATE(STATE_IN_PRE); - goto normal; - } - } - switch (c) { - case '=': - SET_STATE(STATE_NEWLINE_EQUALS); - break; - case '>': - SET_STATE(STATE_BLOCKQUOTE); - printf("
"); - break; - case '`': - SET_STATE(STATE_SINGLE_BACKTICK); - break; - case '#': - SET_STATE(STATE_HASH); - break; - default: - REDO_NORMAL; - } - break; - case STATE_NEWLINE_EQUALS: - if (c == '>') { - SET_STATE(STATE_NEWLINE_EQUALS_ARROW); - } else { - putchar('='); - REDO_NORMAL; - } - break; - case STATE_NEWLINE_EQUALS_ARROW: - if (c == ' ') { - state = STATE_BEFORE_URL; - } else { - putchar('='); - REDO_NORMAL; - } - break; - case STATE_BEFORE_URL: - if (c == ' ') { - continue; - break; - } else { - fputs("", stdout); - *urlp = '\0'; - SET_STATE(STATE_BEFORE_URL_NAME); - break; - case '\n': - *urlp = '\0'; - fputs("\">", stdout); - fputs(urlbuf, stdout); - fputs("
", stdout); - SET_STATE(STATE_NEWLINE); - break; - default: - if (urlp < &urlbuf[BUFSIZE] && c != '>' - && c != '<') - *urlp++ = c; - putchar(c); - } - break; - case STATE_BEFORE_URL_NAME: - if (c != ' ' && c != '\t') { - SET_STATE(STATE_URL_NAME); - goto normal; - } - break; - case STATE_SINGLE_BACKTICK: - case STATE_PRE_SINGLE_BACKTICK: - if (c == '`') { - SET_STATE(state == STATE_SINGLE_BACKTICK ? - STATE_DOUBLE_BACKTICK : - STATE_PRE_DOUBLE_BACKTICK); - } else { - putchar('`'); - REDO_NORMAL; - } - break; - case STATE_DOUBLE_BACKTICK: - case STATE_PRE_DOUBLE_BACKTICK: - if (c == '`') { - if (state == STATE_DOUBLE_BACKTICK) { - SET_STATE(STATE_PRE_START); - fputs("
", stdout);
-					SET_STATE(STATE_SKIP_LINE);
-				}
-			} else {
-				fputs("``", stdout);
-				if (state == STATE_DOUBLE_BACKTICK) {
-					REDO_NORMAL;
-				} else {
-					SET_STATE(STATE_IN_PRE);
-					goto normal;
-				}
-			}
-			break;
-		case STATE_HASH:
-			if (c == '#') {
-				SET_STATE(STATE_DOUBLE_HASH);
-			} else {
-				fputs("

", stdout); - SET_STATE(STATE_AFTER_HASH); - goto normal; - } - break; - case STATE_DOUBLE_HASH: - if (c == '#') { - fputs("

", stdout); - SET_STATE(STATE_AFTER_TRIPLE_HASH); - } else { - fputs("

", stdout); - SET_STATE(STATE_AFTER_DOUBLE_HASH); - goto normal; - } - break; - } - } - exit(0); -} diff --git a/adapter/gemini/gmifetch.c b/adapter/gemini/gmifetch.c deleted file mode 100644 index 5b4b5f48..00000000 --- a/adapter/gemini/gmifetch.c +++ /dev/null @@ -1,688 +0,0 @@ -/* This file is dedicated to the public domain. - * - * Gemini protocol adapter for Chawan. - * Intended to be used through local CGI (by redirection in scheme-map). - * - * (FWIW, it should work with normal CGI or w3m's local CGI too. However, - * it does not rewrite URLs, so you would have to figure out something for - * that, e.g. by setting the base href or rewriting URLs in another layer.) - * - * Usage: gmifetch [URL] - * - * Environment variables: - * - QUERY_STRING is used if no URL arguments are passed. - * - GMIFETCH_KNOWN_HOSTS is used for setting the known_hosts file. If not set, - * we use $XDG_CONFIG_HOME/gmifetch/known_hosts, where $XDG_CONFIG_HOME falls - * back to $HOME/.config/gmifetch if not set. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static SSL_CTX* ssl_ctx; -static SSL *ssl; -static BIO *conn; - -/* CGI responses */ -#define INPUT_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "" \ - "Input required" \ - "" \ - "

Input required

" \ - "

" \ - "%s" \ - "

" \ - "

" - -#define SUCCESS_RESPONSE "Content-Type: %s\r\n" \ - "\r\n" - -#define REDIRECT_RESPONSE "Status: 30%c\r\n" \ - "Location: %s\r\n" \ - "\r\n" - -#define TEMPFAIL_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "" \ - "Temporary failure" \ - "

%s

" \ - "

" \ - "%s" - -#define PERMFAIL_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "" \ - "Permanent failure" \ - "

%s

" \ - "

" \ - "%s" - -#define CERTFAIL_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "" \ - "Certificate failure" \ - "

%s

" \ - "

" \ - "%s" - -#define INVALID_CERT_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "\n" \ - "Invalid certificate\n" \ - "

Invalid certificate

\n" \ - "

\n" \ - "The certificate received from the server does not match the\n" \ - "stored certificate (expected %s, but got %s). Somebody may be\n" \ - "tampering with your connection.\n" \ - "

\n" \ - "If you are sure that this is not a man-in-the-middle attack,\n" \ - "please remove this host from %s.\n" - -#define UNKNOWN_CERT_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "" \ - "Unknown certificate" \ - "

Unknown certificate

" \ - "

\n" \ - "The hostname of the server you are visiting could not be found\n" \ - "in your list of known hosts (%s).\n" \ - "

\n" \ - "The server has sent us a certificate with the following\n" \ - "fingerprint:\n" \ - "

%s
\n" \ - "

Trust it?\n" \ - "

" \ - "\n" \ - "" \ - "" \ - "
" - -#define UPDATED_CERT_RESPONSE "Content-Type: text/html\r\n" \ - "\r\n" \ - "\n" \ - "Certificate date changed\n" \ - "

Certificate date changed

\n" \ - "

\n" \ - "The received certificate's date did not match the date in your\n" \ - "list of known hosts (%s).\n" \ - "

\n" \ - "The new expiration date is: %s.\n" \ - "

\n" \ - "Update it?\n" \ - "

" \ - "" \ - "\n" \ - "" \ - "
\n" - -#define PDIE(x) \ - do { \ - puts("Content-Type: text/plain\r\n"); \ - puts(x); \ - puts(strerror(errno)); \ - exit(1); \ - } while (0) - -#define SDIE(x) \ - do { \ - puts("Content-Type: text/plain\r\n"); \ - puts(x); \ - ERR_print_errors_fp(stdout); \ - exit(1); \ - } while (0) - -#define DIE(x) \ - do { \ - puts("Content-Type: text/plain\r\n\r\n" x); \ - exit(1); \ - } while (0) - -#define BUFSIZE 1024 -#define PUBKEY_BUF_SIZE 8192 - -static void setup_ssl(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - ssl_ctx = SSL_CTX_new(TLS_client_method()); - SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); - if (!(conn = BIO_new_ssl_connect(ssl_ctx))) - SDIE("Error creating BIO"); -#undef FLAGS -} - -static void extract_hostname(const char *s, char **hostp, char **portp, - char **pathp, char **endp, char *urlbuf) -{ - const char *p; - size_t i, schlen; - - if (!(p = strstr(s, "gemini://"))) - DIE("Invalid URL: scheme delimiter not found"); - p += strlen("gemini://"); - schlen = p - s; - if (schlen >= BUFSIZE) - DIE("Scheme too long"); -#define SCHEME "gemini://" - schlen = strlen(SCHEME); - strcpy(urlbuf, SCHEME); - *hostp = &urlbuf[schlen]; - for (i = schlen; *p && *p != ':' && *p != '/' && i < BUFSIZE; ++p, ++i) - urlbuf[i] = *p; - if (i + 2 >= BUFSIZE) /* +2 for CRLF */ - DIE("Host too long"); - *portp = &urlbuf[i]; - if (*p != ':') { - if (i + 5 >= BUFSIZE) - DIE("Host too long"); - strcpy(&urlbuf[i], ":1965"); - i += 5; - } else { - for (; *p && *p != '/' && i < BUFSIZE; ++i, ++p) - urlbuf[i] = *p; - } - *pathp = &urlbuf[i]; - if (i < BUFSIZE) - urlbuf[i++] = '/'; - if (*p == '/') - ++p; - for (; *p && i < BUFSIZE; ++i, ++p) - urlbuf[i] = *p; - if (i + 2 >= BUFSIZE) /* +2 for CRLF */ - DIE("Host too long"); - *endp = &urlbuf[i]; - urlbuf[i] = '\0'; -} - -int check_cert(const char *theirs, char *hostp, char **stored_digestp, - char *linebuf, time_t their_time, FILE *known_hosts) -{ - char *p, *q, *hashp, *timep; - int found; - time_t our_time; - - rewind(known_hosts); - found = 0; - while (!found && fgets(linebuf, BUFSIZE, known_hosts)) { - p = strstr(linebuf, " "); - if (!p) - DIE("Incorrectly formatted known_hosts file"); - *p = '\0'; - found = !strcmp(linebuf, hostp); - } - if (!found) - return -1; - hashp = p + 1; - if (!(q = strstr(hashp, " "))) - DIE("Incorrectly formatted known_hosts file"); - *q = '\0'; - if (strcmp(hashp, "sha256") && strcmp(hashp, "SHA256")) - DIE("Unsupported digest format"); - *stored_digestp = q + 1; - if (!(q = strstr(*stored_digestp, " "))) { - timep = NULL; - if ((q = strstr(*stored_digestp, "\n"))) - *q = '\0'; - } else { - timep = q + 1; - *q = '\0'; - } - if (strcmp(theirs, *stored_digestp)) - return 0; - if (!timep) - return -2; - our_time = (time_t)atol(timep); - if (their_time != our_time) - return -2; - return 1; -} - -static char HexTable[] = "0123456789ABCDEF"; - -void hex_encode(const unsigned char *inp, char *outbuf, int len) -{ - const unsigned char *p; - char *q; - - for (p = inp, q = outbuf; p < &inp[len]; ++p) { - if (p != inp) - *q++ = ':'; - *q++ = HexTable[(*p >> 4) & 0xF]; - *q++ = HexTable[*p & 0xF]; - } - *q++ = '\0'; -} - -static void hash_buf(const unsigned char *ibuf, int len, char *obuf2) -{ - unsigned int len2; - EVP_MD_CTX* mdctx; - unsigned char hashbuf[EVP_MAX_MD_SIZE]; - - if (!(mdctx = EVP_MD_CTX_new())) - SDIE("Failed to initialize MD_CTX"); - if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) - SDIE("Failed to initialize sha256"); - if (!EVP_DigestUpdate(mdctx, ibuf, len)) - SDIE("Failed to update digest"); - len2 = 0; - if (!EVP_DigestFinal_ex(mdctx, hashbuf, &len2)) - SDIE("Failed to finalize digest"); - EVP_MD_CTX_free(mdctx); - hex_encode(hashbuf, obuf2, len2); -} - -/* 1: cert found & valid - * 0: cert found & invalid - * -1: cert not found - * -2: cert found, but notAfter updated - */ -static int connect(char *hostp, char *portp, char *pathp, char *endp, - char *linebuf, char **stored_digestp, time_t *their_time, - char *hashbuf2, FILE *known_hosts) -{ - X509 *cert; - const EVP_PKEY *pkey; - unsigned char pubkey_buf[PUBKEY_BUF_SIZE + 1], *r; - int len, res; - const ASN1_TIME *notAfter; - struct tm their_tm; - - *pathp = '\0'; - if (!BIO_set_conn_hostname(conn, hostp)) - SDIE("Error setting BIO hostname"); - *pathp = '/'; - BIO_get_ssl(conn, &ssl); -#define PREFERRED_CIPHERS "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4" - if (!SSL_set_cipher_list(ssl, PREFERRED_CIPHERS)) - SDIE("Error failed to set cipher list"); - *portp = '\0'; - if (!SSL_set_tlsext_host_name(ssl, hostp)) - SDIE("Error failed to set tlsext host name"); - if (BIO_do_connect(conn) <= 0) - SDIE("Failed to connect"); - if (!BIO_do_handshake(conn)) - SDIE("Failed handshake"); - if (!(cert = SSL_get_peer_certificate(ssl))) - DIE("Failed to get certificate"); - if (!(pkey = X509_get0_pubkey(cert))) - SDIE("Failed to decode public key"); - len = i2d_PUBKEY(pkey, NULL); - if (len * 3 > PUBKEY_BUF_SIZE) - DIE("Public key too long"); - r = pubkey_buf; - if (i2d_PUBKEY(pkey, &r) != len) - DIE("wat"); - hash_buf(pubkey_buf, len, hashbuf2); - notAfter = X509_get0_notAfter(cert); - if (!ASN1_TIME_to_tm(notAfter, &their_tm)) - DIE("Failed to parse time"); - if (X509_cmp_current_time(X509_get0_notBefore(cert)) >= 0) - DIE("Wrong time"); - if (X509_cmp_current_time(notAfter) <= 0) - DIE("Wrong time"); - *their_time = mktime(&their_tm); - res = check_cert(hashbuf2, hostp, stored_digestp, linebuf, *their_time, - known_hosts); - *portp = ':'; - X509_free(cert); - strcpy(endp, "\r\n"); - return res; -} - -static void read_response(const char *urlbuf) -{ - int bytes, total; - const char *tmp; - char *q, status0, status1; - char buffer[BUFSIZE + 1]; - - /* Read response */ - total = 0; - /* Status code */ - while (((bytes = BIO_read(conn, buffer, 3 - total)) > 0 || - BIO_should_retry(conn)) && total < 3) - total += bytes; - if (total < 3 || !isdigit(status0 = buffer[0]) || - !isdigit(status1 = buffer[1]) || buffer[2] != ' ') - DIE("Invalid status code"); - /* Meta */ - #define METALEN (total - 3) - while (((bytes = BIO_read(conn, &buffer[METALEN], 1024 - METALEN)) > 0 || - BIO_should_retry(conn)) && METALEN < BUFSIZE) - total += bytes; - q = strstr(buffer, "\r\n"); - if (!q) - DIE("Invalid status line"); - *q = '\0'; - /* buffer is now META. */ - switch (status0) { - case '1': /* input */ - /* META is the prompt. */ - printf(INPUT_RESPONSE, urlbuf, buffer, status1 == '1' ? - "password" /* sensitive input */ : - "search" /* input */); - break; - case '2': /* success */ - /* META is the content type. */ - printf(SUCCESS_RESPONSE, *buffer ? - buffer : - "text/gemini; charset=utf-8" /* fallback */); - /* Body */ - /* flush any data remaining in buffer */ - total -= 5 + (q - buffer); /* code + space + meta + \r\n len */ - if (total > 0) - fwrite(&q[2], 1, total, stdout); - while ((bytes = BIO_read(conn, buffer, BUFSIZE)) > 0 || - BIO_should_retry(conn)) - fwrite(buffer, 1, bytes, stdout); - break; - case '3': /* redirect */ - /* META is the redirection URL. */ - printf(REDIRECT_RESPONSE, status1 == '0' ? - '7' /* temporary */ : - '1' /* permanent */, buffer); - break; - case '4': /* temporary failure */ - /* META is additional information. */ - /* TODO maybe set status code too? */ - switch (status1) { - case '1': - tmp = "Server unavailable"; - break; - case '2': - tmp = "CGI error"; - break; - case '3': - tmp = "Proxy error"; - break; - case '4': - tmp = "Slow down!"; - break; - case '0': - default: /* no additional information provided in the code */ - tmp = "Temporary failure"; - break; - } - printf(TEMPFAIL_RESPONSE, tmp, buffer); - break; - case '5': /* permanent failure */ - /* TODO maybe set status code too? */ - switch (status1) { - case '1': - tmp = "Not found"; - break; - case '2': - tmp = "Gone"; - break; - case '3': - tmp = "Proxy request refused"; - break; - case '9': - tmp = "Bad request"; - break; - case '0': - default: /* no additional information provided in the code */ - tmp = "Permanent failure"; - break; - } - printf(PERMFAIL_RESPONSE, tmp, buffer); - break; - case '6': /* permanent failure */ - /* TODO maybe set status code too? */ - switch (status1) { - case '1': - tmp = "Certificate not authorized"; - break; - case '2': - tmp = "Certificate not valid"; - break; - case '0': - default: /* no additional information provided in the code */ - tmp = "Certificate failure"; - break; - } - printf(CERTFAIL_RESPONSE, tmp, buffer); - } -} - -void decode_query(const char *input_url, char *output_buffer) -{ - const char *p; - char *q, *endp, c; - - endp = &output_buffer[BUFSIZE]; - for (p = input_url, q = output_buffer; *p && q < endp; ++p, ++q) { - if (*p != '%') { - *q = *p; - } else { - if (!isxdigit(p[1] & 0xFF) || !isxdigit(p[2] & 0xFF)) - DIE("Invalid percent encoding"); - c = tolower(p[1] & 0xFF); - *q = ('a' <= c && c <= 'z') ? - c - 'a' + 10 : - c - '0'; - c = tolower(p[2] & 0xFF); - *q = (*q << 4) | (('a' <= c || c <= 'z') ? - c - 'a' + 10 : - c - '0'); - p += 2; - } - } - if (q >= endp) - DIE("Query too long"); - *q = '\0'; -} - - -void read_post(const char *hostp, char *portp, char *pathp, const char *urlbuf, - char *khsbuf, FILE **known_hosts) -{ - /* TODO move query strings here */ - size_t n; - char *p, *q; - FILE *known_hosts_tmp; - long last_pos, len, total; - size_t khslen; - char inbuf[BUFSIZE + 1], buffer[BUFSIZE + 1]; - - n = fread(inbuf, 1, BUFSIZE, stdin); - inbuf[n] = '\0'; - if ((p = strstr(inbuf, "input="))) { - decode_query(p + 6, buffer); - if (!(q = strstr(pathp, "?"))) /* no query string */ - q = &pathp[strlen(pathp)]; - for (; *p && q < &urlbuf[BUFSIZE]; ++p, ++q) - *q = *p; - if (q >= &urlbuf[BUFSIZE]) - DIE("Query too long"); - } else if (!(p = strstr(inbuf, "trust_cert="))) { - DIE("Invalid POST request: trust_cert missing"); - } - p += sizeof("trust_cert=") - 1; - if (!strncmp(p, "always", 6)) { - /* move to file end */ - fseek(*known_hosts, 0L, SEEK_END); - last_pos = ftell(*known_hosts); - if (!(p = strstr(p, "entry="))) - DIE("Invalid POST request: missing entry"); - p += sizeof("entry=") - 1; - decode_query(p, buffer); - /* replace plus signs */ - p = buffer; - while ((p = strstr(p, "+"))) - *p = ' '; - fwrite(buffer, 1, strlen(buffer), *known_hosts); - fwrite("\n", 1, 1, *known_hosts); - khslen = strlen(khsbuf); - khsbuf[khslen] = '~'; - khsbuf[khslen + 1] = '\0'; - if (!(known_hosts_tmp = fopen(khsbuf, "w+"))) - PDIE("Error opening temporary hosts file"); - rewind(*known_hosts); - *portp = '\0'; - total = 0; - while (fgets(buffer, BUFSIZE, *known_hosts)) { - len = strlen(buffer); - if (!len) - continue; - if ((total += len) > last_pos) { - /* finished */ - fwrite(buffer, 1, len, known_hosts_tmp); - break; - } - if (buffer[len - 1] != '\n') { - /* clean up */ - fclose(known_hosts_tmp); - unlink(khsbuf); - DIE("Line too long"); - } - if (!(p = strstr(buffer, " "))) - DIE("Invalid entry in known_hosts file"); - *p = '\0'; - if (strcmp(buffer, hostp)) { - *p = ' '; - fwrite(buffer, 1, len, known_hosts_tmp); - } - } - *portp = ':'; - memcpy(buffer, khsbuf, BUFSIZE + 1); - buffer[khslen] = '\0'; - fclose(*known_hosts); - fclose(known_hosts_tmp); - if (rename(khsbuf, buffer)) - PDIE("Failed to rename temporary file"); - khsbuf[khslen] = '\0'; - if (!(*known_hosts = fopen(khsbuf, "a+"))) - PDIE("Failed to re-open known hosts file"); - } else if (strncmp(p, "once", 4)) { - DIE("Invalid POST request"); - } -} - -FILE *open_known_hosts(char *khsbuf) -{ - const char *known_hosts_path, *xdg_dir, *home_dir; - char *p; - size_t len; - struct stat s; - FILE *known_hosts; - - known_hosts_path = getenv("GMIFETCH_KNOWN_HOSTS"); - if (!known_hosts_path) { - xdg_dir = getenv("XDG_CONFIG_HOME"); - if ((xdg_dir = getenv("XDG_CONFIG_HOME"))) { - len = strlen(xdg_dir); -#define CONFIG_REL "/gmifetch/known_hosts" - if (len + sizeof(CONFIG_REL) > BUFSIZE) - DIE("Error: config directory path too long"); - memcpy(khsbuf, xdg_dir, len); - memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL)); - } else { - if (!(home_dir = getenv("HOME"))) - home_dir = getpwuid(getuid())->pw_dir; - if (!home_dir) - DIE("Error: failed to get HOME directory"); -#undef CONFIG_REL -#define CONFIG_REL "/.config/gmifetch/known_hosts" - len = strlen(home_dir); - if (len + sizeof(CONFIG_REL) > BUFSIZE) - DIE("Error: home directory path too long"); - memcpy(khsbuf, home_dir, len); - memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL)); - } - } else { - len = strlen(known_hosts_path); - if (len > BUFSIZE) - DIE("Error: known hosts path too long"); - memcpy(khsbuf, known_hosts_path, len); - } - p = khsbuf; - if (*p == '/') - ++p; - for (; *p; ++p) { - if (*p == '/') { - *p = '\0'; - if (stat(khsbuf, &s) == -1) { - if (errno != ENOENT) - PDIE("Error calling stat"); - if (mkdir(khsbuf, 0755) == -1) - PDIE("Error calling mkdir"); - } else if (!S_ISDIR(s.st_mode)) { - if (mkdir(khsbuf, 0755) == -1) - PDIE("Error calling mkdir"); - } - *p = '/'; - } - } - if (!(known_hosts = fopen(khsbuf, "a+"))) - PDIE("Error opening known hosts file"); - return known_hosts; -} - -int main(int argc, const char *argv[]) -{ - const char *input_url, *method, *all_proxy; - char *hostp, *portp, *pathp, *endp, *stored_digestp; - int connect_res; - time_t their_time; - char hashbuf2[EVP_MAX_MD_SIZE * 3 + 1]; - char urlbuf[BUFSIZE + 1], khsbuf[BUFSIZE + 2], linebuf[BUFSIZE + 1]; - FILE *known_hosts; - - if (argc != 2) { - input_url = getenv("QUERY_STRING"); - if (!input_url) - DIE("Usage: gmifetch [url] (or set QUERY_STRING)"); - } else { - input_url = argv[1]; - } - all_proxy = getenv("ALL_PROXY"); -#define PROXY_ERR "gmifetch does not support proxies yet. Please disable" \ - "your proxy for gemini URLs if you wish to proceed anyway." - if (all_proxy && *all_proxy) - DIE(PROXY_ERR); - known_hosts = open_known_hosts(khsbuf); - setup_ssl(); - extract_hostname(input_url, &hostp, &portp, &pathp, &endp, urlbuf); - method = getenv("REQUEST_METHOD"); - if (method && !strcmp(method, "POST")) - read_post(hostp, portp, pathp, urlbuf, khsbuf, &known_hosts); - connect_res = connect(hostp, portp, pathp, endp, linebuf, &stored_digestp, - &their_time, hashbuf2, known_hosts); - if (connect_res == 1) { /* valid certificate */ - /* I really wish this was explicitly mentioned in the - * standard. Something like: - * - * !!!WARNING WARNING WARNING some gemini servers will - * not accept URLs containing the default port number!!! - */ - if (!strncmp(portp, ":1965", 5)) - /* move including null terminator */ - memmove(portp, &portp[5], strlen(&portp[5]) + 1); - BIO_puts(conn, urlbuf); - read_response(urlbuf); - } else if (connect_res == 0) { /* invalid certificate */ - printf(INVALID_CERT_RESPONSE, stored_digestp, hashbuf2, - khsbuf); - } else if (connect_res == -1) { /* no certificate */ - *portp = '\0'; - printf(UNKNOWN_CERT_RESPONSE, khsbuf, hashbuf2, hostp, - hashbuf2, (unsigned long)their_time); - } else { /* -2: updated expiration date */ - *portp = '\0'; - printf(UPDATED_CERT_RESPONSE, khsbuf, - ctime(&their_time), hostp, hashbuf2, - (unsigned long)their_time); - } - BIO_free_all(conn); - exit(0); -} diff --git a/adapter/gopher/gopher2html.nim b/adapter/gopher/gopher2html.nim deleted file mode 100644 index cfdeab1e..00000000 --- a/adapter/gopher/gopher2html.nim +++ /dev/null @@ -1,134 +0,0 @@ -# This file is dedicated to the public domain. -# Gopher directory -> HTML converter for Chawan. - -import std/os -import std/streams -import std/strutils - -import utils/twtstr - -type GopherType = enum - UNKNOWN = "unsupported" - TEXT_FILE = "text file" - ERROR = "error" - DIRECTORY = "directory" - DOS_BINARY = "DOS binary" - SEARCH = "search" - MESSAGE = "message" - SOUND = "sound" - GIF = "gif" - HTML = "HTML" - INFO = "" - IMAGE = "image" - BINARY = "binary" - PNG = "png" - -func gopherType(c: char): GopherType = - return case c - of '0': TEXT_FILE - of '1': DIRECTORY - of '3': ERROR - of '5': DOS_BINARY - of '7': SEARCH - of 'm': MESSAGE - of 's': SOUND - of 'g': GIF - of 'h': HTML - of 'i': INFO - of 'I': IMAGE - of '9': BINARY - of 'p': PNG - else: UNKNOWN - -const ControlPercentEncodeSet = {char(0x00)..char(0x1F), char(0x7F)..char(0xFF)} -const QueryPercentEncodeSet = (ControlPercentEncodeSet + {' ', '"', '#', '<', '>'}) -const PathPercentEncodeSet = (QueryPercentEncodeSet + {'?', '`', '{', '}'}) -const HexCharsUpper = "0123456789ABCDEF" -proc percentEncode(s: string): string = - result = "" - for c in s: - if c notin PathPercentEncodeSet: - result &= c - else: - result &= '%' - result &= HexCharsUpper[uint8(c) shr 4] - result &= HexCharsUpper[uint8(c) and 0xF] - -# returns URL -proc parseParams(): string = - result = "" - let params = commandLineParams() - var was_u = false - for param in params: - if was_u: - result = param - was_u = false - elif param == "-h" or param == "--help": - stdout.write("Usage: gopher2html [-u URL]") - quit(0) - elif param == "-u": - was_u = true - else: - stdout.write("Usage: gopher2html [-u URL]") - quit(1) - -proc main() = - let url = parseParams() - let escapedURL = htmlEscape(url) - stdout.write(""" - - - - -Index of """ & escapedURL & """ - - -

Index of """ & escapedURL & """

""") - let ins = newFileStream(stdin) - var ispre = false - while not ins.atEnd: - let line = ins.readLine() - if line.len == 0: - break # invalid - let tc = line[0] - if tc == '.': - break # end - var i = 1 - template get_field(): string = - let s = line.until('\t', i) - i += s.len - if i >= line.len or line[i] != '\t': - break # invalid - inc i - s - let t = gopherType(tc) - let name = get_field() - var file = get_field() - let host = get_field() - let port = line.until('\t', i) # ignore anything after port - var outs = "" - if t == INFO: - if not ispre: - outs &= "
"
-        ispre = true
-      outs &= htmlEscape(name) & '\n'
-    else:
-      if ispre:
-        outs &= "
" - ispre = false - let ts = $t - var names = "" - if ts != "": - names &= '[' & ts & ']' - names &= htmlEscape(name) - let ourls = if not file.startsWith("URL:"): - if file.len == 0 or file[0] != '/': - file = '/' & file - let pefile = percentEncode(file) - "gopher://" & host & ":" & port & "/" & tc & pefile - else: - file.substr("URL:".len) - outs &= "" & names & "
\n" - stdout.write(outs) - -main() diff --git a/adapter/protocol/about.nim b/adapter/protocol/about.nim new file mode 100644 index 00000000..860294f3 --- /dev/null +++ b/adapter/protocol/about.nim @@ -0,0 +1,14 @@ +import std/envvars + +const chawan = staticRead"res/chawan.html" +const license = staticRead"res/license.html" + +proc main() = + stdout.write("Content-Type: text/html\n\n") + case getEnv("MAPPED_URI_PATH") + of "blank": stdout.write("") + of "chawan": stdout.write(chawan) + of "license": stdout.write(license) + else: stdout.write("Error: about page not found") + +main() diff --git a/adapter/protocol/cha-finger b/adapter/protocol/cha-finger new file mode 100755 index 00000000..e963b339 --- /dev/null +++ b/adapter/protocol/cha-finger @@ -0,0 +1,39 @@ +#!/bin/sh +# Finger protocol adapter for Chawan. Requires curl. +# (It does *not* work without the environment variables MAPPED_URI_*, so no +# w3m support.) +# +# Usage: put this script in your cgi-bin folder, then add the following line to +# your urimethodmap: +# +# finger: /cgi-bin/cha-finger?%s +# +# Note: the Chawan default configuration already does this, so normally you +# don't need to do anything to use the finger protocol. + +die() { + echo "$1" + exit 1 +} + +type curl >/dev/null || \ + die "curl must be installed on your computer to use finger." + +printf 'Content-Type: text/plain\n\n' + +PORT="${MAPPED_URI_PORT:-79}" +test "$PORT" = 79 || die "Invalid port" + +if test -n "$MAPPED_URI_USERNAME" +then USER="$MAPPED_URI_USERNAME" +else case "$MAPPED_URI_PATH" in + /w*) USER="/w ${MAPPED_URI_PATH#/w}" ;; + *) USER="$MAPPED_URI_PATH" ;; + esac +fi +URL="telnet://$MAPPED_URI_HOST:$PORT" + +printf '%s\r\n' "$USER" | if test -n "$ALL_PROXY" +then curl -x "$ALL_PROXY" -- "$URL" +else curl -- "$URL" +fi 2>/dev/null diff --git a/adapter/protocol/data.nim b/adapter/protocol/data.nim new file mode 100644 index 00000000..7b022976 --- /dev/null +++ b/adapter/protocol/data.nim @@ -0,0 +1,29 @@ +import std/envvars +import std/base64 +import std/strutils + +import utils/twtstr + +proc main() = + let str = getEnv("MAPPED_URI_PATH") + const si = "data:".len # start index + var ct = str.until(',', si) + for c in ct: + if c notin AsciiAlphaNumeric and c != '/': + stdout.write("Cha-Control: ConnectionError -7 invalid data URL") + return + let sd = si + ct.len + 1 # data start + let body = percentDecode(str, sd) + if ct.endsWith(";base64"): + try: + let d = base64.decode(body) # decode from ct end + 1 + ct.setLen(ct.len - ";base64".len) # remove base64 indicator + stdout.write("Content-Type: " & ct & "\n\n") + stdout.write(d) + except ValueError: + stdout.write("Cha-Control: ConnectionError -7 invalid data URL") + else: + stdout.write("Content-Type: " & ct & "\n\n") + stdout.write(body) + +main() diff --git a/adapter/protocol/gmifetch.c b/adapter/protocol/gmifetch.c new file mode 100644 index 00000000..5b4b5f48 --- /dev/null +++ b/adapter/protocol/gmifetch.c @@ -0,0 +1,688 @@ +/* This file is dedicated to the public domain. + * + * Gemini protocol adapter for Chawan. + * Intended to be used through local CGI (by redirection in scheme-map). + * + * (FWIW, it should work with normal CGI or w3m's local CGI too. However, + * it does not rewrite URLs, so you would have to figure out something for + * that, e.g. by setting the base href or rewriting URLs in another layer.) + * + * Usage: gmifetch [URL] + * + * Environment variables: + * - QUERY_STRING is used if no URL arguments are passed. + * - GMIFETCH_KNOWN_HOSTS is used for setting the known_hosts file. If not set, + * we use $XDG_CONFIG_HOME/gmifetch/known_hosts, where $XDG_CONFIG_HOME falls + * back to $HOME/.config/gmifetch if not set. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static SSL_CTX* ssl_ctx; +static SSL *ssl; +static BIO *conn; + +/* CGI responses */ +#define INPUT_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "" \ + "Input required" \ + "" \ + "

Input required

" \ + "

" \ + "%s" \ + "

" \ + "

" + +#define SUCCESS_RESPONSE "Content-Type: %s\r\n" \ + "\r\n" + +#define REDIRECT_RESPONSE "Status: 30%c\r\n" \ + "Location: %s\r\n" \ + "\r\n" + +#define TEMPFAIL_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "" \ + "Temporary failure" \ + "

%s

" \ + "

" \ + "%s" + +#define PERMFAIL_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "" \ + "Permanent failure" \ + "

%s

" \ + "

" \ + "%s" + +#define CERTFAIL_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "" \ + "Certificate failure" \ + "

%s

" \ + "

" \ + "%s" + +#define INVALID_CERT_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "\n" \ + "Invalid certificate\n" \ + "

Invalid certificate

\n" \ + "

\n" \ + "The certificate received from the server does not match the\n" \ + "stored certificate (expected %s, but got %s). Somebody may be\n" \ + "tampering with your connection.\n" \ + "

\n" \ + "If you are sure that this is not a man-in-the-middle attack,\n" \ + "please remove this host from %s.\n" + +#define UNKNOWN_CERT_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "" \ + "Unknown certificate" \ + "

Unknown certificate

" \ + "

\n" \ + "The hostname of the server you are visiting could not be found\n" \ + "in your list of known hosts (%s).\n" \ + "

\n" \ + "The server has sent us a certificate with the following\n" \ + "fingerprint:\n" \ + "

%s
\n" \ + "

Trust it?\n" \ + "

" \ + "\n" \ + "" \ + "" \ + "
" + +#define UPDATED_CERT_RESPONSE "Content-Type: text/html\r\n" \ + "\r\n" \ + "\n" \ + "Certificate date changed\n" \ + "

Certificate date changed

\n" \ + "

\n" \ + "The received certificate's date did not match the date in your\n" \ + "list of known hosts (%s).\n" \ + "

\n" \ + "The new expiration date is: %s.\n" \ + "

\n" \ + "Update it?\n" \ + "

" \ + "" \ + "\n" \ + "" \ + "
\n" + +#define PDIE(x) \ + do { \ + puts("Content-Type: text/plain\r\n"); \ + puts(x); \ + puts(strerror(errno)); \ + exit(1); \ + } while (0) + +#define SDIE(x) \ + do { \ + puts("Content-Type: text/plain\r\n"); \ + puts(x); \ + ERR_print_errors_fp(stdout); \ + exit(1); \ + } while (0) + +#define DIE(x) \ + do { \ + puts("Content-Type: text/plain\r\n\r\n" x); \ + exit(1); \ + } while (0) + +#define BUFSIZE 1024 +#define PUBKEY_BUF_SIZE 8192 + +static void setup_ssl(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + ssl_ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + if (!(conn = BIO_new_ssl_connect(ssl_ctx))) + SDIE("Error creating BIO"); +#undef FLAGS +} + +static void extract_hostname(const char *s, char **hostp, char **portp, + char **pathp, char **endp, char *urlbuf) +{ + const char *p; + size_t i, schlen; + + if (!(p = strstr(s, "gemini://"))) + DIE("Invalid URL: scheme delimiter not found"); + p += strlen("gemini://"); + schlen = p - s; + if (schlen >= BUFSIZE) + DIE("Scheme too long"); +#define SCHEME "gemini://" + schlen = strlen(SCHEME); + strcpy(urlbuf, SCHEME); + *hostp = &urlbuf[schlen]; + for (i = schlen; *p && *p != ':' && *p != '/' && i < BUFSIZE; ++p, ++i) + urlbuf[i] = *p; + if (i + 2 >= BUFSIZE) /* +2 for CRLF */ + DIE("Host too long"); + *portp = &urlbuf[i]; + if (*p != ':') { + if (i + 5 >= BUFSIZE) + DIE("Host too long"); + strcpy(&urlbuf[i], ":1965"); + i += 5; + } else { + for (; *p && *p != '/' && i < BUFSIZE; ++i, ++p) + urlbuf[i] = *p; + } + *pathp = &urlbuf[i]; + if (i < BUFSIZE) + urlbuf[i++] = '/'; + if (*p == '/') + ++p; + for (; *p && i < BUFSIZE; ++i, ++p) + urlbuf[i] = *p; + if (i + 2 >= BUFSIZE) /* +2 for CRLF */ + DIE("Host too long"); + *endp = &urlbuf[i]; + urlbuf[i] = '\0'; +} + +int check_cert(const char *theirs, char *hostp, char **stored_digestp, + char *linebuf, time_t their_time, FILE *known_hosts) +{ + char *p, *q, *hashp, *timep; + int found; + time_t our_time; + + rewind(known_hosts); + found = 0; + while (!found && fgets(linebuf, BUFSIZE, known_hosts)) { + p = strstr(linebuf, " "); + if (!p) + DIE("Incorrectly formatted known_hosts file"); + *p = '\0'; + found = !strcmp(linebuf, hostp); + } + if (!found) + return -1; + hashp = p + 1; + if (!(q = strstr(hashp, " "))) + DIE("Incorrectly formatted known_hosts file"); + *q = '\0'; + if (strcmp(hashp, "sha256") && strcmp(hashp, "SHA256")) + DIE("Unsupported digest format"); + *stored_digestp = q + 1; + if (!(q = strstr(*stored_digestp, " "))) { + timep = NULL; + if ((q = strstr(*stored_digestp, "\n"))) + *q = '\0'; + } else { + timep = q + 1; + *q = '\0'; + } + if (strcmp(theirs, *stored_digestp)) + return 0; + if (!timep) + return -2; + our_time = (time_t)atol(timep); + if (their_time != our_time) + return -2; + return 1; +} + +static char HexTable[] = "0123456789ABCDEF"; + +void hex_encode(const unsigned char *inp, char *outbuf, int len) +{ + const unsigned char *p; + char *q; + + for (p = inp, q = outbuf; p < &inp[len]; ++p) { + if (p != inp) + *q++ = ':'; + *q++ = HexTable[(*p >> 4) & 0xF]; + *q++ = HexTable[*p & 0xF]; + } + *q++ = '\0'; +} + +static void hash_buf(const unsigned char *ibuf, int len, char *obuf2) +{ + unsigned int len2; + EVP_MD_CTX* mdctx; + unsigned char hashbuf[EVP_MAX_MD_SIZE]; + + if (!(mdctx = EVP_MD_CTX_new())) + SDIE("Failed to initialize MD_CTX"); + if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) + SDIE("Failed to initialize sha256"); + if (!EVP_DigestUpdate(mdctx, ibuf, len)) + SDIE("Failed to update digest"); + len2 = 0; + if (!EVP_DigestFinal_ex(mdctx, hashbuf, &len2)) + SDIE("Failed to finalize digest"); + EVP_MD_CTX_free(mdctx); + hex_encode(hashbuf, obuf2, len2); +} + +/* 1: cert found & valid + * 0: cert found & invalid + * -1: cert not found + * -2: cert found, but notAfter updated + */ +static int connect(char *hostp, char *portp, char *pathp, char *endp, + char *linebuf, char **stored_digestp, time_t *their_time, + char *hashbuf2, FILE *known_hosts) +{ + X509 *cert; + const EVP_PKEY *pkey; + unsigned char pubkey_buf[PUBKEY_BUF_SIZE + 1], *r; + int len, res; + const ASN1_TIME *notAfter; + struct tm their_tm; + + *pathp = '\0'; + if (!BIO_set_conn_hostname(conn, hostp)) + SDIE("Error setting BIO hostname"); + *pathp = '/'; + BIO_get_ssl(conn, &ssl); +#define PREFERRED_CIPHERS "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4" + if (!SSL_set_cipher_list(ssl, PREFERRED_CIPHERS)) + SDIE("Error failed to set cipher list"); + *portp = '\0'; + if (!SSL_set_tlsext_host_name(ssl, hostp)) + SDIE("Error failed to set tlsext host name"); + if (BIO_do_connect(conn) <= 0) + SDIE("Failed to connect"); + if (!BIO_do_handshake(conn)) + SDIE("Failed handshake"); + if (!(cert = SSL_get_peer_certificate(ssl))) + DIE("Failed to get certificate"); + if (!(pkey = X509_get0_pubkey(cert))) + SDIE("Failed to decode public key"); + len = i2d_PUBKEY(pkey, NULL); + if (len * 3 > PUBKEY_BUF_SIZE) + DIE("Public key too long"); + r = pubkey_buf; + if (i2d_PUBKEY(pkey, &r) != len) + DIE("wat"); + hash_buf(pubkey_buf, len, hashbuf2); + notAfter = X509_get0_notAfter(cert); + if (!ASN1_TIME_to_tm(notAfter, &their_tm)) + DIE("Failed to parse time"); + if (X509_cmp_current_time(X509_get0_notBefore(cert)) >= 0) + DIE("Wrong time"); + if (X509_cmp_current_time(notAfter) <= 0) + DIE("Wrong time"); + *their_time = mktime(&their_tm); + res = check_cert(hashbuf2, hostp, stored_digestp, linebuf, *their_time, + known_hosts); + *portp = ':'; + X509_free(cert); + strcpy(endp, "\r\n"); + return res; +} + +static void read_response(const char *urlbuf) +{ + int bytes, total; + const char *tmp; + char *q, status0, status1; + char buffer[BUFSIZE + 1]; + + /* Read response */ + total = 0; + /* Status code */ + while (((bytes = BIO_read(conn, buffer, 3 - total)) > 0 || + BIO_should_retry(conn)) && total < 3) + total += bytes; + if (total < 3 || !isdigit(status0 = buffer[0]) || + !isdigit(status1 = buffer[1]) || buffer[2] != ' ') + DIE("Invalid status code"); + /* Meta */ + #define METALEN (total - 3) + while (((bytes = BIO_read(conn, &buffer[METALEN], 1024 - METALEN)) > 0 || + BIO_should_retry(conn)) && METALEN < BUFSIZE) + total += bytes; + q = strstr(buffer, "\r\n"); + if (!q) + DIE("Invalid status line"); + *q = '\0'; + /* buffer is now META. */ + switch (status0) { + case '1': /* input */ + /* META is the prompt. */ + printf(INPUT_RESPONSE, urlbuf, buffer, status1 == '1' ? + "password" /* sensitive input */ : + "search" /* input */); + break; + case '2': /* success */ + /* META is the content type. */ + printf(SUCCESS_RESPONSE, *buffer ? + buffer : + "text/gemini; charset=utf-8" /* fallback */); + /* Body */ + /* flush any data remaining in buffer */ + total -= 5 + (q - buffer); /* code + space + meta + \r\n len */ + if (total > 0) + fwrite(&q[2], 1, total, stdout); + while ((bytes = BIO_read(conn, buffer, BUFSIZE)) > 0 || + BIO_should_retry(conn)) + fwrite(buffer, 1, bytes, stdout); + break; + case '3': /* redirect */ + /* META is the redirection URL. */ + printf(REDIRECT_RESPONSE, status1 == '0' ? + '7' /* temporary */ : + '1' /* permanent */, buffer); + break; + case '4': /* temporary failure */ + /* META is additional information. */ + /* TODO maybe set status code too? */ + switch (status1) { + case '1': + tmp = "Server unavailable"; + break; + case '2': + tmp = "CGI error"; + break; + case '3': + tmp = "Proxy error"; + break; + case '4': + tmp = "Slow down!"; + break; + case '0': + default: /* no additional information provided in the code */ + tmp = "Temporary failure"; + break; + } + printf(TEMPFAIL_RESPONSE, tmp, buffer); + break; + case '5': /* permanent failure */ + /* TODO maybe set status code too? */ + switch (status1) { + case '1': + tmp = "Not found"; + break; + case '2': + tmp = "Gone"; + break; + case '3': + tmp = "Proxy request refused"; + break; + case '9': + tmp = "Bad request"; + break; + case '0': + default: /* no additional information provided in the code */ + tmp = "Permanent failure"; + break; + } + printf(PERMFAIL_RESPONSE, tmp, buffer); + break; + case '6': /* permanent failure */ + /* TODO maybe set status code too? */ + switch (status1) { + case '1': + tmp = "Certificate not authorized"; + break; + case '2': + tmp = "Certificate not valid"; + break; + case '0': + default: /* no additional information provided in the code */ + tmp = "Certificate failure"; + break; + } + printf(CERTFAIL_RESPONSE, tmp, buffer); + } +} + +void decode_query(const char *input_url, char *output_buffer) +{ + const char *p; + char *q, *endp, c; + + endp = &output_buffer[BUFSIZE]; + for (p = input_url, q = output_buffer; *p && q < endp; ++p, ++q) { + if (*p != '%') { + *q = *p; + } else { + if (!isxdigit(p[1] & 0xFF) || !isxdigit(p[2] & 0xFF)) + DIE("Invalid percent encoding"); + c = tolower(p[1] & 0xFF); + *q = ('a' <= c && c <= 'z') ? + c - 'a' + 10 : + c - '0'; + c = tolower(p[2] & 0xFF); + *q = (*q << 4) | (('a' <= c || c <= 'z') ? + c - 'a' + 10 : + c - '0'); + p += 2; + } + } + if (q >= endp) + DIE("Query too long"); + *q = '\0'; +} + + +void read_post(const char *hostp, char *portp, char *pathp, const char *urlbuf, + char *khsbuf, FILE **known_hosts) +{ + /* TODO move query strings here */ + size_t n; + char *p, *q; + FILE *known_hosts_tmp; + long last_pos, len, total; + size_t khslen; + char inbuf[BUFSIZE + 1], buffer[BUFSIZE + 1]; + + n = fread(inbuf, 1, BUFSIZE, stdin); + inbuf[n] = '\0'; + if ((p = strstr(inbuf, "input="))) { + decode_query(p + 6, buffer); + if (!(q = strstr(pathp, "?"))) /* no query string */ + q = &pathp[strlen(pathp)]; + for (; *p && q < &urlbuf[BUFSIZE]; ++p, ++q) + *q = *p; + if (q >= &urlbuf[BUFSIZE]) + DIE("Query too long"); + } else if (!(p = strstr(inbuf, "trust_cert="))) { + DIE("Invalid POST request: trust_cert missing"); + } + p += sizeof("trust_cert=") - 1; + if (!strncmp(p, "always", 6)) { + /* move to file end */ + fseek(*known_hosts, 0L, SEEK_END); + last_pos = ftell(*known_hosts); + if (!(p = strstr(p, "entry="))) + DIE("Invalid POST request: missing entry"); + p += sizeof("entry=") - 1; + decode_query(p, buffer); + /* replace plus signs */ + p = buffer; + while ((p = strstr(p, "+"))) + *p = ' '; + fwrite(buffer, 1, strlen(buffer), *known_hosts); + fwrite("\n", 1, 1, *known_hosts); + khslen = strlen(khsbuf); + khsbuf[khslen] = '~'; + khsbuf[khslen + 1] = '\0'; + if (!(known_hosts_tmp = fopen(khsbuf, "w+"))) + PDIE("Error opening temporary hosts file"); + rewind(*known_hosts); + *portp = '\0'; + total = 0; + while (fgets(buffer, BUFSIZE, *known_hosts)) { + len = strlen(buffer); + if (!len) + continue; + if ((total += len) > last_pos) { + /* finished */ + fwrite(buffer, 1, len, known_hosts_tmp); + break; + } + if (buffer[len - 1] != '\n') { + /* clean up */ + fclose(known_hosts_tmp); + unlink(khsbuf); + DIE("Line too long"); + } + if (!(p = strstr(buffer, " "))) + DIE("Invalid entry in known_hosts file"); + *p = '\0'; + if (strcmp(buffer, hostp)) { + *p = ' '; + fwrite(buffer, 1, len, known_hosts_tmp); + } + } + *portp = ':'; + memcpy(buffer, khsbuf, BUFSIZE + 1); + buffer[khslen] = '\0'; + fclose(*known_hosts); + fclose(known_hosts_tmp); + if (rename(khsbuf, buffer)) + PDIE("Failed to rename temporary file"); + khsbuf[khslen] = '\0'; + if (!(*known_hosts = fopen(khsbuf, "a+"))) + PDIE("Failed to re-open known hosts file"); + } else if (strncmp(p, "once", 4)) { + DIE("Invalid POST request"); + } +} + +FILE *open_known_hosts(char *khsbuf) +{ + const char *known_hosts_path, *xdg_dir, *home_dir; + char *p; + size_t len; + struct stat s; + FILE *known_hosts; + + known_hosts_path = getenv("GMIFETCH_KNOWN_HOSTS"); + if (!known_hosts_path) { + xdg_dir = getenv("XDG_CONFIG_HOME"); + if ((xdg_dir = getenv("XDG_CONFIG_HOME"))) { + len = strlen(xdg_dir); +#define CONFIG_REL "/gmifetch/known_hosts" + if (len + sizeof(CONFIG_REL) > BUFSIZE) + DIE("Error: config directory path too long"); + memcpy(khsbuf, xdg_dir, len); + memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL)); + } else { + if (!(home_dir = getenv("HOME"))) + home_dir = getpwuid(getuid())->pw_dir; + if (!home_dir) + DIE("Error: failed to get HOME directory"); +#undef CONFIG_REL +#define CONFIG_REL "/.config/gmifetch/known_hosts" + len = strlen(home_dir); + if (len + sizeof(CONFIG_REL) > BUFSIZE) + DIE("Error: home directory path too long"); + memcpy(khsbuf, home_dir, len); + memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL)); + } + } else { + len = strlen(known_hosts_path); + if (len > BUFSIZE) + DIE("Error: known hosts path too long"); + memcpy(khsbuf, known_hosts_path, len); + } + p = khsbuf; + if (*p == '/') + ++p; + for (; *p; ++p) { + if (*p == '/') { + *p = '\0'; + if (stat(khsbuf, &s) == -1) { + if (errno != ENOENT) + PDIE("Error calling stat"); + if (mkdir(khsbuf, 0755) == -1) + PDIE("Error calling mkdir"); + } else if (!S_ISDIR(s.st_mode)) { + if (mkdir(khsbuf, 0755) == -1) + PDIE("Error calling mkdir"); + } + *p = '/'; + } + } + if (!(known_hosts = fopen(khsbuf, "a+"))) + PDIE("Error opening known hosts file"); + return known_hosts; +} + +int main(int argc, const char *argv[]) +{ + const char *input_url, *method, *all_proxy; + char *hostp, *portp, *pathp, *endp, *stored_digestp; + int connect_res; + time_t their_time; + char hashbuf2[EVP_MAX_MD_SIZE * 3 + 1]; + char urlbuf[BUFSIZE + 1], khsbuf[BUFSIZE + 2], linebuf[BUFSIZE + 1]; + FILE *known_hosts; + + if (argc != 2) { + input_url = getenv("QUERY_STRING"); + if (!input_url) + DIE("Usage: gmifetch [url] (or set QUERY_STRING)"); + } else { + input_url = argv[1]; + } + all_proxy = getenv("ALL_PROXY"); +#define PROXY_ERR "gmifetch does not support proxies yet. Please disable" \ + "your proxy for gemini URLs if you wish to proceed anyway." + if (all_proxy && *all_proxy) + DIE(PROXY_ERR); + known_hosts = open_known_hosts(khsbuf); + setup_ssl(); + extract_hostname(input_url, &hostp, &portp, &pathp, &endp, urlbuf); + method = getenv("REQUEST_METHOD"); + if (method && !strcmp(method, "POST")) + read_post(hostp, portp, pathp, urlbuf, khsbuf, &known_hosts); + connect_res = connect(hostp, portp, pathp, endp, linebuf, &stored_digestp, + &their_time, hashbuf2, known_hosts); + if (connect_res == 1) { /* valid certificate */ + /* I really wish this was explicitly mentioned in the + * standard. Something like: + * + * !!!WARNING WARNING WARNING some gemini servers will + * not accept URLs containing the default port number!!! + */ + if (!strncmp(portp, ":1965", 5)) + /* move including null terminator */ + memmove(portp, &portp[5], strlen(&portp[5]) + 1); + BIO_puts(conn, urlbuf); + read_response(urlbuf); + } else if (connect_res == 0) { /* invalid certificate */ + printf(INVALID_CERT_RESPONSE, stored_digestp, hashbuf2, + khsbuf); + } else if (connect_res == -1) { /* no certificate */ + *portp = '\0'; + printf(UNKNOWN_CERT_RESPONSE, khsbuf, hashbuf2, hostp, + hashbuf2, (unsigned long)their_time); + } else { /* -2: updated expiration date */ + *portp = '\0'; + printf(UPDATED_CERT_RESPONSE, khsbuf, + ctime(&their_time), hostp, hashbuf2, + (unsigned long)their_time); + } + BIO_free_all(conn); + exit(0); +} -- cgit 1.4.1-2-gfad0