summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authordongdigua2024-03-30 17:30:11 +0800
committerdongdigua2024-03-30 17:43:27 +0800
commit81e30ea236a32e3b29818509db5c8a87ca041325 (patch)
tree27bfd2aab2f198aabaa3339bcf03af67d8db82b4
downloadaur-81e30ea236a32e3b29818509db5c8a87ca041325.tar.gz
initial commit
patch is from https://git.sr.ht/~rkta/blog use commit 98cf0c9e because the newer version is broken
-rw-r--r--.SRCINFO20
-rw-r--r--PKGBUILD44
-rw-r--r--gemini.patch1038
3 files changed, 1102 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..aa858c5d84ed
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,20 @@
+pkgbase = w3m-gemini
+ pkgdesc = w3m with gemini support
+ pkgver = 0.5.3.git20230713_1
+ pkgrel = 1
+ url = https://salsa.debian.org/debian/w3m
+ arch = x86_64
+ license = custom
+ makedepends = git
+ makedepends = imlib2
+ depends = openssl
+ depends = gc
+ depends = ncurses
+ depends = gpm
+ optdepends = imlib2: for graphics support
+ source = git+https://salsa.debian.org/debian/w3m.git#commit=edc602651c506aeeb60544b55534dd1722a340d3
+ source = gemini.patch
+ sha256sums = SKIP
+ sha256sums = 43a5711390b22b80533a915a9aade18301781430e4d0cceea8f88df989a89d2a
+
+pkgname = w3m-gemini
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..e7fc5976439e
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,44 @@
+# Maintainer: dongdigua <dongdigua砹outlook碘com>
+# Contributor: Gaetan Bisson <bisson@archlinux.org>
+# Contributor: dorphell <dorphell@archlinux.org>
+
+pkgname=w3m-gemini
+_gitcommit=edc602651c506aeeb60544b55534dd1722a340d3
+_pkgver=0.5.3.git20230713-1
+pkgver=${_pkgver/-/_}
+pkgrel=1
+pkgdesc='w3m with gemini support'
+url='https://salsa.debian.org/debian/w3m'
+license=('custom')
+arch=('x86_64')
+makedepends=('git' 'imlib2')
+optdepends=('imlib2: for graphics support')
+depends=('openssl' 'gc' 'ncurses' 'gpm')
+source=("git+https://salsa.debian.org/debian/w3m.git#commit=${_gitcommit}" 'gemini.patch')
+sha256sums=('SKIP' '43a5711390b22b80533a915a9aade18301781430e4d0cceea8f88df989a89d2a')
+
+# There's also the maintainer's github repo, usually in sync with Debian's:
+# https://github.com/tats/w3m
+
+build() {
+ cd ${pkgname}
+ patch -p1 < ${srcdir}/gemini.patch
+ ./configure \
+ --prefix=/usr \
+ --libexecdir=/usr/lib \
+ --enable-image=x11,fb \
+ --with-imagelib=imlib2 \
+ --with-termlib=ncurses \
+ --disable-w3mmailer \
+
+ make
+}
+
+package() {
+ cd ${pkgname}
+ make DESTDIR="${pkgdir}" install
+
+ install -d "${pkgdir}"/usr/share/{doc,licenses}/w3m
+ install -m644 doc/* "${pkgdir}/usr/share/doc/w3m"
+ ln -s ../../doc/w3m/README "${pkgdir}/usr/share/licenses/w3m"
+}
diff --git a/gemini.patch b/gemini.patch
new file mode 100644
index 000000000000..3dfaeb6c8a02
--- /dev/null
+++ b/gemini.patch
@@ -0,0 +1,1038 @@
+From: Rene Kita <mail@rkta.de>
+Subject: Add Gemini support
+
+This patch adds Gemini support to w3m. This is still work in progress. Consider
+this as alpha stage. INPUT may break with unquoted chars.
+
+The patch should apply to the current master branch of
+https://github.com/tats/w3m (commit c8223fed7cc631ad85d8e5665e509e7988bedbab)
+also to the releases w3m-0.5.3+git20210102, w3m-0.5.3-git20220429 and
+w3m-0.5.3+git20230121.
+---
+ buffer.c | 9 +
+ display.c | 3
+ doc-de/README.func | 1
+ doc/README.func | 1
+ file.c | 292 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ html.h | 1
+ istream.c | 140 ++++++++++++++++++-----
+ istream.h | 5
+ main.c | 152 +++++++++++++++++++++++++
+ proto.h | 1
+ rc.c | 5
+ rc.h | 2
+ scripts/w3mhelp.cgi.in | 5
+ url.c | 35 +++++
+ 14 files changed, 613 insertions(+), 39 deletions(-)
+
+--- a/file.c
++++ b/file.c
+@@ -30,6 +30,8 @@
+
+ #define MAX_INPUT_SIZE 80 /* TODO - max should be screen line length */
+
++Buffer * loadGeminiBuffer(URLFile *uf, Buffer *volatile newBuf);
++
+ static int frame_source = 0;
+ static int need_number = 0;
+
+@@ -248,7 +250,7 @@ loadSomething(URLFile *f,
+ )
+ buf->type = "text/html";
+ else
+- buf->type = "text/plain";
++ buf->type = buf->type ? buf->type : "text/plain";
+ return buf;
+ }
+
+@@ -1702,7 +1704,7 @@ loadGeneralFile(char *path, ParsedURL *v
+ URLFile f, *volatile of = NULL;
+ ParsedURL pu;
+ Buffer *b = NULL;
+- Buffer *(*volatile proc)(URLFile *, Buffer *) = loadBuffer;
++ Buffer *(*volatile proc)(URLFile *, Buffer *);
+ char *volatile tpath;
+ char *volatile t = "text/plain", *p, *volatile real_type = NULL;
+ Buffer *volatile t_buf = NULL;
+@@ -2054,6 +2056,166 @@ loadGeneralFile(char *path, ParsedURL *v
+ else if (pu.scheme == SCM_DATA) {
+ t = f.guess_type;
+ }
++ else if (pu.scheme == SCM_GEMINI) {
++ Str err, hdr, meta;
++ char *m;
++ long status;
++
++ if (fmInitialized) {
++ term_cbreak();
++ message(Sprintf("%s contacted. Waiting for reply...", pu.host)->
++ ptr, 0, 0);
++ refresh();
++ }
++
++ hdr = StrmyUFgets(&f);
++ if (!hdr || !hdr->length) {
++ err = Sprintf("Could not read from %s", pu.host);
++ goto fail;
++ }
++
++ /* Gemini response header: <STATUS><SPACE><META><CR><LF> */
++ status = strtol(hdr->ptr, &m, 10);
++ if (!IS_SPACE(*m)
++ || hdr->length > 1024 + 2
++ || hdr->ptr[hdr->length - 2] != '\r'
++ || hdr->ptr[hdr->length - 1] != '\n') {
++ Strchop(hdr);
++ err = Sprintf("Invalid Gemini response header: %s", hdr->ptr);
++ goto fail;
++ }
++
++ Strchop(hdr);
++ SKIP_BLANKS(m);
++ meta = Strnew_charp(m);
++
++ t_buf = newBuffer(INIT_BUFFER_WIDTH);
++ t_buf->document_header = newTextList();
++
++ charset = WC_CES_UTF_8;
++ switch(status) {
++ case 10: /* INPUT */
++ case 11: /* SENSITIVE INPUT */
++ /* TODO(rkta): Fix quoting */
++ term_raw();
++ p = inputLine(Strnew_m_charp(meta->ptr, ": ", NULL)->ptr, NULL,
++ status == 11 ? IN_PASSWORD : IN_STRING);
++ if (!p || !*p)
++ return NULL;
++ tpath = url_encode(Strnew_m_charp(tpath, "?", p, NULL)->ptr, NULL, 0);
++ request = NULL;
++ UFclose(&f);
++ current = New(ParsedURL);
++ copyParsedURL(current, &pu);
++ goto load_doc;
++ case 20: /* SUCCESS */
++ /*
++ * Gemini spec section 3.3:
++ * If <META> is an empty string, the MIME type MUST default to
++ * "text/gemini; charset=utf-8".
++ */
++ if (!*meta->ptr) {
++ t = "text/gemini";
++ pushText(t_buf->document_header, hdr->ptr);
++ break;
++ }
++
++ t = m = meta->ptr;
++ while (*m && !IS_SPACE(*m) && *m != ';') m++;
++ if (*m)
++ *m++ = '\0';
++ SKIP_BLANKS(m);
++
++ while (*m) { /* Not done parsing */
++ if (!strncmp(m, "lang=", 5)) {
++ /* Ignore this */
++ while (*m && *m++ != ';');
++ SKIP_BLANKS(m);
++ }
++ else if (!strncmp(m, "charset=", 8)) {
++ m += 8;
++ charset = wc_charset_to_ces(m);
++ if (!charset) {
++ err = Sprintf("Unknown charset: %s", hdr->ptr);
++ goto fail;
++ }
++ while (*m && *m++ != ';');
++ SKIP_BLANKS(m);
++ }
++ else {
++ err = Sprintf("Can't parser Gemini response header: %s", hdr->ptr);
++ goto fail;
++ }
++ }
++
++ t_buf->buffername = parsedURL2Str(&pu)->ptr;
++ t_buf->document_charset = charset;
++ t_buf->type = t;
++ pushText(t_buf->document_header, hdr->ptr);
++ break;
++ case 30: /* REDIRECT - TEMPORARY */
++ case 31: /* REDIRECT - PERMANENT */
++ p = meta->ptr;
++ if (*p == '.' || *p == '/' || !strchr(p, ':')) /* relative URI */
++ p = Strnew_m_charp("gemini://", pu.host, "/", meta->ptr,
++ NULL)->ptr;
++ tpath = url_encode(p, NULL, 0);
++ request = NULL;
++ UFclose(&f);
++ current = New(ParsedURL);
++ copyParsedURL(current, &pu);
++ t_buf = newBuffer(INIT_BUFFER_WIDTH);
++ t_buf->bufferprop |= BP_REDIRECTED;
++ status = HTST_NORMAL;
++ goto load_doc;
++ case 40: /* TEMPORARY FAILURE */
++ err = Sprintf("TEMPORARY FAILURE: %s", hdr->ptr);
++ goto fail;
++ case 41: /* SERVER UNAVAILABLE */
++ err = Sprintf("SERVER UNAVAILABLE: %s", hdr->ptr);
++ goto fail;
++ case 42: /* CGI ERROR */
++ err = Sprintf("CGI ERROR: %s", hdr->ptr);
++ goto fail;
++ case 43: /* PROXY ERROR */
++ err = Sprintf("PROXY ERROR: %s", hdr->ptr);
++ goto fail;
++ case 44: /* SLOW DOWN */
++ err = Sprintf("Status code %d not implemented", status);
++ goto fail;
++ case 50: /* PERMANENT FAILURE */
++ err = Sprintf("PERMANENT FAILURE: %s", hdr->ptr);
++ goto fail;
++ case 51: /* NOT FOUND */
++ err = Sprintf("NOT FOUND: %s", hdr->ptr);
++ goto fail;
++ case 52: /* GONE */
++ err = Sprintf("GONE: %s", hdr->ptr);
++ goto fail;
++ case 53: /* PROXY REQUEST REFUSED */
++ err = Sprintf("PROXY REQUEST REFUSED: %s", hdr->ptr);
++ goto fail;
++ case 59: /* BAD REQUEST */
++ err = Sprintf("BAD REQUEST: %s", hdr->ptr);
++ goto fail;
++ case 60: /* CLIENT CERTIFICATE REQUIRED */
++ err = Sprintf("CLIENT CERTIFICATE REQUIRED: %s", hdr->ptr);
++ goto fail;
++ case 61: /* CERTIFICATE NOT AUTHORISED */
++ err = Sprintf("CERTIFICATE NOT AUTHORISED: %s", hdr->ptr);
++ goto fail;
++ case 62: /* CERTIFICATE NOT VALID */
++ err = Sprintf("CERTIFICATE NOT VALID: %s", hdr->ptr);
++ goto fail;
++ default:
++ err = Sprintf("Unknown status code %d", status);
++fail:
++ TRAP_OFF;
++ disp_err_message(err->ptr, FALSE);
++ UFclose(&f);
++ return NULL;
++ }
++ }
+ else if (searchHeader) {
+ searchHeader = SearchHeader = FALSE;
+ if (t_buf == NULL)
+@@ -2232,6 +2394,8 @@ loadGeneralFile(char *path, ParsedURL *v
+
+ if (is_html_type(t))
+ proc = loadHTMLBuffer;
++ else if (!strcmp(t, "text/gemini"))
++ proc = loadGeminiBuffer;
+ else if (is_plain_text_type(t))
+ proc = loadBuffer;
+ #ifdef USE_IMAGE
+@@ -2486,7 +2650,6 @@ is_boundary(unsigned char *ch1, unsigned
+ return 1;
+ }
+
+-
+ static void
+ set_breakpoint(struct readbuffer *obuf, int tag_length)
+ {
+@@ -7643,6 +7806,129 @@ loadGopherSearch0(URLFile *uf, ParsedURL
+ }
+ #endif /* USE_GOPHER */
+
++Buffer *
++loadGeminiBuffer(URLFile *uf, Buffer *volatile buf)
++{
++ Anchor *a;
++ FILE *src = NULL;
++ wc_ces charset;
++ Str l, line, tmpf;
++ char *spacer, *p, *q;
++ int hseq, len, nlines, pre = 0;
++ clen_t linelen = 0, trbyte = 0;
++ Lineprop *propBuf = NULL;
++ MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
++
++ if (SETJMP(AbortLoading) != 0) {
++ goto _end;
++ }
++ TRAP_ON;
++
++ charset = buf->document_charset;
++ hseq = nlines = 0;
++ if (buf->sourcefile == NULL &&
++ (uf->scheme != SCM_LOCAL || buf->mailcap)) {
++ tmpf = tmpfname(TMPF_SRC, NULL);
++ if (!(src = fopen(tmpf->ptr, "w"))) {
++ TRAP_OFF;
++ disp_err_message(Sprintf("Cannot open %s, aborting!", tmpf->ptr)->ptr,
++ FALSE);
++ return NULL;
++ }
++ buf->sourcefile = tmpf->ptr;
++ }
++
++ while ((line = StrmyISgets(uf->stream)) && line->length) {
++ if (src)
++ Strfputs(line, src);
++ linelen += line->length;
++ line = convertLine(uf, line, PAGER_MODE, &charset, charset);
++ Strchop(line);
++
++ p = line->ptr;
++ if (!strncmp(p, "```", 3)) {
++ pre = !pre;
++ continue;
++ }
++
++ ++nlines;
++ showProgress(&linelen, &trbyte);
++
++ if (pre) {
++ line = checkType(line, &propBuf, NULL);
++ addnewline(buf, line->ptr, propBuf, NULL,
++ line->length, FOLD_BUFFER_WIDTH, nlines);
++ continue;
++ }
++
++ if (!strncmp(p, "=>", 2)) {
++ p += 2;
++ SKIP_BLANKS(p);
++ q = p;
++ while(*q && !IS_SPACE(*q)) q++;
++ if (*q)
++ *q++ = '\0';
++ SKIP_BLANKS(q);
++ if (!*q)
++ q = p;
++ buf->href = putAnchor(buf->href,
++ url_encode(p, baseURL(buf), charset),
++ NULL, &a, NO_REFERER, q, '\0', nlines, 0);
++ buf->hmarklist = putHmarker(buf->hmarklist, currentLn(buf),
++ 0, hseq);
++ if (displayLinkNumber)
++ line = Sprintf("[%d]: %s", hseq + 1, q);
++ else
++ line = Sprintf("%s", q);
++ line = checkType(line, &propBuf, NULL);
++ for (int i = 0; i < line->length; i++)
++ propBuf[i] |= PE_ANCHOR;
++ a->end.line = nlines;
++ a->end.pos = line->length;
++ a->hseq = hseq++;
++ addnewline(buf, line->ptr, propBuf, NULL,
++ line->length, FOLD_BUFFER_WIDTH, nlines);
++ continue;
++ }
++
++ len = line->length;
++ spacer = "";
++ if (*p == '>') {
++ spacer = " ";
++ p++;
++ SKIP_BLANKS(p);
++ }
++ while (len > buf->width) {
++ q = &p[buf->width - (strlen(spacer) + 1)];
++ while(!IS_SPACE(*q) && q != p) q--;
++ if (q == p)
++ break;
++ *q = '\0';
++ l = checkType(Strnew_m_charp(spacer, p, NULL), &propBuf, NULL);
++ addnewline(buf, l->ptr, propBuf, NULL, l->length, -1, nlines);
++ nlines++;
++ len -= (l->length - strlen(spacer));
++ if (line->ptr[0] == '*')
++ spacer = " ";
++ p = q + 1;
++ }
++ l = checkType(Strnew_m_charp(spacer, p, NULL), &propBuf, NULL);
++ addnewline(buf, l->ptr, propBuf, NULL, l->length, FOLD_BUFFER_WIDTH,
++ nlines);
++ }
++ _end:
++ TRAP_OFF;
++ if (src)
++ fclose(src);
++ UFclose(uf);
++ buf->topLine = buf->firstLine;
++ buf->lastLine = buf->currentLine;
++ buf->currentLine = buf->firstLine;
++ buf->trbyte = trbyte + linelen;
++
++ return buf;
++}
++
+ /*
+ * loadBuffer: read file and make new buffer
+ */
+--- a/html.h
++++ b/html.h
+@@ -419,5 +419,6 @@ struct environment {
+ #ifdef USE_SSL
+ #define SCM_HTTPS 13
+ #endif /* USE_SSL */
++#define SCM_GEMINI 14
+
+ #endif /* _HTML_H */
+--- a/url.c
++++ b/url.c
+@@ -75,6 +75,7 @@ static int
+ #ifdef USE_SSL
+ 443, /* https */
+ #endif /* USE_SSL */
++ 1965 /* gemini */
+ };
+
+ struct cmdtable schemetable[] = {
+@@ -95,6 +96,7 @@ struct cmdtable schemetable[] = {
+ #ifdef USE_SSL
+ {"https", SCM_HTTPS},
+ #endif /* USE_SSL */
++ {"gemini", SCM_GEMINI},
+ {NULL, SCM_UNKNOWN},
+ };
+
+@@ -235,6 +237,7 @@ DefaultFile(int scheme)
+ case SCM_LOCAL_CGI:
+ case SCM_FTP:
+ case SCM_FTPDIR:
++ case SCM_GEMINI:
+ return allocStr("/", -1);
+ }
+ return NULL;
+@@ -256,7 +259,7 @@ free_ssl_ctx(void)
+ if (ssl_ctx != NULL)
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+- ssl_accept_this_site(NULL);
++ ssl_accept_this_site(NULL, NULL, 0);
+ }
+
+ #if SSLEAY_VERSION_NUMBER >= 0x00905100
+@@ -1262,6 +1265,7 @@ _parsedURL2Str(ParsedURL *pu, int pass,
+ "news", "news", "data", "mailto",
+ #ifdef USE_SSL
+ "https",
++ "gemini",
+ #endif /* USE_SSL */
+ };
+
+@@ -1962,6 +1966,35 @@ openURL(char *url, ParsedURL *pu, Parsed
+ }
+ break;
+ #endif /* USE_GOPHER */
++ case SCM_GEMINI:
++ sock = openSocket(pu->host, schemeNumToName(pu->scheme), pu->port);
++ if (sock < 0) {
++ *status = HTST_MISSING;
++ return uf;
++ }
++ if (!(sslh = openSSLHandle(sock, pu->host,
++ &uf.ssl_certificate))) {
++ *status = HTST_MISSING;
++ return uf;
++ }
++ hr->flag |= HR_FLAG_LOCAL;
++ tmp = Strnew_m_charp("gemini://", pu->host, pu->file,
++ pu->query ?
++ Strnew_m_charp("?", pu ->query, NULL)->ptr :
++ "",
++ "\r\n", NULL);
++ *status = HTST_NORMAL;
++ uf.stream = newSSLStream(sslh, sock);
++ SSL_write(sslh, tmp->ptr, tmp->length);
++ if(w3m_reqlog){
++ FILE *ff = fopen(w3m_reqlog, "a");
++ if (ff == NULL)
++ return uf;
++ fputs("GEMINI: request via SSL\n", ff);
++ fwrite(tmp->ptr, sizeof(char), tmp->length, ff);
++ fclose(ff);
++ }
++ return uf;
+ #ifdef USE_NNTP
+ case SCM_NNTP:
+ case SCM_NNTP_GROUP:
+--- a/display.c
++++ b/display.c
+@@ -382,7 +382,8 @@ displayBuffer(Buffer *buf, int mode)
+ if (buf->height == 0)
+ buf->height = LASTLINE + 1;
+ if ((buf->width != INIT_BUFFER_WIDTH &&
+- (is_html_type(buf->type) || FoldLine))
++ (is_html_type(buf->type) || FoldLine || !strcmp(buf->type,
++ "text/gemini")))
+ || buf->need_reshape) {
+ buf->need_reshape = TRUE;
+ reshapeBuffer(buf);
+--- a/buffer.c
++++ b/buffer.c
+@@ -18,6 +18,10 @@ extern int do_getch();
+ char *NullLine = "";
+ Lineprop NullProp[] = { 0 };
+
++
++/* TODO(rkta): header file */
++Buffer * loadGeminiBuffer(URLFile *uf, Buffer *volatile buf);
++
+ /*
+ * Buffer creation
+ */
+@@ -507,6 +511,8 @@ reshapeBuffer(Buffer *buf)
+ {
+ URLFile f;
+ Buffer sbuf;
++ /* TODO(rkta): Shouldn't we do this for all schemes? */
++ Str t;
+ #ifdef USE_M17N
+ wc_uint8 old_auto_detect = WcOption.auto_detect;
+ #endif
+@@ -517,6 +523,7 @@ reshapeBuffer(Buffer *buf)
+ buf->width = INIT_BUFFER_WIDTH;
+ if (buf->sourcefile == NULL)
+ return;
++ t = Strnew_charp(buf->type);
+ init_stream(&f, SCM_LOCAL, NULL);
+ examineFile(buf->mailcap_source ? buf->mailcap_source : buf->sourcefile,
+ &f);
+@@ -562,6 +569,8 @@ reshapeBuffer(Buffer *buf)
+ #endif
+ if (is_html_type(buf->type))
+ loadHTMLBuffer(&f, buf);
++ else if (!strcmp(t->ptr, "text/gemini"))
++ loadGeminiBuffer(&f, buf);
+ else
+ loadBuffer(&f, buf);
+ UFclose(&f);
+--- a/main.c
++++ b/main.c
+@@ -406,6 +406,32 @@ die_oom(size_t bytes)
+ return NULL;
+ }
+
++/* TODO(rkta): header */
++TextList * load_known_hosts(void);
++
++TextList *
++load_known_hosts(void)
++{
++ FILE *f;
++ Str l;
++ TextList *kh;
++
++ kh = newTextList();
++
++ if (!(f = fopen(known_hosts_file, "r")))
++ return kh;
++
++ while (!feof(f)) {
++ l = Strfgets(f);
++ if (!l->length)
++ continue;
++ Strchop(l);
++ pushText(kh, l->ptr);
++ }
++ fclose(f);
++ return kh;
++}
++
+ int
+ main(int argc, char **argv)
+ {
+@@ -908,6 +934,9 @@ main(int argc, char **argv)
+ if (UseHistory)
+ loadHistory(URLHist);
+ #endif /* not USE_HISTORY */
++ known_hosts_file = rcFile("known_hosts");
++ if (ssl_known_hosts)
++ known_hosts = load_known_hosts();
+
+ #ifdef USE_M17N
+ /* if (w3m_dump)
+@@ -2557,10 +2586,36 @@ DEFUN(movRW, NEXT_WORD, "Move to the nex
+ displayBuffer(Currentbuf, B_NORMAL);
+ }
+
++static int
++save_known_hosts(void)
++{
++ FILE *fp;
++ TextListItem *host;
++ char *khf, *tmpf;
++
++ if (!known_hosts)
++ return 0;
++
++ tmpf = tmpfname(TMPF_HIST, NULL)->ptr;
++ khf = rcFile("known_hosts");
++ if (!(fp = fopen(tmpf, "w")))
++ return 1;
++
++ for (host = known_hosts->first; host; host = host->next)
++ fprintf(fp, "%s\n", host->ptr);
++
++ fclose(fp);
++ if (rename(tmpf, khf))
++ return 1;
++
++ return 0;
++}
++
+ static void
+ _quitfm(int confirm)
+ {
+ char *ans = "y";
++ int err = 0;
+
+ if (checkDownloadList())
+ /* FIXME: gettextize? */
+@@ -2587,7 +2642,9 @@ _quitfm(int confirm)
+ if (UseHistory && SaveURLHist)
+ saveHistory(URLHist, URLHistSize);
+ #endif /* USE_HISTORY */
+- w3m_exit(0);
++ if ((err = save_known_hosts()))
++ perror(NULL);
++ w3m_exit(err);
+ }
+
+ /* Quit */
+@@ -4786,6 +4843,13 @@ DEFUN(vwSrc, SOURCE VIEW, "Toggle betwee
+ displayBuffer(Currentbuf, B_NORMAL);
+ return;
+ }
++
++ /* TODO(rkta): keep && document this */
++ if (!strcmp(Currentbuf->type, "text/gemini")) {
++ geminize();
++ return;
++ }
++
+ if (Currentbuf->sourcefile == NULL) {
+ if (Currentbuf->pagerSource &&
+ !strcasecmp(Currentbuf->type, "text/plain")) {
+@@ -4864,6 +4928,91 @@ DEFUN(vwSrc, SOURCE VIEW, "Toggle betwee
+ displayBuffer(Currentbuf, B_NORMAL);
+ }
+
++DEFUN(geminize, GEMINIZE, "Toggle between text/gemini shown or processed")
++{
++ Buffer *buf;
++
++ if (Currentbuf->type == NULL || Currentbuf->bufferprop & BP_FRAME)
++ return;
++ if ((buf = Currentbuf->linkBuffer[LB_SOURCE]) != NULL ||
++ (buf = Currentbuf->linkBuffer[LB_N_SOURCE]) != NULL) {
++ Currentbuf = buf;
++ displayBuffer(Currentbuf, B_NORMAL);
++ return;
++ }
++
++ if (Currentbuf->sourcefile == NULL) {
++ if (Currentbuf->pagerSource &&
++ !strcasecmp(Currentbuf->type, "text/plain")) {
++#ifdef USE_M17N
++ wc_ces old_charset;
++ wc_bool old_fix_width_conv;
++#endif
++ FILE *f;
++ Str tmpf = tmpfname(TMPF_SRC, NULL);
++ f = fopen(tmpf->ptr, "w");
++ if (f == NULL)
++ return;
++#ifdef USE_M17N
++ old_charset = DisplayCharset;
++ old_fix_width_conv = WcOption.fix_width_conv;
++ DisplayCharset = (Currentbuf->document_charset != WC_CES_US_ASCII)
++ ? Currentbuf->document_charset : 0;
++ WcOption.fix_width_conv = WC_FALSE;
++#endif
++ saveBufferBody(Currentbuf, f, TRUE);
++#ifdef USE_M17N
++ DisplayCharset = old_charset;
++ WcOption.fix_width_conv = old_fix_width_conv;
++#endif
++ fclose(f);
++ Currentbuf->sourcefile = tmpf->ptr;
++ }
++ else {
++ return;
++ }
++ }
++
++ buf = newBuffer(INIT_BUFFER_WIDTH);
++
++ if (!strcmp(Currentbuf->type, "text/gemini")) {
++ buf->type = "text/plain";
++ buf->real_type = Currentbuf->real_type;
++ buf->buffername = Sprintf("source of %s", Currentbuf->buffername)->ptr;
++ buf->linkBuffer[LB_N_SOURCE] = Currentbuf;
++ Currentbuf->linkBuffer[LB_SOURCE] = buf;
++ }
++ else if (!strcasecmp(Currentbuf->type, "text/plain")) {
++ buf->type = "text/gemini";
++ if (Currentbuf->real_type &&
++ !strcasecmp(Currentbuf->real_type, "text/plain"))
++ buf->real_type = "text/gemini";
++ else
++ buf->real_type = Currentbuf->real_type;
++ buf->buffername = Sprintf("Gemini view of %s",
++ Currentbuf->buffername)->ptr;
++ buf->linkBuffer[LB_SOURCE] = Currentbuf;
++ Currentbuf->linkBuffer[LB_N_SOURCE] = buf;
++ }
++ else {
++ return;
++ }
++ buf->currentURL = Currentbuf->currentURL;
++ buf->real_scheme = Currentbuf->real_scheme;
++ buf->filename = Currentbuf->filename;
++ buf->sourcefile = Currentbuf->sourcefile;
++ buf->header_source = Currentbuf->header_source;
++ buf->search_header = Currentbuf->search_header;
++ buf->document_charset = Currentbuf->document_charset;
++ buf->clone = Currentbuf->clone;
++ (*buf->clone)++;
++
++ buf->need_reshape = TRUE;
++ reshapeBuffer(buf);
++ pushBuffer(buf);
++ displayBuffer(Currentbuf, B_NORMAL);
++}
++
+ /* reload */
+ DEFUN(reload, RELOAD, "Load current document anew")
+ {
+@@ -5089,6 +5238,7 @@ chkURLBuffer(Buffer *buf)
+ "https?://[a-zA-Z0-9:%\\-\\./_@]*\\[[a-fA-F0-9:][a-fA-F0-9:\\.]*\\][a-zA-Z0-9:%\\-\\./?=~_\\&+@#,\\$;]*",
+ "ftp://[a-zA-Z0-9:%\\-\\./_@]*\\[[a-fA-F0-9:][a-fA-F0-9:\\.]*\\][a-zA-Z0-9:%\\-\\./=_+@#,\\$]*",
+ #endif /* INET6 */
++ "gemini:/[a-zA-Z0-9:%\\-\\./=_\\+@#,\\$;]*",
+ NULL
+ };
+ int i;
+--- a/proto.h
++++ b/proto.h
+@@ -97,6 +97,7 @@ extern void peekURL(void);
+ extern void peekIMG(void);
+ extern void curURL(void);
+ extern void vwSrc(void);
++extern void geminize(void);
+ extern void reload(void);
+ extern void reshape(void);
+ extern void chkURL(void);
+--- a/istream.c
++++ b/istream.c
+@@ -1,6 +1,7 @@
+ /* $Id: istream.c,v 1.27 2010/07/18 13:43:23 htrb Exp $ */
+ #include "fm.h"
+ #include "myctype.h"
++#include "rc.h"
+ #include "istream.h"
+ #include <signal.h>
+ #ifdef USE_SSL
+@@ -350,15 +351,43 @@ ISeos(InputStream stream)
+ }
+
+ #ifdef USE_SSL
+-static Str accept_this_site;
++TextList *known_hosts;
++char *known_hosts_file;
++static Str _accept_this_site;
++
++static Str
++X509_fingerprint(X509 *x)
++{
++ Str fp = Strnew();
++ const EVP_MD *digest;
++ unsigned n;
++ unsigned char md[EVP_MAX_MD_SIZE];
++
++ digest = EVP_get_digestbyname("sha1");
++ X509_digest(x, digest, md, &n);
++ for(int i = 0; i < n - 1; i++)
++ Strcat(fp, Sprintf("%02x:", md[i]));
++ Strcat(fp, Sprintf("%02x", md[n - 1]));
++
++ return fp;
++}
+
+ void
+-ssl_accept_this_site(char *hostname)
++ssl_accept_this_site(char *hostname, X509 *x, int perm)
+ {
+- if (hostname)
+- accept_this_site = Strnew_charp(hostname);
+- else
+- accept_this_site = NULL;
++
++ if (!hostname)
++ return;
++
++ if (!perm) {
++ _accept_this_site = Strnew_charp(hostname);
++ }
++ else {
++ pushText(known_hosts, Strnew_m_charp(hostname,
++ " ",
++ X509_fingerprint(x)->ptr,
++ NULL)->ptr);
++ }
+ }
+
+ static int
+@@ -497,6 +526,50 @@ ssl_check_cert_ident(X509 * x, char *hos
+ return ret;
+ }
+
++/* TODO(rkta): header */
++TextList * load_known_hosts(void);
++
++/* Return true if the host's cert was manually accepted before */
++static int
++accept_this_site(const char *hostname, X509 *x)
++{
++ Str fp;
++ TextListItem *host;
++ char *h;
++ size_t n;
++
++ if (!ssl_known_hosts) {
++ if (!_accept_this_site)
++ return 0;
++ return !strncasecmp(_accept_this_site->ptr,
++ hostname,
++ _accept_this_site->length);
++ }
++
++ if (!known_hosts && (!(known_hosts = load_known_hosts())))
++ known_hosts = newTextList();
++
++ if (!(host = known_hosts->last)) /* Empty list */
++ return 0;
++
++ n = strlen(hostname);
++ fp = X509_fingerprint(x);
++ if (!strncasecmp(host->ptr, hostname, n)
++ && !strncmp(host->ptr + n + 1, fp->ptr, fp->length))
++ return 1;
++
++ for (host = host->prev; host; host = host->prev) {
++ if (!strncasecmp(host->ptr, hostname, n)
++ && !strncmp(host->ptr + n + 1, fp->ptr, fp->length)) {
++ h = host->ptr;
++ delText(known_hosts, host);
++ pushText(known_hosts, h);
++ return 1;
++ }
++ }
++ return 0;
++}
++
+ Str
+ ssl_get_certificate(SSL * ssl, char *hostname)
+ {
+@@ -504,7 +577,7 @@ ssl_get_certificate(SSL * ssl, char *hos
+ X509 *x;
+ X509_NAME *xn;
+ char *p;
+- int len;
++ int len, perm;
+ Str s;
+ char buf[2048];
+ Str amsg = NULL;
+@@ -513,10 +586,10 @@ ssl_get_certificate(SSL * ssl, char *hos
+
+ if (ssl == NULL)
+ return NULL;
++
+ x = SSL_get_peer_certificate(ssl);
+ if (x == NULL) {
+- if (accept_this_site
+- && strcasecmp(accept_this_site->ptr, hostname) == 0)
++ if (accept_this_site(hostname, x))
+ ans = "y";
+ else {
+ /* FIXME: gettextize? */
+@@ -537,7 +610,7 @@ ssl_get_certificate(SSL * ssl, char *hos
+ }
+ if (amsg)
+ disp_err_message(amsg->ptr, FALSE);
+- ssl_accept_this_site(hostname);
++ ssl_accept_this_site(hostname, x, 0);
+ /* FIXME: gettextize? */
+ s = amsg ? amsg : Strnew_charp("valid certificate");
+ return s;
+@@ -547,39 +620,46 @@ ssl_get_certificate(SSL * ssl, char *hos
+ * The chain length is automatically checked by OpenSSL when we
+ * set the verify depth in the ctx.
+ */
++ perm = 0;
+ if (ssl_verify_server) {
+ long verr;
+ if ((verr = SSL_get_verify_result(ssl))
+ != X509_V_OK) {
+ const char *em = X509_verify_cert_error_string(verr);
+- if (accept_this_site
+- && strcasecmp(accept_this_site->ptr, hostname) == 0)
++ if (accept_this_site(hostname, x))
+ ans = "y";
+ else {
+- /* FIXME: gettextize? */
+- emsg = Sprintf("%s: accept? (y/n)", em);
++ if (ssl_known_hosts)
++ emsg = Sprintf("%s: accept? (y)es/(n)o/(a)lways)", em);
++ else
++ emsg = Sprintf("%s: accept? (y/n)", em);
+ ans = inputAnswer(emsg->ptr);
+- }
+- if (ans && TOLOWER(*ans) == 'y') {
+- /* FIXME: gettextize? */
+- amsg = Sprintf("Accept unsecure SSL session: "
+- "unverified: %s", em);
+- }
+- else {
+- /* FIXME: gettextize? */
+- char *e =
+- Sprintf("This SSL session was rejected: %s", em)->ptr;
+- disp_err_message(e, FALSE);
+- free_ssl_ctx();
+- return NULL;
++ if (ans && TOLOWER(*ans) == 'y') {
++ /* FIXME: gettextize? */
++ amsg = Sprintf("Accept unsecure SSL session: "
++ "unverified: %s", em);
++ }
++ else if (ssl_known_hosts && ans && TOLOWER(*ans) == 'a') {
++ amsg = Sprintf("Permanently accepted unverified SSL"
++ "session: %s", em);
++ perm = 1;
++ }
++ else {
++ /* FIXME: gettextize? */
++ char *e =
++ Sprintf("This SSL session was rejected: %s", em)->ptr;
++ disp_err_message(e, FALSE);
++ free_ssl_ctx();
++ return NULL;
++ }
+ }
+ }
++ ssl_accept_this_site(hostname, x, perm);
+ }
+ #endif
+ emsg = ssl_check_cert_ident(x, hostname);
+ if (emsg != NULL) {
+- if (accept_this_site
+- && strcasecmp(accept_this_site->ptr, hostname) == 0)
++ if (accept_this_site(hostname, x))
+ ans = "y";
+ else {
+ Str ep = Strdup(emsg);
+@@ -601,10 +681,10 @@ ssl_get_certificate(SSL * ssl, char *hos
+ free_ssl_ctx();
+ return NULL;
+ }
++ ssl_accept_this_site(hostname, x, 0);
+ }
+ if (amsg)
+ disp_err_message(amsg->ptr, FALSE);
+- ssl_accept_this_site(hostname);
+ /* FIXME: gettextize? */
+ s = amsg ? amsg : Strnew_charp("valid certificate");
+ Strcat_charp(s, "\n");
+--- a/istream.h
++++ b/istream.h
+@@ -109,6 +109,9 @@ typedef struct encoded_stream *EncodedSt
+
+ typedef union input_stream *InputStream;
+
++extern TextList *known_hosts;
++extern char *known_hosts_file;
++
+ extern InputStream newInputStream(int des);
+ extern InputStream newFileStream(FILE * f, void (*closep) ());
+ extern InputStream newStrStream(Str s);
+@@ -130,7 +133,7 @@ int ISread_n(InputStream stream, char *d
+ extern int ISfileno(InputStream stream);
+ extern int ISeos(InputStream stream);
+ #ifdef USE_SSL
+-extern void ssl_accept_this_site(char *hostname);
++extern void ssl_accept_this_site(char *hostname, X509 *x, int perm);
+ extern Str ssl_get_certificate(SSL * ssl, char *hostname);
+ #endif
+
+--- a/rc.c
++++ b/rc.c
+@@ -36,6 +36,8 @@ struct rc_search_table {
+ static struct rc_search_table *RC_search_table;
+ static int RC_table_size;
+
++int ssl_known_hosts = 1;
++
+ #define P_INT 0
+ #define P_SHORT 1
+ #define P_CHARINT 2
+@@ -209,6 +211,7 @@ static int OptionEncode = FALSE;
+ #define CMT_SSL_CA_PATH N_("Path to directory for PEM encoded certificates of CAs")
+ #define CMT_SSL_CA_FILE N_("File consisting of PEM encoded certificates of CAs")
+ #define CMT_SSL_CA_DEFAULT N_("Use default locations for PEM encoded certificates of CAs")
++#define CMT_SSL_KNOWN_HOSTS N_("Remember accepted self signed certificates")
+ #endif /* USE_SSL_VERIFY */
+ #define CMT_SSL_FORBID_METHOD N_("List of forbidden SSL methods (2: SSLv2, 3: SSLv3, t: TLSv1.0, 5: TLSv1.1, 6: TLSv1.2, 7: TLSv1.3)")
+ #ifdef SSL_CTX_set_min_proto_version
+@@ -647,6 +650,8 @@ struct param_ptr params7[] = {
+ NULL},
+ {"ssl_ca_default", P_INT, PI_ONOFF, (void *)&ssl_ca_default,
+ CMT_SSL_CA_DEFAULT, NULL},
++ {"ssl_known_hosts", P_INT, PI_ONOFF, (void *)&ssl_known_hosts,
++ CMT_SSL_KNOWN_HOSTS, NULL},
+ #endif /* USE_SSL_VERIFY */
+ {NULL, 0, 0, NULL, NULL, NULL},
+ };
+--- a/rc.h
++++ b/rc.h
+@@ -4,4 +4,6 @@
+ extern void show_params(FILE * fp);
+ extern int str_to_bool(char *value, int old);
+
++extern int ssl_known_hosts;
++
+ #endif /* RC_H */
+--- a/doc-de/README.func
++++ b/doc-de/README.func
+@@ -32,6 +32,7 @@ EXIT Sofort beenden
+ EXTERN Verwende externen Browser zur Anzeige
+ EXTERN_LINK Verwende externen Browser zur Anzeige des Linkziels
+ FRAME Wechsle zwischen Kennung und Umsetzung von HTML-Frames
++GEMINIZE Wechsle zwischen text/gemini-Wiedergabe und -Verarbeitung
+ GOTO Öffne angegebenes Dokument in neuem Puffer
+ GOTO_HOME Zurück zur Startseite (die Variablen HTTP_HOME oder WWW_HOME spezifiziert wurden)
+ GOTO_LINE Gehe zur angebenen Zeile
+--- a/doc/README.func
++++ b/doc/README.func
+@@ -32,6 +32,7 @@ EXIT Quit without confirmation
+ EXTERN Display using an external browser
+ EXTERN_LINK Display target using an external browser
+ FRAME Toggle rendering HTML frames
++GEMINIZE Toggle between text/gemini shown or processed
+ GOTO Open specified document in a new buffer
+ GOTO_HOME Return to the homepage (specified HTTP_HOME or WWW_HOME variable)
+ GOTO_LINE Go to the specified line
+--- a/scripts/w3mhelp.cgi.in
++++ b/scripts/w3mhelp.cgi.in
+@@ -152,8 +152,9 @@ print "<P><A HREF=\"$keymap\">$head</A>\
+ pipeBuf"));
+
+ &show_keymap('Buffer Operations',
+- split(" ", "backBf nextBf prevBf goHome selMn selBuf vwSrc svSrc
+- svBuf editBf editScr reload reshape rdrwSc dispI stopI"));
++ split(" ", "backBf nextBf prevBf goHome selMn selBuf geminize
++ vwSrc svSrc svBuf editBf editScr reload reshape rdrwSc
++ dispI stopI"));
+
+ &show_keymap('Tab Operations',
+ split(" ", "newT closeT nextT prevT tabMn tabR tabL"));