Early gemini support in w3m
I added support from the gemini protocol and gemtext in w3m.
The patch below is a prototype, the bare minimum is implemented:
- all certificates are accepted, tofu is not implemented
- the response and meta from the servers are not handle
- it handles only the 'text/gemini' mime type
- socket and tls errors are not handled, it is assumed it always works
- gemini input is not supported
With this patch, w3m can only load gemtext pages and follow links.
To use this patch, w3m has to be build with the patch applied.
First, save the patch below as gemini.patch, then run:
git clone https://github.com/tats/w3m
cd w3m
git checkout b201f426e4d184113a7b2ed95ae8cb2f1ff68f5a
git apply gemini.patch
# build w3m
sudo apt-get install -y libgc-dev libssl-dev libglib2.0-dev libgdk-pixbuf-2.0-dev libgdk-pixbuf-xlib-2.0-dev gettext
./configure
make
The patch:
From 3ee741499188817824e8abb3b049512389ec84f7 Mon Sep 17 00:00:00 2001
From: Remy Noulin <loader2x@gmail.com>
Date: Sun, 14 Nov 2021 16:21:43 +0200
Subject: [PATCH] prototype support for gemini
The bare minimum is implemented:
- all certificates are accepted, tofu is not implemented
- the response and meta from the servers are not handle
- it handles only the 'text/gemini' mime type
- socket and tls errors are not handled, it is assumed it always works
- gemini input is not supported
With this patch, w3m can only load gemtext pages and follow links.
Bonus/oldconfigure.sh | 1 +
acinclude.m4 | 40 +++--
config.h.dist | 7 +-
config.h.in | 1 +
configure | 18 +++
file.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++----
html.h | 1 +
main.c | 6 +
proto.h | 3 +
url.c | 65 +++++++-
10 files changed, 508 insertions(+), 59 deletions(-)
---
Bonus/oldconfigure.sh | 1 +
acinclude.m4 | 40 ++--
config.h.dist | 7 +-
config.h.in | 1 +
configure | 18 ++
file.c | 425 ++++++++++++++++++++++++++++++++++++++----
html.h | 1 +
main.c | 6 +
proto.h | 3 +
url.c | 67 ++++++-
10 files changed, 509 insertions(+), 60 deletions(-)
diff --git a/Bonus/oldconfigure.sh b/Bonus/oldconfigure.sh
index 541facb..cc30bbe 100755
--- a/Bonus/oldconfigure.sh
+++ b/Bonus/oldconfigure.sh
@@ -116,6 +116,7 @@ opt_enable_set "$use_history" history
opt_enable_set "$use_digest_auth" digest-auth
opt_enable_set "$use_nntp" nntp
opt_enable_set "$use_gopher" gopher
+opt_enable_set "$use_gemini" gemini
if test x"$use_lynx_key" = xy; then
opt_push "--enable-keymap=lynx"
else
diff --git a/acinclude.m4 b/acinclude.m4
index 398eb8c..24dbee8 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -117,7 +117,7 @@ AC_DEFUN([AC_W3M_NNTP],
[enable_nntp="yes"])
test x"$enable_nntp" = xyes && AC_DEFINE(USE_NNTP)
AC_MSG_RESULT($enable_nntp)])
-#
+#
# ----------------------------------------------------------------
# AC_W3M_GOPHER
# ----------------------------------------------------------------
@@ -131,6 +131,18 @@ AC_DEFUN([AC_W3M_GOPHER],
AC_MSG_RESULT($enable_gopher)])
#
# ----------------------------------------------------------------
+# AC_W3M_GEMINI
+# ----------------------------------------------------------------
+AC_DEFUN([AC_W3M_GEMINI],
+[AC_SUBST(USE_GEMINI)
+ AC_MSG_CHECKING(if Gemini is enabled)
+ AC_ARG_ENABLE(gemini,
+ [ --disable-gemini disable Gemini],,
+ [enable_gemini="yes"])
+ test x"$enable_gemini" = xyes && AC_DEFINE(USE_GEMINI)
+ AC_MSG_RESULT($enable_gemini)])
+#
+# ----------------------------------------------------------------
# AC_W3M_M17N
# ----------------------------------------------------------------
# m17n enable?
@@ -210,7 +222,7 @@ else
S*) charset=Shift_JIS;;
J*) charset=ISO-2022-JP;;
U*) charset=UTF-8;;
- esac
+ esac
fi
display_charset=$charset
AC_MSG_CHECKING(which charset is used for display)
@@ -371,7 +383,7 @@ AC_DEFUN([AC_W3M_HELP_CGI],
[enable_help_cgi="yes"])
test x"$enable_help_cgi" = xyes && AC_DEFINE(USE_HELP_CGI)
AC_MSG_RESULT($enable_help_cgi)])
-#
+#
# ----------------------------------------------------------------
# AC_W3M_EXTERNAL_URI_LOADER
# ----------------------------------------------------------------
@@ -383,7 +395,7 @@ AC_DEFUN([AC_W3M_EXTERNAL_URI_LOADER],
[enable_external_uri_loader="yes"])
test x"$enable_external_uri_loader" = xyes && AC_DEFINE(USE_EXTERNAL_URI_LOADER)
AC_MSG_RESULT($enable_external_uri_loader)])
-#
+#
# ----------------------------------------------------------------
# AC_W3M_W3MMAILER
# ----------------------------------------------------------------
@@ -406,7 +418,7 @@ AC_DEFUN([AC_W3M_EXTLIBS],
extlib="not found"
for dir in /lib /usr/lib /usr/local/lib /usr/ucblib /usr/ccslib /usr/ccs/lib /lib64 /usr/lib64
do
- if test -f $dir/lib$lib.a -o -f $dir/lib$lib.so ; then
+ if test -f $dir/lib$lib.a -o -f $dir/lib$lib.so ; then
LIBS="$LIBS -l$lib"
extlib="found at $dir"
break
@@ -587,7 +599,7 @@ AC_DEFUN([AC_W3M_ALARM],
fi])
#
# ----------------------------------------------------------------
-# AC_W3M_CHECK_VER(name, version, major, minor, micro,
+# AC_W3M_CHECK_VER(name, version, major, minor, micro,
# action-if-ok, message-if-badver, action-if-nover)
# ----------------------------------------------------------------
AC_DEFUN([AC_W3M_CHECK_VER],
@@ -643,14 +655,14 @@ AC_DEFUN([AC_W3M_IMAGE],
if test x"$enable_image" = xyes; then
enable_image=x11
case "`uname -s`" in
- Linux|linux|LINUX|FreeBSD|freebsd|FREEBSD)
+ Linux|linux|LINUX|FreeBSD|freebsd|FREEBSD)
if test -c /dev/fb0; then
enable_image=x11,fb
fi;;
CYGWIN*)
enable_image=x11,win;;
esac
- fi
+ fi
save_ifs="$IFS"; IFS=",";
set x $enable_image; shift
IFS="$save_ifs"
@@ -745,14 +757,14 @@ AC_DEFUN([AC_W3M_IMAGE],
if test x"$have_imlib2" = xyes; then
AC_DEFINE(USE_W3MIMG_X11)
IMGOBJS="$IMGOBJS x11/x11_w3mimg.o"
- IMGTARGETS="x11"
+ IMGTARGETS="x11"
AC_DEFINE(USE_IMLIB2)
IMGX11CFLAGS="`${IMLIB2_CONFIG} --cflags`"
IMGX11LDFLAGS="-lX11 `${PKG_CONFIG} --libs imlib2`"
elif test x"$have_gtk2" = xyes; then
AC_DEFINE(USE_W3MIMG_X11)
IMGOBJS="$IMGOBJS x11/x11_w3mimg.o"
- IMGTARGETS="x11"
+ IMGTARGETS="x11"
AC_DEFINE(USE_GDKPIXBUF)
AC_DEFINE(USE_GTK2)
IMGX11CFLAGS="`${PKG_CONFIG} --cflags gdk-pixbuf-2.0 gdk-pixbuf-xlib-2.0`"
@@ -760,18 +772,18 @@ AC_DEFUN([AC_W3M_IMAGE],
elif test x"$have_gdkpixbuf" = xyes; then
AC_DEFINE(USE_W3MIMG_X11)
IMGOBJS="$IMGOBJS x11/x11_w3mimg.o"
- IMGTARGETS="x11"
+ IMGTARGETS="x11"
AC_DEFINE(USE_GDKPIXBUF)
IMGX11CFLAGS="`${GDKPIXBUF_CONFIG} --cflags`"
IMGX11LDFLAGS="`${GDKPIXBUF_CONFIG} --libs` -lgdk_pixbuf_xlib"
elif test x"$have_imlib" = xyes; then
AC_DEFINE(USE_W3MIMG_X11)
IMGOBJS="$IMGOBJS x11/x11_w3mimg.o"
- IMGTARGETS="x11"
+ IMGTARGETS="x11"
AC_DEFINE(USE_IMLIB)
IMGX11CFLAGS="`${IMLIB_CONFIG} --cflags`"
IMGX11LDFLAGS="`${IMLIB_CONFIG} --libs`"
- IMGTARGETS="x11"
+ IMGTARGETS="x11"
else
AC_MSG_WARN([unable to build w3mimgdisplay with X11 support])
fi
@@ -850,7 +862,7 @@ AC_MSG_RESULT($enable_ipv6)
if test x"$enable_ipv6" = xyes; then
AC_MSG_CHECKING(if IPv6 API available)
AC_SUBST(INET6)
- AC_CHECK_FUNC(getaddrinfo,
+ AC_CHECK_FUNC(getaddrinfo,
[enable_ipv6="yes"],
[enable_ipv6="no"])
if test x"$enable_ipv6" = xno; then
diff --git a/config.h.dist b/config.h.dist
index fdb591a..876c6fc 100644
--- a/config.h.dist
+++ b/config.h.dist
@@ -8,7 +8,7 @@
/* User Configuration */
-/*
+/*
If you define USE_DICT, you can use dictionary look-up function
in w3m. See README.dict for detail.
*/
@@ -80,8 +80,8 @@ ETC_DIR = /usr/local/etc/w3m
RC_DIR = ~/.w3m
HELP_FILE = w3mhelp-w3m_ja.html
RC_DIR = ~/.w3m/
-SYS_LIBRARIES = -lgpm -lbsd -lnsl -lncurses -L/usr/lib -L/usr/lib -L/usr/local/ssl/lib -L/usr/local/ssl/lib -lssl -lcrypto
-LOCAL_LIBRARIES =
+SYS_LIBRARIES = -lgpm -lbsd -lnsl -lncurses -L/usr/lib -L/usr/lib -L/usr/local/ssl/lib -L/usr/local/ssl/lib -lssl -lcrypto
+LOCAL_LIBRARIES =
CC = gcc
MYCFLAGS = -O -I./gc/include -I/usr/local/ssl/include/openssl -I/usr/local/ssl/include
GCCFLAGS = -O -I./gc/include -I./$(srcdir)/include -DATOMIC_UNCOLLECTABLE -DNO_SIGNALS -DNO_EXECUTE_PERMISSION -DSILENT -DALL_INTERIOR_POINTERS
@@ -132,6 +132,7 @@ INSTALL_W3MIMGDISPLAY=$(INSTALL_PROGRAM)
#define DEF_CAFILE ""
#undef USE_NNTP
#undef USE_GOPHER
+#undef USE_GEMINI
#define USE_EXTERNAL_URI_LOADER
#undef USE_ALARM
#undef USE_IMAGE
diff --git a/config.h.in b/config.h.in
index 8a3829d..237aad4 100644
--- a/config.h.in
+++ b/config.h.in
@@ -67,6 +67,7 @@
#undef USE_W3MMAILER
#undef USE_NNTP
#undef USE_GOPHER
+#undef USE_GEMINI
#undef USE_ALARM
#undef USE_IMAGE
#undef USE_W3MIMG_X11
diff --git a/configure b/configure
index 5126dec..be6ea45 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,7 @@ USE_EXTERNAL_URI_LOADER
USE_HELP_CGI
USE_DICT
USE_GOPHER
+USE_GEMINI
USE_NNTP
USE_COOKIE
USE_ALARM
@@ -826,6 +827,7 @@ enable_alarm
enable_cookie
enable_nntp
enable_gopher
+enable_gemini
enable_dict
enable_help_cgi
enable_external_uri_loader
@@ -1508,6 +1510,7 @@ Optional Features:
--disable-cookie disable cookie
--disable-nntp disable NNTP
--disable-gopher disable Gopher
+ --disable-gemini disable Gemini
--disable-dict disable dictionary lookup
--disable-help-cgi disable help cgi
--disable-external-uri-loader disable external URI loader
@@ -7966,6 +7969,21 @@ fi
$as_echo "$enable_gopher" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if Gemini is enabled" >&5
+$as_echo_n "checking if Gemini is enabled... " >&6; }
+ # Check whether --enable-gemini was given.
+if test "${enable_gemini+set}" = set; then :
+ enableval=$enable_gemini;
+else
+ enable_gemini="yes"
+fi
+
+ test x"$enable_gemini" = xyes && $as_echo "#define USE_GEMINI 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_gemini" >&5
+$as_echo "$enable_gemini" >&6; }
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if dictionary lookup is enabled" >&5
$as_echo_n "checking if dictionary lookup is enabled... " >&6; }
# Check whether --enable-dict was given.
diff --git a/file.c b/file.c
index f2e0563..4138ecd 100644
--- a/file.c
+++ b/file.c
@@ -13,6 +13,13 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
+#include <ctype.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <netdb.h>
+#include <openssl/x509.h>
/* foo */
#include "html.h"
@@ -164,20 +171,20 @@ static struct compression_decoder {
int use_d_arg;
} compression_decoders[] = {
{ CMP_COMPRESS, ".gz", "application/x-gzip",
- 0, GUNZIP_CMDNAME, GUNZIP_NAME, "gzip",
- {"gzip", "x-gzip", NULL}, 0 },
+ 0, GUNZIP_CMDNAME, GUNZIP_NAME, "gzip",
+ {"gzip", "x-gzip", NULL}, 0 },
{ CMP_COMPRESS, ".Z", "application/x-compress",
0, GUNZIP_CMDNAME, GUNZIP_NAME, "compress",
- {"compress", "x-compress", NULL}, 0 },
+ {"compress", "x-compress", NULL}, 0 },
{ CMP_BZIP2, ".bz2", "application/x-bzip",
0, BUNZIP2_CMDNAME, BUNZIP2_NAME, "bzip, bzip2",
- {"x-bzip", "bzip", "bzip2", NULL}, 0 },
+ {"x-bzip", "bzip", "bzip2", NULL}, 0 },
{ CMP_DEFLATE, ".deflate", "application/x-deflate",
1, INFLATE_CMDNAME, INFLATE_NAME, "deflate",
- {"deflate", "x-deflate", NULL}, 0 },
+ {"deflate", "x-deflate", NULL}, 0 },
{ CMP_BROTLI, ".br", "application/x-br",
0, BROTLI_CMDNAME, BROTLI_NAME, "br",
- {"br", "x-br", NULL}, 1 },
+ {"br", "x-br", NULL}, 1 },
{ CMP_NOCOMPRESS, NULL, NULL, 0, NULL, NULL, NULL, {NULL}, 0},
};
/* *INDENT-ON* */
@@ -472,7 +479,7 @@ acceptableEncoding()
return encodings->ptr;
}
-/*
+/*
* convert line
*/
#ifdef USE_M17N
@@ -1186,7 +1193,7 @@ AuthBasicCred(struct http_auth *ha, Str uname, Str pw, ParsedURL *pu,
#include <openssl/md5.h>
/* RFC2617: 3.2.2 The Authorization Request Header
- *
+ *
* credentials = "Digest" digest-response
* digest-response = 1#( username | realm | nonce | digest-uri
* | response | [ algorithm ] | [cnonce] |
@@ -1425,7 +1432,7 @@ struct auth_param basic_auth_param[] = {
#ifdef USE_DIGEST_AUTH
/* RFC2617: 3.2.1 The WWW-Authenticate Response Header
* challenge = "Digest" digest-challenge
- *
+ *
* digest-challenge = 1#( realm | [ domain ] | nonce |
* [ opaque ] |[ stale ] | [ algorithm ] |
* [ qop-options ] | [auth-param] )
@@ -1539,7 +1546,7 @@ getAuthCookie(struct http_auth *hauth, char *auth_header,
auth_header_len);
if (a_found) {
/* This means that *-Authenticate: header is received after
- * Authorization: header is sent to the server.
+ * Authorization: header is sent to the server.
*/
if (fmInitialized) {
message("Wrong username or password", 0, 0);
@@ -1555,7 +1562,7 @@ getAuthCookie(struct http_auth *hauth, char *auth_header,
*uname = NULL;
*pwd = NULL;
- if (!a_found && find_auth_user_passwd(pu, realm, (Str*)uname, (Str*)pwd,
+ if (!a_found && find_auth_user_passwd(pu, realm, (Str*)uname, (Str*)pwd,
proxy)) {
/* found username & password in passwd file */ ;
}
@@ -1594,7 +1601,7 @@ getAuthCookie(struct http_auth *hauth, char *auth_header,
realm);
exit(1);
}
-
+
/* FIXME: gettextize? */
printf(proxy ? "Proxy Username for %s: " : "Username for %s: ",
realm);
@@ -1689,7 +1696,69 @@ getLinkNumberStr(int correction)
return Sprintf("[%d]", cur_hseq + correction);
}
-/*
+
+static int
+verify_callback(X509_STORE_CTX *ctx, void *data)
+{
+ // accept all certificates
+ (void)data;
+ X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+
+ int rc;
+ int day, sec;
+ const ASN1_TIME *notBefore = X509_get0_notBefore(cert);
+ const ASN1_TIME *notAfter = X509_get0_notAfter(cert);
+ if (!ASN1_TIME_diff(&day, &sec, NULL, notBefore)) {
+ rc = X509_V_ERR_UNSPECIFIED;
+ goto invalid_cert;
+ }
+ if (day > 0 || sec > 0) {
+ rc = X509_V_ERR_CERT_NOT_YET_VALID;
+ goto invalid_cert;
+ }
+ if (!ASN1_TIME_diff(&day, &sec, NULL, notAfter)) {
+ rc = X509_V_ERR_UNSPECIFIED;
+ goto invalid_cert;
+ }
+ if (day < 0 || sec < 0) {
+ rc = X509_V_ERR_CERT_HAS_EXPIRED;
+ goto invalid_cert;
+ }
+
+ unsigned char md[512 / 8];
+ const EVP_MD *sha512 = EVP_sha512();
+ unsigned int len = sizeof(md);
+ rc = X509_digest(cert, sha512, md, &len);
+ assert(rc == 1);
+
+ char fingerprint[512 / 8 * 3];
+ for (size_t i = 0; i < sizeof(md); ++i) {
+ snprintf(&fingerprint[i * 3], 4, "%02X%s",
+ md[i], i + 1 == sizeof(md) ? "" : ":");
+ }
+
+ SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (!servername) {
+ rc = X509_V_ERR_HOSTNAME_MISMATCH;
+ goto invalid_cert;
+ }
+
+ rc = X509_check_host(cert, servername, strlen(servername), 0, NULL);
+ if (rc != 1) {
+ rc = X509_V_ERR_HOSTNAME_MISMATCH;
+ goto invalid_cert;
+ }
+
+ return 0;
+
+invalid_cert:
+ return 0;
+}
+
+
+/*
* loadGeneralFile: load file to buffer
*/
#define DO_EXTERNAL ((Buffer *(*)(URLFile *, Buffer *))doExternal)
@@ -1747,6 +1816,145 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
}
}
TRAP_OFF;
+ if (pu.scheme == SCM_GEMINI) {
+ // get uri
+ // request URL is tpath
+ // open and close connection here
+ // assume text/gemini
+
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ int sock = -1;
+ SSL_load_error_strings();
+ ERR_load_crypto_strings();
+ ssl_ctx = SSL_CTX_new(TLS_method());
+ SSL_CTX_set_cert_verify_callback(ssl_ctx, verify_callback, NULL);
+ BIO *sbio = BIO_new(BIO_f_ssl());
+ // open url
+ struct addrinfo hints = {0};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ char pbuf[7];
+ snprintf(pbuf, sizeof(pbuf), "%d", pu.port);
+ struct addrinfo *addr;
+ int r = getaddrinfo(pu.host, pbuf, &hints, &addr);
+ if (r != 0) {
+ // error resolve
+ puts("gemini error resolve");
+ }
+ for (struct addrinfo *rp = addr; rp != NULL; rp = rp->ai_next) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sock == -1) continue;
+ if (connect(sock, rp->ai_addr, rp->ai_addrlen) < 0) {
+ close(sock);
+ sock = -1;
+ continue;
+ }
+ break;
+ }
+ freeaddrinfo(addr);
+ if (sock == -1) {
+ // Error
+ puts("gemini open socket");
+ }
+ // set sock
+ ssl = SSL_new(ssl_ctx);
+ SSL_set_connect_state(ssl);
+ r = SSL_set1_host(ssl, pu.host);
+ if (r != 1) {
+ // Error
+ puts("gemini error");
+ }
+ r = SSL_set_tlsext_host_name(ssl, pu.host);
+ if (r != 1) {
+ // Error
+ puts("gemini error");
+ }
+ r = SSL_set_fd(ssl, sock);
+ if (r != 1) {
+ // Error
+ }
+ r = SSL_connect(ssl);
+ if (r != 1) {
+ // Error
+ }
+ X509 *cert = SSL_get_peer_certificate(ssl);
+ if (!cert) {
+ // Error
+ puts("gemini cert error");
+ }
+ X509_free(cert);
+ long vr = SSL_get_verify_result(ssl);
+ if (vr != X509_V_OK) {
+ // Error
+ puts("gemini cert not ok");
+ }
+ BIO_set_ssl(sbio, ssl, 0);
+ BIO *bio;
+ bio = BIO_new(BIO_f_buffer());
+ BIO_push(bio, sbio);
+ char req[1024 + 3];
+ if (!strncmp(tpath, "gemini://", strlen("gemini://"))) {
+ r = snprintf(req, sizeof(req), "%s\r\n", tpath);
+ }
+ else {
+ // relative path
+ // remove filename in referer
+ char *q = referer + strlen(referer);
+ while(q > referer) {
+ q--;
+ if (*q == '/') break;
+ }
+ memcpy(req, referer, q - referer +1);
+ req[q-referer+1] = 0;
+ strncat(req, tpath, sizeof(req)-1);
+ strncat(req, "\r\n", sizeof(req)-1);
+ }
+ r = BIO_puts(sbio, req);
+ if (r == -1) {
+ // Error
+ puts(ERR_error_string(SSL_get_error(ssl, -1),NULL));
+ }
+ /* assert(r == (int)strlen(req)); */
+ #define GEMINI_META_MAXLEN 1024
+ #define GEMINI_STATUS_MAXLEN 2
+ char buf[GEMINI_META_MAXLEN
+ + GEMINI_STATUS_MAXLEN
+ + 2 /* CRLF */ + 1 /* NUL */] = {0};
+ r = BIO_gets(bio, buf, sizeof(buf));
+ if (r == -1) {
+ // Error
+ puts("BIO_gets error");
+ }
+ // check status, meta, \r\n
+ Str gemtext = Strnew();
+ char buf2[BUFSIZ];
+ int n = 1;
+ while (n > 0) {
+ n = BIO_read(bio, buf2, sizeof(buf2));
+ if (n == -1) {
+ puts("gemini error read");
+ }
+ else if (n > 0) {
+ // save string in buf2
+ Strcat_charp_n(gemtext, buf2, n);
+ }
+ }
+ init_stream(&f, SCM_MISSING, NULL);
+ f.scheme = pu.scheme;
+ f.url = parsedURL2Str(&pu)->ptr;
+ f.ext = filename_extension(pu.file, 1);
+ f.stream = newStrStream(gemtext);
+ page = loadGemini(&f, &pu, &charset);
+ t = "text/gemini";
+ close(sock);
+ BIO_free(BIO_pop(bio)); // ssl bio
+ BIO_free(bio); // buffered bio
+ SSL_free(ssl);
+ SSL_CTX_free(ssl_ctx);
+ TRAP_OFF;
+ goto page_loaded;
+ }
url_option.referer = referer;
url_option.flag = flag;
f = openURL(tpath, &pu, current, &url_option, request, extra_header, of,
@@ -1847,6 +2055,9 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
#ifdef USE_GOPHER
(pu.scheme == SCM_GOPHER && non_null(GOPHER_proxy)) ||
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+ pu.scheme == SCM_GEMINI ||
+#endif /* USE_GEMINI */
(pu.scheme == SCM_FTP && non_null(FTP_proxy))
) && !Do_not_use_proxy && !check_no_proxy(pu.host))) {
@@ -1898,7 +2109,7 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
t = "text/plain";
if (add_auth_cookie_flag && realm && uname && pwd) {
/* If authorization is required and passed */
- add_auth_user_passwd(&pu, qstr_unquote(realm)->ptr, uname, pwd,
+ add_auth_user_passwd(&pu, qstr_unquote(realm)->ptr, uname, pwd,
0);
add_auth_cookie_flag = 0;
}
@@ -1931,7 +2142,7 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
&& (realm = get_auth_param(hauth.param, "realm")) != NULL) {
auth_pu = schemeToProxy(pu.scheme);
getAuthCookie(&hauth, "Proxy-Authorization:",
- extra_header, auth_pu, &hr, request,
+ extra_header, auth_pu, &hr, request,
&uname, &pwd);
if (uname == NULL) {
/* abort */
@@ -2011,6 +2222,18 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
}
}
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+ else if (pu.scheme == SCM_GEMINI) {
+ // TODO remove this? copied before f = openURL
+ // parse the URL only to get the scheme
+ // open and close connection here
+ // assume text/gemini
+ page = loadGemini(&f, &pu, &charset);
+ t = "text/gemini";
+ TRAP_OFF;
+ goto page_loaded;
+ }
+#endif /* USE_GEMINI */
else if (pu.scheme == SCM_FTP) {
check_compression(path, &f);
if (f.compression != CMP_NOCOMPRESS) {
@@ -2112,7 +2335,7 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
/* XXX: can we use guess_type to give the type to loadHTMLstream
* to support default utf8 encoding for XHTML here? */
f.guess_type = t;
-
+
page_loaded:
if (page) {
FILE *src;
@@ -2239,7 +2462,7 @@ loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
#endif
else if (w3m_backend) ;
else if (!(w3m_dump & ~DUMP_FRAME) || is_dump_text_type(t)) {
- if (!do_download &&
+ if (!do_download &&
#ifdef USE_GOPHER
!gopher_download &&
#endif
@@ -3657,10 +3880,10 @@ process_input(struct parsed_tag *tag)
case FORM_INPUT_RESET:
q = "RESET";
break;
- /* if no VALUE attribute is specified in
- * <INPUT TYPE=CHECKBOX> tag, then the value "on" is used
- * as a default value. It is not a part of HTML4.0
- * specification, but an imitation of Netscape behaviour.
+ /* if no VALUE attribute is specified in
+ * <INPUT TYPE=CHECKBOX> tag, then the value "on" is used
+ * as a default value. It is not a part of HTML4.0
+ * specification, but an imitation of Netscape behaviour.
*/
case FORM_INPUT_CHECKBOX:
q = "on";
@@ -4344,7 +4567,7 @@ process_idattr(struct readbuffer *obuf, int cmd, struct parsed_tag *tag)
char *id = NULL, *framename = NULL;
Str idtag = NULL;
- /*
+ /*
* HTML_TABLE is handled by the other process.
*/
if (cmd == HTML_TABLE)
@@ -5725,7 +5948,7 @@ HTMLlineproc2body(Buffer *buf, Str (*feed) (), int llimit)
#endif
}
else if (*str == '&') {
- /*
+ /*
* & escape processing
*/
p = getescapecmd(&str);
@@ -6474,7 +6697,7 @@ HTMLlineproc0(char *line, struct html_feed_environ *h_env, int internal)
tbl_mode->end_tag : obuf->end_tag;
if (*line == '<' || obuf->status != R_ST_NORMAL) {
- /*
+ /*
* Tag processing
*/
if (obuf->status == R_ST_EOL)
@@ -6554,7 +6777,7 @@ HTMLlineproc0(char *line, struct html_feed_environ *h_env, int internal)
proc_normal:
if (obuf->table_level >= 0 && tbl && tbl_mode) {
- /*
+ /*
* within table: in <table>..</table>, all input tokens
* are fed to the table renderer, and then the renderer
* makes HTML output.
@@ -6900,7 +7123,7 @@ addnewline(Buffer *buf, char *line, Lineprop *prop, Linecolor *color, int pos,
}
}
-/*
+/*
* loadHTMLBuffer: read file and make new buffer
*/
Buffer *
@@ -7431,7 +7654,7 @@ loadHTMLstream(URLFile *f, Buffer *newBuf, FILE * src, int internal)
HTMLlineproc2(newBuf, htmlenv1.buf);
}
-/*
+/*
* loadHTMLString: read string and make new buffer
*/
Buffer *
@@ -7474,7 +7697,7 @@ loadHTMLString(Str page)
#ifdef USE_GOPHER
-/*
+/*
* loadGopherDir: get gopher directory
*/
#ifdef USE_M17N
@@ -7634,7 +7857,141 @@ loadGopherSearch0(URLFile *uf, ParsedURL *pu)
}
#endif /* USE_GOPHER */
-/*
+#ifdef USE_GEMINI
+Str
+loadGemini(URLFile *uf, ParsedURL *pu, wc_ces * charset)
+{
+ Str volatile tmp;
+ Str lbuf, url, name;
+ char *volatile p, *volatile q;
+ int pre, quote, haslinkname;
+ MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
+#ifdef USE_M17N
+ wc_ces doc_charset = DocumentCharset;
+#endif
+
+ tmp = parsedURL2Str(pu);
+ p = html_quote(tmp->ptr);
+ tmp =
+ convertLine(NULL, Strnew_charp(file_unquote(tmp->ptr)), RAW_MODE,
+ charset, doc_charset);
+ q = html_quote(tmp->ptr);
+ // p is url
+ // q is title
+ tmp = Strnew_m_charp("<html>\n<head>\n<base href=\"", p, "\">\n<title>", q,
+ "</title>\n</head>\n<body>\n", NULL);
+
+ if (SETJMP(AbortLoading) != 0)
+ goto gemini_end;
+ TRAP_ON;
+ pre = quote = 0;
+ // gemini://gemini.circumlunar.space/docs/specification.gmi
+ // TODO escape html like escape-goat https://github.com/RangerMauve/gemini-to-html
+ while (1) {
+ if (lbuf = StrUFgets(uf), lbuf->length == 0)
+ break;
+ lbuf = convertLine(uf, lbuf, HTML_MODE, charset, doc_charset);
+ p = lbuf->ptr;
+ if (quote && p[0] != '>') {
+ quote = 0;
+ Strcat_m_charp(tmp, "</blockquote>\n", NULL);
+ }
+ if (pre) {
+ if (!strcmp(p, "```\n")) {
+ Strcat_m_charp(tmp, "</pre>\n", NULL);
+ pre = 0;
+ }
+ else
+ Strcat_m_charp(tmp, p, "\n", NULL);
+ }
+ else if (strncmp(p, "=>", 2) == 0) {
+ // link
+ for (q = p+2; *q && isspace(*q); q++);
+ // url
+ p = q;
+ haslinkname = 0;
+ while(*q) {
+ if (isspace(*q)) {
+ haslinkname = 1;
+ break;
+ }
+ q++;
+ }
+ url = Strnew_charp_n(p, q - p);
+ // optional link name
+ if (haslinkname) {
+ for (; *q && isspace(*q); q++);
+ // link name
+ p = q;
+ //for (; *q && !isspace(*q); q++);
+ //name = Strnew_charp_n(p, q - p);
+ // TODO handle links without a protocol > add gemini://
+ Strcat_m_charp(tmp, "<a href=\"",
+ html_quote(url_encode(url->ptr, NULL, *charset)),
+ "\">", html_quote(p/*name->ptr*/), "</a><br>\n", NULL);
+ }
+ else {
+ Strcat_m_charp(tmp, "<a href=\"",
+ html_quote(url_encode(url->ptr, NULL, *charset)),
+ "\">", html_quote(url->ptr), "</a><br>\n", NULL);
+ }
+ }
+ else if (!strcmp(p, "```\n")) {
+ // preformated
+ pre = 1;
+ Strcat_m_charp(tmp, "<pre>\n", NULL);
+ }
+ else if (p[0] == '#') {
+ // header line
+ if (p[1] == '#') {
+ if (p[2] == '#') {
+ Strcat_m_charp(tmp, "<h3>", p, "</h3>\n", NULL);
+ }
+ else {
+ Strcat_m_charp(tmp, "<h2>", p, "</h2>\n", NULL);
+ }
+ }
+ else {
+ Strcat_m_charp(tmp, "<h1>", p, "</h1>\n", NULL);
+ }
+ }
+ else if (p[0] == '*') {
+ // list line
+ // TODO ul and li
+ Strcat_m_charp(tmp, p, "<br/>\n", NULL);
+ }
+ else if (p[0] == '>') {
+ // quote line
+ if (!quote) {
+ Strcat_m_charp(tmp, "<blockquote>", p, "\n", NULL);
+ quote = 1;
+ }
+ else
+ Strcat_m_charp(tmp, p, "\n", NULL);
+ }
+ else {
+ // text line inside <p></p>
+ // blank line = <br\>
+ if (non_null(p)) {
+ Strcat_m_charp(tmp, "<p>", p, "</p>\n", NULL);
+ }
+ else {
+ Strcat_m_charp(tmp, "<br/>\n", NULL);
+ }
+ }
+ }
+ gemini_end:
+ TRAP_OFF;
+ if (pre)
+ Strcat_m_charp(tmp, "</pre>\n", NULL);
+ if (quote)
+ Strcat_m_charp(tmp, "</blockquote>\n", NULL);
+ Strcat_charp(tmp, "</body>\n</html>\n");
+ return tmp;
+}
+#endif /* USE_GEMINI */
+
+/*
* loadBuffer: read file and make new buffer
*/
Buffer *
@@ -7842,7 +8199,7 @@ conv_symbol(Line *l)
return Strnew_charp_n(l->lineBuf, l->len);
}
-/*
+/*
* saveBuffer: write buffer to file
*/
static void
@@ -7913,7 +8270,7 @@ loadcmdout(char *cmd,
return buf;
}
-/*
+/*
* getshell: execute shell command and get the result into a buffer
*/
Buffer *
@@ -7930,7 +8287,7 @@ getshell(char *cmd)
return buf;
}
-/*
+/*
* getpipe: execute shell command and connect pipe to the buffer
*/
Buffer *
@@ -7956,7 +8313,7 @@ getpipe(char *cmd)
return buf;
}
-/*
+/*
* Open pager buffer
*/
Buffer *
@@ -8514,7 +8871,7 @@ doFileSave(URLFile uf, char *defstr)
char *p, *q;
pid_t pid;
char *lock;
- char *tmpf = NULL;
+ char *tmpf = NULL;
#if !(defined(HAVE_SYMLINK) && defined(HAVE_LSTAT))
FILE *f;
#endif
diff --git a/html.h b/html.h
index 749e7ef..d101d87 100644
--- a/html.h
+++ b/html.h
@@ -417,5 +417,6 @@ struct environment {
#ifdef USE_SSL
#define SCM_HTTPS 13
#endif /* USE_SSL */
+#define SCM_GEMINI 14
#endif /* _HTML_H */
diff --git a/main.c b/main.c
index ced6f40..267bd6f 100644
--- a/main.c
+++ b/main.c
@@ -179,6 +179,9 @@ fversion(FILE * f)
#ifdef USE_GOPHER
",gopher"
#endif
+#ifdef USE_GEMINI
+ ",gemini"
+#endif
#ifdef INET6
",ipv6"
#endif
@@ -5067,6 +5070,9 @@ chkURLBuffer(Buffer *buf)
#ifdef USE_GOPHER
"gopher://[a-zA-Z0-9][a-zA-Z0-9:%\\-\\./_]*",
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+ "gemini://[a-zA-Z0-9][a-zA-Z0-9:%\\-\\./?=~_\\&+@#,\\$;]*[a-zA-Z0-9_/=\\-]",
+#endif /* USE_GEMINI */
"ftp://[a-zA-Z0-9][a-zA-Z0-9:%\\-\\./=_+@#,\\$]*[a-zA-Z0-9_/]",
#ifdef USE_NNTP
"news:[^<> ][^<> ]*",
diff --git a/proto.h b/proto.h
index 66d497e..2c4fdfa 100644
--- a/proto.h
+++ b/proto.h
@@ -271,6 +271,9 @@ extern Str loadGopherSearch0(URLFile *uf, ParsedURL *pu);
#define loadGopherSearch(uf,pu,charset) loadGopherSearch0(uf,pu)
#endif
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+extern Str loadGemini(URLFile *uf, ParsedURL *pu, wc_ces * charset);
+#endif /* USE_GEMINI */
extern Buffer *loadBuffer(URLFile *uf, Buffer *newBuf);
#ifdef USE_IMAGE
extern Buffer *loadImageBuffer(URLFile *uf, Buffer *newBuf);
diff --git a/url.c b/url.c
index 1fbda17..a356749 100644
--- 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},
};
@@ -130,6 +132,8 @@ static char * schemeNumToName(int scheme);
#define HTTP_DEFAULT_FILE "/"
#endif /* not HTTP_DEFAULT_FILE */
+#define GEMINI_DEFAULT_FILE "/index.gmi"
+
#ifdef SOCK_DEBUG
#include <stdarg.h>
@@ -231,6 +235,10 @@ DefaultFile(int scheme)
case SCM_GOPHER:
return allocStr("1", -1);
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+ case SCM_GEMINI:
+ return allocStr(GEMINI_DEFAULT_FILE, -1);
+#endif /* USE_GEMINI */
case SCM_LOCAL:
case SCM_LOCAL_CGI:
case SCM_FTP:
@@ -928,7 +936,7 @@ parseURL(char *url, ParsedURL *p_url, ParsedURL *current)
p_url->host != NULL && *p_url->host != '\0' &&
!is_localhost(p_url->host)) {
/*
- * In the environments other than CYGWIN, a URL like
+ * In the environments other than CYGWIN, a URL like
* file://host/file is regarded as ftp://host/file.
* On the other hand, file://host/file on CYGWIN is
* regarded as local access to the file //host/file.
@@ -996,14 +1004,14 @@ parseURL(char *url, ParsedURL *p_url, ParsedURL *current)
while (*p && *p != '#' && p != cgi)
p++;
if (*p == '#' && p_url->scheme == SCM_LOCAL) {
- /*
+ /*
* According to RFC2396, # means the beginning of
* URI-reference, and # should be escaped. But,
* if the scheme is SCM_LOCAL, the special
* treatment will apply to # for convinience.
*/
if (p > q && *(p - 1) == '/' && (cgi == NULL || p < cgi)) {
- /*
+ /*
* # comes as the first character of the file name
* that means, # is not a label but a part of the file
* name.
@@ -1012,7 +1020,7 @@ parseURL(char *url, ParsedURL *p_url, ParsedURL *current)
goto again;
}
else if (*(p + 1) == '\0') {
- /*
+ /*
* # comes as the last character of the file name that
* means, # is not a label but a part of the file
* name.
@@ -1136,7 +1144,7 @@ parseURL2(char *url, ParsedURL *pu, ParsedURL *current)
#ifdef USE_EXTERNAL_URI_LOADER
if (pu->scheme == SCM_UNKNOWN
&& strchr(pu->file, ':') == NULL
- && current && (p = strchr(current->file, ':')) != NULL) {
+ && current && current->file && (p = strchr(current->file, ':')) != NULL) {
pu->file = Sprintf("%s:%s",
allocStr(current->file,
p - current->file), pu->file)->ptr;
@@ -1213,10 +1221,10 @@ parseURL2(char *url, ParsedURL *pu, ParsedURL *current)
) {
if (relative_uri) {
/* In this case, pu->file is created by [process 1] above.
- * pu->file may contain relative path (for example,
+ * pu->file may contain relative path (for example,
* "/foo/../bar/./baz.html"), cleanupName() must be applied.
* When the entire abs_path is given, it still may contain
- * elements like `//', `..' or `.' in the pu->file. It is
+ * elements like `//', `..' or `.' in the pu->file. It is
* server's responsibility to canonicalize such path.
*/
pu->file = cleanupName(pu->file);
@@ -1259,6 +1267,7 @@ _parsedURL2Str(ParsedURL *pu, int pass, int user, int label)
"news", "news", "data", "mailto",
#ifdef USE_SSL
"https",
+ "gemini",
#endif /* USE_SSL */
};
@@ -1448,7 +1457,7 @@ otherinfo(ParsedURL *target, ParsedURL *current, char *referer)
int cross_origin = FALSE;
if (CrossOriginReferer && current && current->host &&
(!target || !target->host ||
- strcasecmp(current->host, target->host) != 0 ||
+ strcasecmp(current->host, target->host) != 0 ||
current->port != target->port ||
current->scheme != target->scheme))
cross_origin = TRUE;
@@ -1601,6 +1610,19 @@ HTTPrequest(ParsedURL *pu, ParsedURL *current, HRequest *hr, TextList *extra)
return tmp;
}
+#ifdef USE_GEMINI
+static Str
+GEMINIrequest(ParsedURL *pu, ParsedURL *current, HRequest *hr, TextList *extra)
+{
+ Str tmp;
+ // TODO remove not needed, because with gemini we just write the url in the request, pu is parsed URL - tmp = Strnew_m_charp(pu->ptr,"\r\n", NULL);
+#ifdef DEBUG
+ fprintf(stderr, "GEMINIrequest: [ %s ]\n\n", tmp->ptr);
+#endif /* DEBUG */
+ return tmp;
+}
+#endif /* USE_GEMINI */
+
void
init_stream(URLFile *uf, int scheme, InputStream stream)
{
@@ -1959,6 +1981,33 @@ openURL(char *url, ParsedURL *pu, ParsedURL *current,
}
break;
#endif /* USE_GOPHER */
+#ifdef USE_GEMINI
+ 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 = GEMINIrequest(pu, current, hr, extra_header);
+ *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;
+#endif /* USE_GEMINI */
#ifdef USE_NNTP
case SCM_NNTP:
case SCM_NNTP_GROUP:
@@ -2132,7 +2181,7 @@ check_no_proxy(char *domain)
if (!NOproxy_netaddr) {
return 0;
}
- /*
+ /*
* to check noproxy by network addr
*/
if (SETJMP(AbortLoading) != 0) {
--
2.30.2
hashtags: #gemini