diff --git a/LYMessages_en.h b/LYMessages_en.h
index 7014be68..5563a971 100644
--- a/LYMessages_en.h
+++ b/LYMessages_en.h
@@ -809,6 +809,10 @@
 #define SEGMENT_KEYWORDS_WILL   gettext("\nThe keywords that you enter will allow you to search on a")
 #define SEGMENT_PERSONS_DB_NAME gettext(" person's name in the database.\n")
 
+/* HTGemini.c */
+#define GEMINI_SENDING_REQUEST  gettext("Sending Gemini request.")
+#define GEMINI_SENT_REQUEST     gettext("Gemini request sent; waiting for response.")
+
 /* HTNews.c */
 #define FAILED_CONNECTION_CLOSED gettext("Connection closed ???")
 #define FAILED_CANNOT_OPEN_POST gettext("Cannot open temporary file for news POST.")
diff --git a/WWW/Library/Implementation/HTAccess.c b/WWW/Library/Implementation/HTAccess.c
index ba81b839..10f4705c 100644
--- a/WWW/Library/Implementation/HTAccess.c
+++ b/WWW/Library/Implementation/HTAccess.c
@@ -153,6 +153,9 @@ extern GLOBALREF (HTProtocol, HTSNewsReply);
 extern GLOBALREF (HTProtocol, HTGopher);
 extern GLOBALREF (HTProtocol, HTCSO);
 #endif /* not DISABLE_GOPHER */
+#ifndef DISABLE_GEMINI
+extern GLOBALREF (HTProtocol, HTGemini);
+#endif // DISABLE_GEMINI
 #ifndef DISABLE_FINGER
 extern GLOBALREF (HTProtocol, HTFinger);
 #endif /* not DISABLE_FINGER */
@@ -174,6 +177,9 @@ GLOBALREF HTProtocol HTSNews, HTSNewsPost, HTSNewsReply;
 #ifndef DISABLE_GOPHER
 GLOBALREF HTProtocol HTGopher, HTCSO;
 #endif /* not DISABLE_GOPHER */
+#ifndef DISABLE_GEMINI
+GLOBALREF HTProtocol HTGemini;
+#endif // DISABLE_GEMINI
 #ifndef DISABLE_FINGER
 GLOBALREF HTProtocol HTFinger;
 #endif /* not DISABLE_FINGER */
@@ -208,6 +214,9 @@ static void HTAccessInit(void)	/* Call me once */
     HTRegisterProtocol(&HTGopher);
     HTRegisterProtocol(&HTCSO);
 #endif /* not DISABLE_GOPHER */
+#ifndef DISABLE_GEMINI
+    HTRegisterProtocol(&HTGemini);
+#endif // DISABLE_GEMINI
 #ifndef DISABLE_FINGER
     HTRegisterProtocol(&HTFinger);
 #endif /* not DISABLE_FINGER */
diff --git a/WWW/Library/Implementation/HTGemini.c b/WWW/Library/Implementation/HTGemini.c
new file mode 100644
index 00000000..fc122368
--- /dev/null
+++ b/WWW/Library/Implementation/HTGemini.c
@@ -0,0 +1,744 @@
+/*
+ * $LynxId: HTGemini.c,v 1.77 2022/04/01 00:18:09 tom Exp $
+ *
+ *			GEMINI ACCESS				HTGemini.c
+ *			=============
+ *
+ *  History:
+ *  04 Apr 24 Adapted from HTGemini.c
+ *
+ */
+
+#define HTSTREAM_INTERNAL 1
+
+#include <HTUtils.h>		/* Coding convention macros */
+#include <HTFile.h>		/* For HTFileFormat() */
+
+#ifndef DISABLE_GOPHER
+#include <HTAlert.h>
+#include <HTParse.h>
+#include <HTTCP.h>
+#include <HTFinger.h>
+#include <LYGlobalDefs.h>
+
+/*
+ *  Implements.
+ */
+#include <HTGemini.h>
+
+#define GEMINI_PORT 1965 /* See protocol spec */
+#define BIG 1024		/* Bug */
+#define LINE_LENGTH 256		/* Bug */
+
+/*
+ *  Gopher entity types.
+ */
+#define GOPHER_TEXT		'0'
+#define GOPHER_MENU		'1'
+#define GOPHER_CSO		'2'
+#define GOPHER_ERROR		'3'
+#define GOPHER_MACBINHEX	'4'
+#define GOPHER_PCBINARY		'5'
+#define GOPHER_UUENCODED	'6'
+#define GOPHER_INDEX		'7'
+#define GOPHER_TELNET		'8'
+#define GOPHER_BINARY		'9'
+#define GOPHER_GIF		'g'
+#define GOPHER_HTML		'h'	/* HTML */
+#define GOPHER_CHTML		'H'	/* HTML */
+#define GOPHER_SOUND		's'
+#define GOPHER_WWW		'w'	/* W3 address */
+#define GOPHER_IMAGE		'I'
+#define GOPHER_TN3270		'T'
+#define GOPHER_INFO		'i'
+#define GOPHER_DUPLICATE	'+'
+#define GOPHER_PLUS_IMAGE	':'	/* Addition from Gopher Plus */
+#define GOPHER_PLUS_MOVIE	';'
+#define GOPHER_PLUS_SOUND	'<'
+#define GOPHER_PLUS_PDF		'P'
+
+#include <HTFormat.h>
+
+/*
+ *  Hypertext object building machinery.
+ */
+#include <HTML.h>
+
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYLeaks.h>
+
+#define PUTC(c) (*targetClass.put_character)(target, c)
+#define PUTS(s) (*targetClass.put_string)(target, s)
+#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
+#define END(e) (*targetClass.end_element)(target, e, 0)
+#define ENTITY(e) (*targetClass.put_entity)(target, e)
+#define FREE_TARGET (*targetClass._free)(target)
+
+#define NEXT_CHAR HTGetCharacter()
+
+/*
+ *  Module-wide variables.
+ */
+static int s;			/* Socket for gopher or CSO host */
+
+struct _HTStructured {
+    const HTStructuredClass *isa;	/* For gopher streams */
+    /* ... */
+};
+
+static HTStructured *target;	/* the new gopher hypertext */
+static HTStructuredClass targetClass;	/* Its action routines */
+
+/*	Matrix of allowed characters in filenames
+ *	=========================================
+ */
+static BOOL acceptable_html[256];
+static BOOL acceptable_file[256];
+static BOOL acceptable_inited = NO;
+
+static void init_acceptable(void)
+{
+    unsigned int i;
+    const char *good =
+    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";
+
+    for (i = 0; i < 256; i++) {
+	acceptable_html[i] = NO;
+	acceptable_file[i] = NO;
+    }
+    for (; *good; good++) {
+	acceptable_html[(unsigned int) *good] = YES;
+	acceptable_file[(unsigned int) *good] = YES;
+    }
+    for (good = ";?=#"; *good; ++good) {
+	acceptable_html[(unsigned int) *good] = YES;
+    }
+    acceptable_inited = YES;
+}
+
+/*	Decode one hex character
+ *	========================
+ */
+static const char hex[17] = "0123456789abcdef";
+
+static char from_hex(int c)
+{
+    return (char) ((c >= '0') && (c <= '9') ? c - '0'
+		   : (c >= 'A') && (c <= 'F') ? c - 'A' + 10
+		   : (c >= 'a') && (c <= 'f') ? c - 'a' + 10
+		   : 0);
+}
+
+/*	Paste in an Anchor
+ *	==================
+ *
+ *	The title of the destination is set, as there is no way
+ *	of knowing what the title is when we arrive.
+ *
+ * On entry,
+ *	HT	is in append mode.
+ *	text	points to the text to be put into the file, 0 terminated.
+ *	addr	points to the hypertext reference address 0 terminated.
+ */
+BOOLEAN HT_Is_Gemini_URL = FALSE;
+
+static void write_anchor(const char *text, const char *addr)
+{
+    BOOL present[HTML_A_ATTRIBUTES];
+    const char *value[HTML_A_ATTRIBUTES];
+
+    int i;
+
+    for (i = 0; i < HTML_A_ATTRIBUTES; i++)
+	present[i] = 0;
+    present[HTML_A_HREF] = YES;
+    ((const char **) value)[HTML_A_HREF] = addr;
+    present[HTML_A_TITLE] = YES;
+    ((const char **) value)[HTML_A_TITLE] = text;
+
+    CTRACE((tfp, "HTGemini: adding URL: %s\n", addr));
+
+    HT_Is_Gemini_URL = TRUE;	/* tell HTML.c that this is a Gopher URL */
+    (*targetClass.start_element) (target, HTML_A, present,
+				  (const char **) value, -1, 0);
+
+    PUTS(text);
+    END(HTML_A);
+}
+
+static int HTGeminiAcceptAllCerts(int preverify_ok GCC_UNUSED, X509_STORE_CTX * x509_ctx GCC_UNUSED) {
+  return 1;
+}
+
+static SSL_CTX* ssl_ctx = NULL;
+
+typedef struct {
+  int status;
+  union {
+    struct {
+      char* prompt;
+    } r1x;
+
+    struct {
+      char*   mimetype;
+      HTList* opts;
+    } r2x;
+
+    struct {
+      char* uri;
+    } r3x;
+
+    struct {
+      char* err;
+    } r4x;
+
+    struct {
+      char* err;
+    } r5x;
+
+    struct {
+      char* err;
+    } r6x;
+  };
+} HTGeminiHdr;
+
+// hdrbuf is the whole header without the crlf
+HTGeminiHdr* HTGeminiParseHdr(char const* hdrbuf, size_t hdrsz) {
+  char const* crlf;
+  HTGeminiHdr* hdr;
+
+  if (!hdrbuf || hdrsz < 2)
+    return NULL;
+
+  if (!isdigit(hdrbuf[0]) || !isdigit(hdrbuf[1]))
+    return NULL;
+
+  hdr = (HTGeminiHdr*)malloc(sizeof(HTGeminiHdr));
+  if (!hdr)
+    outofmem(__FILE__, "HTGeminiParseHdr");
+  memset(hdr, 0, sizeof(*hdr));
+
+  hdr->status = (hdrbuf[0] - '0') * 10 + (hdrbuf[1] - '0');
+
+  // chomp the status symbols
+  hdrbuf  += 2;
+  hdrsz   -= 2;
+
+  // chomp whitespace
+  while (hdrsz > 0 && isspace(*hdrbuf)) {
+    ++hdrbuf;
+    --hdrsz;
+  }
+
+  if (hdrsz > 0) {
+    switch (hdr->status / 10) {
+      case 1 :
+        hdr->r1x.prompt = strdup(hdrbuf);
+        break;
+      case 2:
+        {
+          char const* semicolon;
+          semicolon = strchr(hdrbuf, ';');
+          semicolon = semicolon ? semicolon : hdrbuf + hdrsz;
+          hdr->r2x.mimetype = strndup(hdrbuf, semicolon - hdrbuf);
+
+          if (semicolon) {
+            hdrsz = hdrsz - (semicolon + 1 - hdrbuf);
+            hdrbuf = semicolon + 1;
+          } else {
+            hdrbuf += hdrsz;
+            hdrsz = 0;
+          }
+
+          while (hdrsz && (semicolon = strchr(hdrbuf, ';')) != NULL) {
+            char* opt = strndup(hdrbuf, semicolon - hdrbuf);
+            HTList_appendObject(hdr->r2x.opts, (void*)opt);
+            hdrsz = hdrsz - (semicolon + 1 - hdrbuf);
+            hdrbuf = semicolon + 1;
+          }
+        }
+        break;
+      case 3 :
+        hdr->r3x.uri = strdup(hdrbuf);
+        break;
+      case 4 :
+        hdr->r4x.err = strdup(hdrbuf);
+        break;
+      case 5 :
+        hdr->r5x.err = strdup(hdrbuf);
+        break;
+      case 6 :
+        hdr->r5x.err = strdup(hdrbuf);
+        break;
+      default:
+    }
+  }
+
+  return hdr;
+}
+
+typedef struct {
+  char* url;
+  char* txt;
+} HTGeminiLink;
+
+void HTGeminiAddLink(char* buf, size_t buf_sz) {
+  // links are: =>{sp}{url}[{sp}{description} (plus usual \r\n)
+  char* url = NULL;
+  char* txt = NULL;
+
+  buf += 2;
+  while (*buf && isspace(*buf))
+    ++buf;
+
+  if (!*buf) {
+    START(HTML_A);
+    END(HTML_A);
+    return;
+  }
+
+  url = buf;
+  // go past the URL
+  while (*buf && !isspace(*buf))
+    ++buf;
+
+  if (*buf) {
+    *buf = '\0';
+    ++buf;
+
+    // skip whitespace
+    while (*buf && isspace(*buf))
+      ++buf;
+
+    if (*buf)
+      txt = buf;
+  }
+
+  if (!txt)
+    txt = url;
+
+  {
+    BOOL present[HTML_A_ATTRIBUTES] = { 0 };
+    const char *value[HTML_A_ATTRIBUTES] = { 0 };
+
+    present[HTML_A_HREF] = YES;
+    ((const char **) value)[HTML_A_HREF] = url;
+
+    (*targetClass.start_element) (target, HTML_A, present,
+        (const char **) value, -1, 0);
+    START(HTML_PRE);
+    PUTS(txt);
+    END(HTML_PRE);
+    END(HTML_A);
+    START(HTML_BR);
+    END(HTML_BR);
+  }
+}
+
+void HTGeminiDisplayGemtext(
+    HTGeminiHdr* hdr,
+    FILE* f, 
+    HTFormat format_out,
+    HTParentAnchor *anAnchor,
+    HTStream *sink)
+{
+  ssize_t r;
+  char* line = NULL;
+  size_t line_sz = 0;
+  int preformatted_mode = 0;
+
+  target = HTML_new(anAnchor, format_out, sink);
+  targetClass = *target->isa;
+  //START(HTML);
+
+  START(HTML_BODY);
+  while ((r = getline(&line, &line_sz, f)) != -1) {
+    char* l = line;
+    size_t lsz = line_sz;
+    int need_nl = 0;
+
+    if (!preformatted_mode && r > 0 && l[r - 1] == '\n') {
+      l[r - 1] = '\0';
+      need_nl = 1;
+      --r;
+    }
+
+    if (r == 0) {
+      if (need_nl) {
+        START(HTML_BR);
+        END(HTML_BR);
+      }
+      continue;
+    }
+
+    if (strncmp(l, "```", 3) == 0) {
+      if (preformatted_mode)
+        END(HTML_PRE);
+      else 
+        START(HTML_PRE);
+      preformatted_mode = !preformatted_mode;
+      continue;
+    }
+
+    if (preformatted_mode) {
+      PUTS(line);
+      continue;
+    }
+
+    if (strncmp(l, "=>", 2) == 0) {
+      HTGeminiAddLink(l, lsz);
+    } else if (*l == '#') {
+      ++l;
+      --lsz;
+      int n_hash = 1;
+      while (lsz && *l && *l == '#') {
+        ++n_hash;
+        ++l;
+        --lsz;
+      }
+
+      n_hash = n_hash <= 6 ? n_hash : 6;
+      START(HTML_H1 + (n_hash - 1));
+      PUTS(l);
+      END(HTML_H1 + (n_hash - 1));
+    } else { // text line
+      PUTS(line);
+      if (need_nl) {
+        START(HTML_BR);
+        END(HTML_BR);
+      }
+      PUTC('\n');
+    }
+  }
+  END(HTML_BODY);
+  //END(HTML);
+  free(line);
+  FREE_TARGET;
+}
+
+void HTGeminiDisplayText(
+    HTGeminiHdr* hdr,
+    FILE* f, 
+    HTFormat format_out,
+    HTParentAnchor *anAnchor,
+    HTStream *sink)
+{
+  if (!hdr->r2x.mimetype || !strcmp(hdr->r2x.mimetype, "text/plain")) {
+    HTParseFile(WWW_PLAINTEXT, format_out, anAnchor, f, sink);
+  } else if (!strcmp(hdr->r2x.mimetype, "text/html")) {
+    HTParseFile(WWW_HTML, format_out, anAnchor, f, sink);
+  } else if (!strcmp(hdr->r2x.mimetype, "text/gemini")) {
+    HTGeminiDisplayGemtext(hdr, f, format_out, anAnchor, sink);
+  } else {
+    // TODO
+    HTParseFile(WWW_PLAINTEXT, format_out, anAnchor, f, sink);
+  }
+}
+
+static void HTGeminiHdrFree(HTGeminiHdr* hdr) {
+  if (!hdr)
+    return;
+  switch (hdr->status / 10) {
+    case 1 :
+      free(hdr->r1x.prompt);
+      break;
+    case 2 :
+      {
+        HTList* l;
+        free(hdr->r2x.mimetype);
+        l = hdr->r2x.opts;
+        while (l) {
+          free(l->object);
+          l = l->next;
+        }
+        HTList_delete(hdr->r2x.opts);
+      }
+      break;
+    case 3 :
+      free(hdr->r3x.uri);
+      break;
+    case 4 :
+      free(hdr->r4x.err);
+      break;
+    case 5 :
+      free(hdr->r5x.err);
+      break;
+    case 6 :
+      free(hdr->r6x.err);
+      break;
+  }
+  free(hdr);
+}
+
+/*	Load by name.						HTLoadGemini
+ *	=============
+ *
+ */
+static int HTLoadGemini(const char *arg,
+			HTParentAnchor *anAnchor,
+			HTFormat format_out,
+			HTStream *sink)
+{
+
+#ifndef USE_SSL /* SSL is required for Gemini */
+  HTAlert(gettext("Gemini requires SSL support, which is not available!"));
+  return -3;
+# else
+
+  const size_t MAX_BUF_SIZE = 4096;
+
+  int ret;
+  int status;			/* tcp return */
+  char *command;		/* The whole command */
+  char *crlf;		/* Selector string */
+  char* buf;
+  HTGeminiHdr* hdr;
+  int s;
+  SSL* ssl;
+  size_t iosz;
+  int resp_code = 0;
+
+  if (!acceptable_inited)
+    init_acceptable();
+
+  if (!arg)
+    return -3;		/* Bad if no name specified     */
+  if (!*arg)
+    return -2;		/* Bad if name had zero length  */
+  CTRACE((tfp, "HTGemini: Looking for %s\n", arg));
+
+  {
+    size_t cmdlen = strlen(arg);
+    command = (char*)malloc(cmdlen + 2 /* \r\n */ + 1 /* null */);
+    if (!command)
+      outofmem(__FILE__, "HTLoadGemini");
+    strcpy(command, arg);
+    //de_escape(command, selector);
+  }
+
+  { // append crlf
+    char *p = command + strlen(command);
+    *p++ = CR;		/* Macros to be correct on Mac */
+    *p++ = LF;
+    *p = '\0';
+  }
+
+  /*
+   * Set up a socket to the server for the data.
+   */
+  status = HTDoConnect(arg, "gemini", GEMINI_PORT, &s);
+  if (status == HT_INTERRUPTED) {
+    /*
+     * Interrupt cleanly.
+     */
+    CTRACE((tfp, "HTGemini: Interrupted on connect; recovering cleanly.\n"));
+    _HTProgress(CONNECTION_INTERRUPTED);
+    FREE(command);
+    return HT_NOT_LOADED;
+  }
+
+  if (status < 0) {
+    CTRACE((tfp, "HTGemini: Unable to connect to remote host for `%s'.\n",
+          arg));
+    FREE(command);
+    return HTInetStatus("connect");
+  }
+
+  if (!ssl_ctx) {
+    // TODO this is openssl only for now.  we should probably include e.g. gnutls
+    // at least, maybe SSLeay based on other lynx ssl code?
+    ssl_ctx = SSL_CTX_new(TLS_client_method());
+    // avoid SSLv2 and SSLv3
+    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
+
+    const char* const PREFERRED_CIPHERS = "HIGH:!aNULL:!kRSA:!SRP:!PSK:!CAMELLIA:!RC4:!MD5:!DSS";
+    SSL_CTX_set_cipher_list(ssl_ctx, PREFERRED_CIPHERS);
+
+    // TODO proper cert checking, TOFU implementation, etc
+    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, HTGeminiAcceptAllCerts);
+    //SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_COMPRESSION);
+    HTSSLInitPRNG();
+  }
+
+  ssl = SSL_new(ssl_ctx);
+  SSL_set_fd(ssl, s);
+  // TODO load client certificate
+
+  {
+    char* ssl_host;
+    int r;
+
+    ssl_host = HTParse(arg, "", PARSE_HOST);
+    r = (int) SSL_set_tlsext_host_name(ssl, ssl_host);
+    CTRACE((tfp, "...called SSL_set_tlsext_host_name(%s) ->%d\n",
+      ssl_host, r));
+    FREE(ssl_host);
+  }
+
+  //SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
+  status = SSL_connect(ssl);
+  if (status != 1) {
+    CTRACE((tfp, "HTGemini: SSL_connect failed.\n"));
+    ret = HT_NO_DATA;
+    goto shutdown;
+  }
+
+  //HTInitInput(s);		/* Set up input buffering */
+
+  CTRACE((tfp, "HTGemini: Connected, writing command `%s' to socket %d\n",
+        command, s));
+
+#ifdef NOT_ASCII
+  {
+    char *p;
+
+    for (p = command; *p; p++) {
+      *p = TOASCII(*p);
+    }
+  }
+#endif
+
+  _HTProgress(GEMINI_SENDING_REQUEST);
+  
+  status = SSL_write_ex(ssl, command, strlen(command), &iosz);
+  FREE(command);
+  if (status != 1) {
+    CTRACE((tfp, "HTGemini: Unable to send command.\n"));
+    ret = HTInetStatus("send");
+    goto shutdown;
+  }
+
+  _HTProgress(GEMINI_SENT_REQUEST);
+
+  buf = (char*)malloc(MAX_BUF_SIZE+ 1);
+  if (!buf) {
+    outofmem(__FILE__, "HTLoadGemini");
+    goto shutdown;
+  }
+  iosz = 0;
+  status = SSL_read_ex(ssl, buf, MAX_BUF_SIZE, &iosz);
+  buf[iosz] = '\0';
+
+  if (status != 1 || iosz < 4) { // strlen({digit}{digit}\r\n) == 4
+    ret = HT_NO_DATA;
+    goto shutdown;
+  }
+
+  crlf = strstr(buf, "\r\n");
+  if (!crlf) {
+    HTAlert("HTGemini: invalid response header");
+    goto shutdown;
+  }
+
+  // end the header string at crlf, ensuring that string ops will work properly
+  *crlf = '\0';
+
+  hdr = HTGeminiParseHdr(buf, crlf - buf);
+
+  if (!hdr || hdr->status == 0) {
+    HTAlert("HTGemini: invalid response header");
+    ret = HT_NO_DATA;
+    goto shutdown;
+  }
+
+  if (hdr->status / 10 == 2) { // 2x response codes ok, need body
+    char* bufdata;
+    FILE* f;
+    // TODO do we want to do this a better way?  open_memstream is POSIX, right?
+    char* fbuf;
+    size_t fbuf_sz;
+    f = open_memstream(&fbuf, &fbuf_sz);
+    if (!f) {
+      ret = HT_NO_DATA;
+      goto shutdown;
+    }
+
+    // write any data that was read into buf to the file
+    if (crlf - buf + 2 < iosz) {
+      bufdata = crlf + 2;
+      if (fwrite(bufdata, iosz - (crlf - buf + 2), 1, f) != 1) {
+        // TODO actual error handling
+        int e = errno;
+        CTRACE((tfp, "HTGemini: writing buffered data to : %s\n", strerror(e)));
+      }
+    }
+
+    while (1) {
+      status = SSL_read_ex(ssl, buf, MAX_BUF_SIZE, &iosz);
+      if (status != 1) {
+        int e = SSL_get_error(ssl, status);
+        if (e != SSL_ERROR_WANT_READ) // TODO more conditions?  error message?
+          break;
+      }
+      if (iosz) {
+        if (fwrite(buf, iosz, 1, f) != 1) {
+          int e = errno;
+          CTRACE((tfp, "HTGemini: bad write to memory buffer: %s", strerror(e)));
+          // TODO break?
+        }
+      }
+    }
+    fflush(f);
+
+    if (!hdr->r2x.mimetype || !strncmp(hdr->r2x.mimetype, "text/", strlen("text/"))) {
+      HTGeminiDisplayText(hdr, f, format_out, anAnchor, sink);
+      //HTParseFile(WWW_PLAINTEXT, format_out, anAnchor, f, sink);
+    } else {
+      HTParseFile(WWW_PLAINTEXT, format_out, anAnchor, f, sink);
+      char buf[64];
+      snprintf(buf, sizeof(buf), "Got mime type %s\n", hdr->r2x.mimetype);
+      HTAlert(buf);
+    }
+    fclose(f);
+    free(fbuf);
+    ret = HT_LOADED;
+  } else {
+    char buf[128];
+    switch (hdr->status / 10) {
+      case 1 :
+        snprintf(buf, sizeof(buf), gettext("Input required %02d: %s\n"), hdr->status, hdr->r1x.prompt);
+        break;
+      case 3 :
+        snprintf(buf, sizeof(buf), gettext("Redirect %02d: %s\n"), hdr->status, hdr->r3x.uri);
+        break;
+      case 4 :
+        snprintf(buf, sizeof(buf), gettext("Temporary failure %02d: %s\n"), hdr->status, hdr->r4x.err);
+        break;
+      case 5 :
+        snprintf(buf, sizeof(buf), gettext("Permanent failure %02d: %s\n"), hdr->status, hdr->r5x.err);
+        break;
+      case 6 :
+        snprintf(buf, sizeof(buf), gettext("Authentication required %02d: %s\n"), hdr->status, hdr->r6x.err);
+        break;
+      default :
+        snprintf(buf, sizeof(buf), gettext("Response code %02d\n"), hdr->status);
+    }
+    // TODO handle input
+    snprintf(buf, sizeof(buf), gettext("Got response code %02d from server\n"), hdr->status);
+    HTAlert(buf);
+    ret = HT_NO_DATA;
+  }
+
+shutdown:
+  HTGeminiHdrFree(hdr);
+  free(buf);
+  if (ssl) {
+    SSL_shutdown(ssl);
+    SSL_free(ssl);
+  }
+  if (s >= 0)
+    NETCLOSE(s);
+  return ret;
+#endif // USE_SSL 
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _HTGEMINI_C_1_INIT { "gopher", HTLoadGemini, NULL }
+GLOBALDEF(HTProtocol, HTGemini, _HTGEMINI_C_1_INIT);
+#else
+GLOBALDEF HTProtocol HTGemini =
+{"gemini", HTLoadGemini, NULL};
+#endif /* GLOBALDEF_IS_MACRO */
+
+#endif /* not DISABLE_GOPHER */
diff --git a/WWW/Library/Implementation/HTGemini.h b/WWW/Library/Implementation/HTGemini.h
new file mode 100644
index 00000000..c178b78f
--- /dev/null
+++ b/WWW/Library/Implementation/HTGemini.h
@@ -0,0 +1,29 @@
+/*                                                          Gemini protocol module for libwww
+                                      GEMINI ACCESS
+                                             
+  HISTORY:
+  
+  4 Apr 24               Adapted from HTGopher.c
+                         
+ */
+
+#ifndef HTGEMINI_H
+#define HTGEMINI_H
+
+#include <HTAccess.h>
+#include <HTAnchor.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef GLOBALREF_IS_MACRO
+    extern GLOBALREF (HTProtocol, HTGemini);
+
+#else
+    GLOBALREF HTProtocol HTGemini;
+#endif				/* GLOBALREF_IS_MACRO */
+
+#ifdef __cplusplus
+}
+#endif
+#endif				/* HTGEMINI_H */
diff --git a/WWW/Library/Implementation/makefile.in b/WWW/Library/Implementation/makefile.in
index 44de1c71..5a7df77a 100644
--- a/WWW/Library/Implementation/makefile.in
+++ b/WWW/Library/Implementation/makefile.in
@@ -109,7 +109,7 @@ COMMON = $(LOB)/HTParse$o $(LOB)/HTAccess$o $(LOB)/HTTP$o \
 	$(LOB)/HTAtom$o $(LOB)/HTAnchor$o $(LOB)/HTStyle$o \
 	$(LOB)/HTList$o $(LOB)/HTString$o \
 	$(LOB)/HTRules$o $(LOB)/HTFormat$o  $(LOB)/HTMIME$o \
-	$(LOB)/HTNews$o  $(LOB)/HTGopher$o \
+	$(LOB)/HTNews$o  $(LOB)/HTGopher$o $(LOB)/HTGemini$o \
 	$(LOB)/HTTelnet$o $(LOB)/HTFinger$o $(LOB)/HTWSRC$o $(HTWAIS_o) \
 	$(LOB)/HTAAUtil$o $(LOB)/HTAABrow$o \
 	$(LOB)/HTGroup$o \
@@ -126,7 +126,7 @@ CFILES = $(CMN)HTParse.c $(CMN)HTAccess.c $(CMN)HTTP.c $(CMN)HTFile.c \
 	$(CMN)HTChunk.c $(CMN)HTAtom.c    $(CMN)HTAnchor.c $(CMN)HTStyle.c \
 	$(CMN)HTList.c  $(CMN)HTString.c  $(CMN)HTRules.c \
 	$(CMN)HTFormat.c $(CMN)HTMIME.c \
-	$(CMN)HTNews.c  $(CMN)HTGopher.c  $(CMN)HTTelnet.c \
+	$(CMN)HTNews.c  $(CMN)HTGopher.c  $(CMN)HTGemini.c $(CMN)HTTelnet.c \
 	$(CMN)HTFinger.c $(HTWAIS_c)	  $(CMN)HTWSRC.c \
 	$(CMN)HTAABrow.c \
 	$(CMN)HTGroup.c  \
@@ -144,7 +144,7 @@ HFILES = $(CMN)HTParse.h $(CMN)HTAccess.h $(CMN)HTTP.h $(CMN)HTFile.h \
 	$(CMN)HTString.h $(CMN)HTRules.h \
 	$(CMN)HTFormat.h $(CMN)HTInit.h \
 	$(CMN)HTMIME.h $(CMN)HTNews.h \
-	$(CMN)HTGopher.h \
+	$(CMN)HTGopher.h $(CMN)HTGemini.h \
 	$(CMN)HTUtils.h $(CMN)www_tcp.h $(CMN)HText.h \
 	$(CMN)HTTelnet.h $(CMN)HTFinger.h \
 	$(CMN)HTWAIS.h  $(CMN)HTWSRC.h \
@@ -216,6 +216,10 @@ $(LOB)/HTGopher$o : $(CMN)HTGopher.c $(CMN)HTUtils.h $(CMN)HTList.h \
 	 $(CMN)HTMLDTD.h
 	$(COMPILE) $(CMN)HTGopher.c
 
+$(LOB)/HTGemini$o : $(CMN)HTGemini.c $(CMN)HTUtils.h $(CMN)HTList.h \
+	 $(CMN)HTMLDTD.h
+	$(COMPILE) $(CMN)HTGemini.c
+
 $(LOB)/HTTelnet$o : $(CMN)HTTelnet.c $(CMN)HTUtils.h $(CMN)HTTelnet.h $(CMN)../../../userdefs.h
 	$(COMPILE) $(CMN)HTTelnet.c
 
diff --git a/makefile.bcb b/makefile.bcb
index 8ae1443e..16c938e1 100644
--- a/makefile.bcb
+++ b/makefile.bcb
@@ -136,6 +136,7 @@ LYNX_OBJS = \
    HTFtp.obj\
    HTFwriter.obj\
    HTGopher.obj\
+   HTGemini.obj\
    HTGroup.obj\
    HTInit.obj\
    HTLex.obj\
@@ -323,6 +324,11 @@ HTGopher.obj :  $(WWW_LIB)/HTGopher.c
  $(CEAT_lynxdexe) $(CC_FLAGS) -o$@ $(WWW_LIB)/HTGopher.c
 |
 
+HTGemini.obj :  $(WWW_LIB)/HTGemini.c
+  $(BCC32) -P- -c @&&|
+ $(CEAT_lynxdexe) $(CC_FLAGS) -o$@ $(WWW_LIB)/HTGemini.c
+|
+
 HTGroup.obj :  $(WWW_LIB)/HTGroup.c
   $(BCC32) -P- -c @&&|
  $(CEAT_lynxdexe) $(CC_FLAGS) -o$@ $(WWW_LIB)/HTGroup.c
diff --git a/makefile.msc b/makefile.msc
index 19eb0221..f2e0196b 100644
--- a/makefile.msc
+++ b/makefile.msc
@@ -153,6 +153,7 @@ OBJS = $(CS_OBJS) \
 	"HTFinger.obj" \
 	"HTFormat.obj" \
 	"HTGopher.obj" \
+	"HTGemini.obj" \
 	"HTGroup.obj" \
 	"HTInit.obj" \
 	"HTLex.obj" \
@@ -460,6 +461,9 @@ HTFTP.obj : $(WWW_DIR)\HTFTP.c
 HTGopher.obj : $(WWW_DIR)\HTGopher.c
 	$(COMPILE) $(WWW_DIR)\HTGopher.c
 
+HTGemini.obj : $(WWW_DIR)\HTGemini.c
+	$(COMPILE) $(WWW_DIR)\HTGemini.c
+
 HTGroup.obj : $(WWW_DIR)\HTGroup.c
 	$(COMPILE) $(WWW_DIR)\HTGroup.c
 
diff --git a/makelynx.bat b/makelynx.bat
index 9c1d0ee7..81bedfc5 100644
--- a/makelynx.bat
+++ b/makelynx.bat
@@ -161,6 +161,8 @@ if errorlevel 1 PAUSE
 if errorlevel 1 PAUSE
 %COMPILE_CMD% HTGopher.c
 if errorlevel 1 PAUSE
+%COMPILE_CMD% HTGemini.c
+if errorlevel 1 PAUSE
 %COMPILE_CMD% HTGroup.c
 if errorlevel 1 PAUSE
 
diff --git a/src/LYGetFile.c b/src/LYGetFile.c
index 57e7f30e..c02cb66e 100644
--- a/src/LYGetFile.c
+++ b/src/LYGetFile.c
@@ -269,6 +269,7 @@ int getfile(DocInfo *doc, int *target)
 	    url_type != LYNXCGI_URL_TYPE &&
 	    url_type != LYNXIMGMAP_URL_TYPE &&
 	    url_type != GOPHER_URL_TYPE &&
+	    url_type != GEMINI_URL_TYPE &&
 	    url_type != CSO_URL_TYPE &&
 	    url_type != PROXY_URL_TYPE &&
 	    url_type != LYNXOPTIONS_URL_TYPE &&
@@ -887,6 +888,8 @@ int getfile(DocInfo *doc, int *target)
 		       url_type == NCFTP_URL_TYPE)) ||
 		     (no_goto_gopher &&
 		      url_type == GOPHER_URL_TYPE) ||
+		     (no_goto_gemini &&
+		      url_type == GEMINI_URL_TYPE) ||
 		     (no_goto_http &&
 		      url_type == HTTP_URL_TYPE) ||
 		     (no_goto_https &&
diff --git a/src/LYGlobalDefs.h b/src/LYGlobalDefs.h
index 37e30ba4..7e781142 100644
--- a/src/LYGlobalDefs.h
+++ b/src/LYGlobalDefs.h
@@ -342,6 +342,7 @@ extern "C" {
     extern BOOLEAN no_goto_finger;
     extern BOOLEAN no_goto_ftp;
     extern BOOLEAN no_goto_gopher;
+    extern BOOLEAN no_goto_gemini;
     extern BOOLEAN no_goto_http;
     extern BOOLEAN no_goto_https;
     extern BOOLEAN no_goto_lynxcgi;
diff --git a/src/LYMain.c b/src/LYMain.c
index 1d52bb66..390b08ee 100644
--- a/src/LYMain.c
+++ b/src/LYMain.c
@@ -275,6 +275,7 @@ BOOLEAN no_goto_file = FALSE;
 BOOLEAN no_goto_finger = FALSE;
 BOOLEAN no_goto_ftp = FALSE;
 BOOLEAN no_goto_gopher = FALSE;
+BOOLEAN no_goto_gemini = FALSE;
 BOOLEAN no_goto_http = FALSE;
 BOOLEAN no_goto_https = FALSE;
 BOOLEAN no_goto_lynxcgi = FALSE;
diff --git a/src/LYMainLoop.c b/src/LYMainLoop.c
index d580c5da..afaaa44c 100644
--- a/src/LYMainLoop.c
+++ b/src/LYMainLoop.c
@@ -626,6 +626,7 @@ static void do_check_goto_URL(bstring **user_input,
 	{ STR_FINGER_URL,	&no_goto_finger },
 	{ STR_FTP_URL,		&no_goto_ftp },
 	{ STR_GOPHER_URL,	&no_goto_gopher },
+	{ STR_GEMINI_URL,	&no_goto_gemini },
 	{ STR_HTTP_URL,		&no_goto_http },
 	{ STR_HTTPS_URL,	&no_goto_https },
 	{ STR_MAILTO_URL,	&no_goto_mailto },
diff --git a/src/LYUtils.c b/src/LYUtils.c
index e45e641e..3f8f7c3f 100644
--- a/src/LYUtils.c
+++ b/src/LYUtils.c
@@ -2522,26 +2522,33 @@ UrlTypes is_url(char *filename)
 		    }
 		    break;
 
-#ifndef DISABLE_GOPHER
+#if !defined(DISABLE_GOPHER) && !defined(DISABLE_GEMINI)
 		case 'G':
 		case 'g':
-		    if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) {
-			if (strlen(cp) >= 11
-			    && (cp1 = StrChr(cp + 11, '/')) != NULL) {
-
-			    if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w')
-				/* if this is a gopher html type */
-				result = HTML_GOPHER_URL_TYPE;
-			    else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')
-				result = TELNET_GOPHER_URL_TYPE;
-			    else if (*(cp1 + 1) == '7')
-				result = INDEX_GOPHER_URL_TYPE;
-			    else
-				result = GOPHER_URL_TYPE;
-			} else {
-			    result = GOPHER_URL_TYPE;
-			}
-		    }
+#if !defined(DISABLE_GOPHER)
+        if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) {
+          if (strlen(cp) >= 11
+              && (cp1 = StrChr(cp + 11, '/')) != NULL) {
+
+            if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w')
+              /* if this is a gopher html type */
+              result = HTML_GOPHER_URL_TYPE;
+            else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')
+              result = TELNET_GOPHER_URL_TYPE;
+            else if (*(cp1 + 1) == '7')
+              result = INDEX_GOPHER_URL_TYPE;
+            else
+              result = GOPHER_URL_TYPE;
+          } else {
+            result = GOPHER_URL_TYPE;
+          }
+        }
+#endif // !defined(DISABLE_GOPHER)
+#ifndef DISABLE_GEMINI
+        if (CompareType(cp, STR_GEMINI_URL, LEN_GEMINI_URL)) {
+          result = GEMINI_URL_TYPE;
+        }
+#endif // DISABLE_GEMINI
 		    break;
 #endif
 		case 'W':
@@ -3736,6 +3743,9 @@ static const struct {
 #ifndef DISABLE_GOPHER
     { "goto_gopher",	&no_goto_gopher,	CAN_ANONYMOUS_GOTO_GOPHER },
 #endif
+//#ifndef DISABLE_GEMINI
+    { "goto_gemini",	&no_goto_gemini,	CAN_ANONYMOUS_GOTO_GEMINI },
+//#endif
     { "goto_http",	&no_goto_http,		CAN_ANONYMOUS_GOTO_HTTP },
     { "goto_https",	&no_goto_https,		CAN_ANONYMOUS_GOTO_HTTPS },
     { "goto_lynxcgi",	&no_goto_lynxcgi,	CAN_ANONYMOUS_GOTO_LYNXCGI },
@@ -4110,6 +4120,8 @@ static const char *guess_scheme(const char *url)
 	    scheme = "ftp://";
 	} else if (0 == strncasecomp(url, "gopher.", 7)) {
 	    scheme = "gopher://";
+	} else if (0 == strncasecomp(url, "gemini.", 7)) {
+	    scheme = "gemini://";
 	} else if (0 == strncasecomp(url, "wais.", 5)) {
 	    scheme = "wais://";
 	} else if (0 == strncasecomp(url, "cso.", 4) ||
diff --git a/src/LYUtils.h b/src/LYUtils.h
index 7ad76db7..944cf869 100644
--- a/src/LYUtils.h
+++ b/src/LYUtils.h
@@ -387,7 +387,9 @@ extern "C" {
 	LYNXOPTIONS_URL_TYPE,
 	LYNXPRINT_URL_TYPE,
 
-	PROXY_URL_TYPE
+	PROXY_URL_TYPE,
+
+  GEMINI_URL_TYPE
 
     } UrlTypes;
 
@@ -420,6 +422,10 @@ extern "C" {
 #define LEN_GOPHER_URL       7
 #define isGOPHER_URL(addr)   !strncasecomp(addr, STR_GOPHER_URL, LEN_GOPHER_URL)
 
+#define STR_GEMINI_URL       "gemini:"
+#define LEN_GEMINI_URL       7
+#define isGEMINI_URL(addr)   !strncasecomp(addr, STR_GEMINI_URL, LEN_GEMINI_URL)
+
 #define STR_HTTP_URL         "http:"
 #define LEN_HTTP_URL         5
 #define isHTTP_URL(addr)     !strncasecomp(addr, STR_HTTP_URL, LEN_HTTP_URL)
diff --git a/userdefs.h b/userdefs.h
index fb610474..618221b8 100644
--- a/userdefs.h
+++ b/userdefs.h
@@ -1761,6 +1761,7 @@
 #define CAN_ANONYMOUS_GOTO_FINGER	TRUE
 #define CAN_ANONYMOUS_GOTO_FTP		FALSE
 #define CAN_ANONYMOUS_GOTO_GOPHER	FALSE
+#define CAN_ANONYMOUS_GOTO_GEMINI FALSE
 #define CAN_ANONYMOUS_GOTO_HTTP		TRUE
 #define CAN_ANONYMOUS_GOTO_HTTPS	FALSE
 #define CAN_ANONYMOUS_GOTO_LYNXCGI	FALSE
