]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/gopher.cc
Summary: Encapsulate ClientHttpRequest::entry.
[thirdparty/squid.git] / src / gopher.cc
index 5a54882ad9adab630a18eef9f1c6b1c9c297eb73..f7dbbf2cf6221f4fcb805d8841702756faa32273 100644 (file)
@@ -1,17 +1,21 @@
 
 /*
- * $Id: gopher.cc,v 1.100 1997/10/22 05:50:29 wessels Exp $
+ * $Id: gopher.cc,v 1.183 2003/06/20 01:01:00 robertc Exp $
  *
  * DEBUG: section 10    Gopher
  * AUTHOR: Harvest Derived
  *
- * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
- * --------------------------------------------------------
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
  *
- *  Squid is the result of efforts by numerous individuals from the
- *  Internet community.  Development is led by Duane Wessels of the
- *  National Laboratory for Applied Network Research and funded by
- *  the National Science Foundation.
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *  
- */
-
-/*
- * Copyright (c) 1994, 1995.  All rights reserved.
- *  
- *   The Harvest software was developed by the Internet Research Task
- *   Force Research Group on Resource Discovery (IRTF-RD):
- *  
- *         Mic Bowman of Transarc Corporation.
- *         Peter Danzig of the University of Southern California.
- *         Darren R. Hardy of the University of Colorado at Boulder.
- *         Udi Manber of the University of Arizona.
- *         Michael F. Schwartz of the University of Colorado at Boulder.
- *         Duane Wessels of the University of Colorado at Boulder.
- *  
- *   This copyright notice applies to software in the Harvest
- *   ``src/'' directory only.  Users should consult the individual
- *   copyright notices in the ``components/'' subdirectories for
- *   copyright information about other software bundled with the
- *   Harvest source code distribution.
- *  
- * TERMS OF USE
- *   
- *   The Harvest software may be used and re-distributed without
- *   charge, provided that the software origin and research team are
- *   cited in any use of the system.  Most commonly this is
- *   accomplished by including a link to the Harvest Home Page
- *   (http://harvest.cs.colorado.edu/) from the query page of any
- *   Broker you deploy, as well as in the query result pages.  These
- *   links are generated automatically by the standard Broker
- *   software distribution.
- *   
- *   The Harvest software is provided ``as is'', without express or
- *   implied warranty, and with no support nor obligation to assist
- *   in its use, correction, modification or enhancement.  We assume
- *   no liability with respect to the infringement of copyrights,
- *   trade secrets, or any patents, and are not responsible for
- *   consequential damages.  Proper use of the Harvest software is
- *   entirely the responsibility of the user.
- *  
- * DERIVATIVE WORKS
- *  
- *   Users may make derivative works from the Harvest software, subject 
- *   to the following constraints:
- *  
- *     - You must include the above copyright notice and these 
- *       accompanying paragraphs in all forms of derivative works, 
- *       and any documentation and other materials related to such 
- *       distribution and use acknowledge that the software was 
- *       developed at the above institutions.
- *  
- *     - You must notify IRTF-RD regarding your distribution of 
- *       the derivative work.
- *  
- *     - You must clearly notify users that your are distributing 
- *       a modified version and not the original Harvest software.
- *  
- *     - Any derivative product is also subject to these copyright 
- *       and use restrictions.
- *  
- *   Note that the Harvest software is NOT in the public domain.  We
- *   retain copyright, as specified above.
- *  
- * HISTORY OF FREE SOFTWARE STATUS
- *  
- *   Originally we required sites to license the software in cases
- *   where they were going to build commercial products/services
- *   around Harvest.  In June 1995 we changed this policy.  We now
- *   allow people to use the core Harvest software (the code found in
- *   the Harvest ``src/'' directory) for free.  We made this change
- *   in the interest of encouraging the widest possible deployment of
- *   the technology.  The Harvest software is really a reference
- *   implementation of a set of protocols and formats, some of which
- *   we intend to standardize.  We encourage commercial
- *   re-implementations of code complying to this set of standards.  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
  */
 
 #include "squid.h"
+#include "Store.h"
+#include "HttpRequest.h"
+#include "comm.h"
+#if DELAY_POOLS
+#include "DelayPools.h"
+#include "MemObject.h"
+#endif
 
 /* gopher type code from rfc. Anawat. */
 #define GOPHER_FILE         '0'
 #define GOPHER_PORT         70
 
 #define TAB                 '\t'
-#define TEMP_BUF_SIZE       SM_PAGE_SIZE
+#define TEMP_BUF_SIZE       4096
 #define MAX_CSO_RESULT      1024
 
-typedef struct gopher_ds {
+typedef struct gopher_ds
+{
     StoreEntry *entry;
-    char host[SQUIDHOSTNAMELEN + 1];
     enum {
-       NORMAL,
-       HTML_DIR,
-       HTML_INDEX_RESULT,
-       HTML_CSO_RESULT,
-       HTML_INDEX_PAGE,
-       HTML_CSO_PAGE
+        NORMAL,
+        HTML_DIR,
+        HTML_INDEX_RESULT,
+        HTML_CSO_RESULT,
+        HTML_INDEX_PAGE,
+        HTML_CSO_PAGE
     } conversion;
     int HTML_header_added;
-    int port;
     char type_id;
     char request[MAX_URL];
     int data_in;
@@ -157,44 +92,46 @@ typedef struct gopher_ds {
     int len;
     char *buf;                 /* pts to a 4k page */
     int fd;
-} GopherStateData;
+    request_t *req;
+    FwdState *fwdState;
+    char replybuf[BUFSIZ];
+}
+
+GopherStateData;
 
 static PF gopherStateFree;
-static void gopher_mime_content _PARAMS((char *buf, const char *name, const char *def));
-static void gopherMimeCreate _PARAMS((GopherStateData *));
-static int gopher_url_parser(const char *url,
-    char *host,
-    int *port,
-    char *type_id,
-    char *request);
-static void gopherEndHTML _PARAMS((GopherStateData *));
-static void gopherToHTML _PARAMS((GopherStateData *, char *inbuf, int len));
+static void gopher_mime_content(MemBuf * mb, const char *name, const char *def);
+static void gopherMimeCreate(GopherStateData *);
+static void gopher_request_parse(const request_t * req,
+                                 char *type_id,
+                                 char *request);
+static void gopherEndHTML(GopherStateData *);
+static void gopherToHTML(GopherStateData *, char *inbuf, int len);
 static PF gopherTimeout;
-static PF gopherReadReply;
-static void gopherSendComplete(int fd,
-    char *buf,
-    int size,
-    int errflag,
-    void *data);
+static IOCB gopherReadReply;
+static IOWCB gopherSendComplete;
 static PF gopherSendRequest;
-static GopherStateData *CreateGopherStateData _PARAMS((void));
-static CNCB gopherConnectDone;
-static STABH gopherAbort;
 
 static char def_gopher_bin[] = "www/unknown";
 static char def_gopher_text[] = "text/plain";
 
 static void
-gopherStateFree(int fd, void *data)
+gopherStateFree(int fdnotused, void *data)
 {
-    GopherStateData *gopherState = data;
+    GopherStateData *gopherState = (GopherStateData *)data;
+
     if (gopherState == NULL)
-       return;
+        return;
+
     if (gopherState->entry) {
-       storeUnregisterAbort(gopherState->entry);
-       storeUnlockObject(gopherState->entry);
+        storeUnlockObject(gopherState->entry);
+    }
+
+    if (gopherState->req) {
+        requestUnlink(gopherState->req);
     }
-    put_free_4k_page(gopherState->buf);
+
+    memFree(gopherState->buf, MEM_4K_BUF);
     gopherState->buf = NULL;
     cbdataFree(gopherState);
 }
@@ -202,14 +139,16 @@ gopherStateFree(int fd, void *data)
 
 /* figure out content type from file extension */
 static void
-gopher_mime_content(char *buf, const char *name, const char *def_ctype)
+gopher_mime_content(MemBuf * mb, const char *name, const char *def_ctype)
 {
     char *ctype = mimeGetContentType(name);
     char *cenc = mimeGetContentEncoding(name);
+
     if (cenc)
-       snprintf(buf + strlen(buf), MAX_MIME - strlen(buf), "Content-Encoding: %s\r\n", cenc);
-    snprintf(buf + strlen(buf), MAX_MIME - strlen(buf), "Content-Type: %s\r\n",
-       ctype ? ctype : def_ctype);
+        memBufPrintf(mb, "Content-Encoding: %s\r\n", cenc);
+
+    memBufPrintf(mb, "Content-Type: %s\r\n",
+                 ctype ? ctype : def_ctype);
 }
 
 
@@ -218,125 +157,165 @@ gopher_mime_content(char *buf, const char *name, const char *def_ctype)
 static void
 gopherMimeCreate(GopherStateData * gopherState)
 {
-    LOCAL_ARRAY(char, tempMIME, MAX_MIME);
+    MemBuf mb;
 
-    snprintf(tempMIME, MAX_MIME,
-       "HTTP/1.0 200 OK Gatewaying\r\n"
-       "Server: Squid/%s\r\n"
-       "Date: %s\r\n"
-       "MIME-version: 1.0\r\n",
-       version_string, mkrfc1123(squid_curtime));
+    memBufDefInit(&mb);
+
+    memBufPrintf(&mb,
+                 "HTTP/1.0 200 OK Gatewaying\r\n"
+                 "Server: Squid/%s\r\n"
+                 "Date: %s\r\n",
+                 version_string, mkrfc1123(squid_curtime));
 
     switch (gopherState->type_id) {
 
     case GOPHER_DIRECTORY:
+
     case GOPHER_INDEX:
+
     case GOPHER_HTML:
+
     case GOPHER_WWW:
+
     case GOPHER_CSO:
-       strcat(tempMIME, "Content-Type: text/html\r\n");
-       break;
+        memBufPrintf(&mb, "Content-Type: text/html\r\n");
+        break;
+
     case GOPHER_GIF:
+
     case GOPHER_IMAGE:
+
     case GOPHER_PLUS_IMAGE:
-       strcat(tempMIME, "Content-Type: image/gif\r\n");
-       break;
+        memBufPrintf(&mb, "Content-Type: image/gif\r\n");
+        break;
+
     case GOPHER_SOUND:
+
     case GOPHER_PLUS_SOUND:
-       strcat(tempMIME, "Content-Type: audio/basic\r\n");
-       break;
+        memBufPrintf(&mb, "Content-Type: audio/basic\r\n");
+        break;
+
     case GOPHER_PLUS_MOVIE:
-       strcat(tempMIME, "Content-Type: video/mpeg\r\n");
-       break;
+        memBufPrintf(&mb, "Content-Type: video/mpeg\r\n");
+        break;
+
     case GOPHER_MACBINHEX:
+
     case GOPHER_DOSBIN:
+
     case GOPHER_UUENCODED:
+
     case GOPHER_BIN:
-       /* Rightnow We have no idea what it is. */
-       gopher_mime_content(tempMIME, gopherState->request, def_gopher_bin);
-       break;
+        /* Rightnow We have no idea what it is. */
+        gopher_mime_content(&mb, gopherState->request, def_gopher_bin);
+        break;
+
     case GOPHER_FILE:
+
     default:
-       gopher_mime_content(tempMIME, gopherState->request, def_gopher_text);
-       break;
+        gopher_mime_content(&mb, gopherState->request, def_gopher_text);
+        break;
     }
-    strcat(tempMIME, "\r\n");
-    storeAppend(gopherState->entry, tempMIME, strlen(tempMIME));
+
+    memBufPrintf(&mb, "\r\n");
+    EBIT_CLR(gopherState->entry->flags, ENTRY_FWD_HDR_WAIT);
+    storeAppend(gopherState->entry, mb.buf, mb.size);
+    memBufClean(&mb);
 }
 
-/* Parse a gopher url into components.  By Anawat. */
-static int
-gopher_url_parser(const char *url, char *host, int *port, char *type_id, char *request)
+/* Parse a gopher request into components.  By Anawat. */
+static void
+gopher_request_parse(const request_t * req, char *type_id, char *request)
 {
-    LOCAL_ARRAY(char, proto, MAX_URL);
-    LOCAL_ARRAY(char, hostbuf, MAX_URL);
-    int t;
-
-    proto[0] = hostbuf[0] = '\0';
-    host[0] = request[0] = '\0';
-    (*port) = 0;
-    (*type_id) = 0;
-
-    t = sscanf(url, "%[a-zA-Z]://%[^/]/%c%s", proto, hostbuf,
-       type_id, request);
-    if ((t < 2) || strcasecmp(proto, "gopher")) {
-       return -1;
-    } else if (t == 2) {
-       (*type_id) = GOPHER_DIRECTORY;
-       request[0] = '\0';
-    } else if (t == 3) {
-       request[0] = '\0';
-    } else {
-       /* convert %xx to char */
-       url_convert_hex(request, 0);
+    const char *path = req->urlpath.buf();
+
+    if (request)
+        request[0] = '\0';
+
+    if (path && (*path == '/'))
+        path++;
+
+    if (!path || !*path) {
+        *type_id = GOPHER_DIRECTORY;
+        return;
     }
 
-    host[0] = '\0';
-    if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2)
-       (*port) = GOPHER_PORT;
+    *type_id = path[0];
 
-    return 0;
+    if (request) {
+        xstrncpy(request, path + 1, MAX_URL);
+        /* convert %xx to char */
+        url_convert_hex(request, 0);
+    }
 }
 
 int
-gopherCachable(const char *url)
+gopherCachable(const request_t * req)
 {
-    GopherStateData *gopherState = NULL;
     int cachable = 1;
-    /* use as temp data structure to parse gopher URL */
-    gopherState = CreateGopherStateData();
+    char type_id;
     /* parse to see type */
-    gopher_url_parser(url,
-       gopherState->host,
-       &gopherState->port,
-       &gopherState->type_id,
-       gopherState->request);
-    switch (gopherState->type_id) {
+    gopher_request_parse(req,
+                         &type_id,
+                         NULL);
+
+    switch (type_id) {
+
     case GOPHER_INDEX:
+
     case GOPHER_CSO:
+
     case GOPHER_TELNET:
+
     case GOPHER_3270:
-       cachable = 0;
-       break;
+        cachable = 0;
+        break;
+
     default:
-       cachable = 1;
+        cachable = 1;
     }
-    gopherStateFree(-1, gopherState);
+
     return cachable;
 }
 
+static void
+gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring)
+{
+    storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
+    storeAppendPrintf(e, "<HTML><HEAD><TITLE>");
+    storeAppendPrintf(e, title, substring);
+    storeAppendPrintf(e, "</TITLE>");
+    storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
+    storeAppendPrintf(e, "</HEAD>\n<BODY><H1>");
+    storeAppendPrintf(e, title, substring);
+    storeAppendPrintf(e, "</H1>\n");
+}
+
+static void
+gopherHTMLFooter(StoreEntry * e)
+{
+    storeAppendPrintf(e, "<HR noshade size=\"1px\">\n");
+    storeAppendPrintf(e, "<ADDRESS>\n");
+    storeAppendPrintf(e, "Generated %s by %s (%s)\n",
+                      mkrfc1123(squid_curtime),
+                      getMyHostname(),
+                      full_appname_string);
+    storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
+}
+
 static void
 gopherEndHTML(GopherStateData * gopherState)
 {
-    LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
+    StoreEntry *e = gopherState->entry;
 
     if (!gopherState->data_in) {
-       snprintf(tmpbuf, TEMP_BUF_SIZE,
-           "<HTML><HEAD><TITLE>Server Return Nothing.</TITLE>\n"
-           "</HEAD><BODY><HR><H1>Server Return Nothing.</H1></BODY></HTML>\n");
-       storeAppend(gopherState->entry, tmpbuf, strlen(tmpbuf));
-       return;
+        gopherHTMLHeader(e, "Server Return Nothing", NULL);
+        storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>");
+    } else {
+        storeAppendPrintf(e, "</PRE>\n");
     }
+
+    gopherHTMLFooter(e);
 }
 
 
@@ -350,647 +329,672 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
     char *tline = NULL;
     LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
     LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
-    LOCAL_ARRAY(char, outbuf, TEMP_BUF_SIZE << 4);
     char *name = NULL;
     char *selector = NULL;
     char *host = NULL;
     char *port = NULL;
     char *escaped_selector = NULL;
-    char *icon_type = NULL;
+    const char *icon_url = NULL;
     char gtype;
     StoreEntry *entry = NULL;
 
-    memset(outbuf, '\0', TEMP_BUF_SIZE << 4);
     memset(tmpbuf, '\0', TEMP_BUF_SIZE);
     memset(line, '\0', TEMP_BUF_SIZE);
 
     entry = gopherState->entry;
 
-    if (gopherState->conversion == HTML_INDEX_PAGE) {
-       snprintf(outbuf, TEMP_BUF_SIZE << 4,
-           "<HTML><HEAD><TITLE>Gopher Index %s</TITLE></HEAD>\n"
-           "<BODY><H1>%s<BR>Gopher Search</H1>\n"
-           "<p>This is a searchable Gopher index. Use the search\n"
-           "function of your browser to enter search terms.\n"
-           "<ISINDEX></BODY></HTML>\n", entry->url, entry->url);
-       storeAppend(entry, outbuf, strlen(outbuf));
-       /* now let start sending stuff to client */
-       BIT_RESET(entry->flag, DELAY_SENDING);
-       gopherState->data_in = 1;
-
-       return;
+    if (gopherState->conversion == gopher_ds::HTML_INDEX_PAGE) {
+        char *html_url = html_quote(storeUrl(entry));
+        gopherHTMLHeader(entry, "Gopher Index %s", html_url);
+        storeAppendPrintf(entry,
+                          "<p>This is a searchable Gopher index. Use the search\n"
+                          "function of your browser to enter search terms.\n"
+                          "<ISINDEX>\n");
+        gopherHTMLFooter(entry);
+        /* now let start sending stuff to client */
+        storeBufferFlush(entry);
+        gopherState->data_in = 1;
+
+        return;
     }
-    if (gopherState->conversion == HTML_CSO_PAGE) {
-       snprintf(outbuf, TEMP_BUF_SIZE << 4,
-           "<HTML><HEAD><TITLE>CSO Search of %s</TITLE></HEAD>\n"
-           "<BODY><H1>%s<BR>CSO Search</H1>\n"
-           "<P>A CSO database usually contains a phonebook or\n"
-           "directory.  Use the search function of your browser to enter\n"
-           "search terms.</P><ISINDEX></BODY></HTML>\n",
-           entry->url, entry->url);
-
-       storeAppend(entry, outbuf, strlen(outbuf));
-       /* now let start sending stuff to client */
-       BIT_RESET(entry->flag, DELAY_SENDING);
-       gopherState->data_in = 1;
-
-       return;
+
+    if (gopherState->conversion == gopher_ds::HTML_CSO_PAGE) {
+        char *html_url = html_quote(storeUrl(entry));
+        gopherHTMLHeader(entry, "CSO Search of %s", html_url);
+        storeAppendPrintf(entry,
+                          "<P>A CSO database usually contains a phonebook or\n"
+                          "directory.  Use the search function of your browser to enter\n"
+                          "search terms.</P><ISINDEX>\n");
+        gopherHTMLFooter(entry);
+        /* now let start sending stuff to client */
+        storeBufferFlush(entry);
+        gopherState->data_in = 1;
+
+        return;
     }
+
     inbuf[len] = '\0';
+    String outbuf;
 
     if (!gopherState->HTML_header_added) {
-       if (gopherState->conversion == HTML_CSO_RESULT)
-           strcat(outbuf, "<HTML><HEAD><TITLE>CSO Searchs Result</TITLE></HEAD>\n"
-               "<BODY><H1>CSO Searchs Result</H1>\n<PRE>\n");
-       else
-           strcat(outbuf, "<HTML><HEAD><TITLE>Gopher Menu</TITLE></HEAD>\n"
-               "<BODY><H1>Gopher Menu</H1>\n<PRE>\n");
-       gopherState->HTML_header_added = 1;
+        if (gopherState->conversion == gopher_ds::HTML_CSO_RESULT)
+            gopherHTMLHeader(entry, "CSO Search Result", NULL);
+        else
+            gopherHTMLHeader(entry, "Gopher Menu", NULL);
+
+        outbuf.append ("<PRE>");
+
+        gopherState->HTML_header_added = 1;
     }
+
     while ((pos != NULL) && (pos < inbuf + len)) {
 
-       if (gopherState->len != 0) {
-           /* there is something left from last tx. */
-           xstrncpy(line, gopherState->buf, gopherState->len);
-           lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len);
-           if (lpos)
-               *lpos = '\0';
-           else {
-               /* there is no complete line in inbuf */
-               /* copy it to temp buffer */
-               if (gopherState->len + len > TEMP_BUF_SIZE) {
-                   debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
-                       entry->url);
-                   len = TEMP_BUF_SIZE - gopherState->len;
-               }
-               xmemcpy(gopherState->buf + gopherState->len, inbuf, len);
-               gopherState->len += len;
-               return;
-           }
-
-           /* skip one line */
-           pos = (char *) memchr(pos, '\n', 256);
-           if (pos)
-               pos++;
-
-           /* we're done with the remain from last tx. */
-           gopherState->len = 0;
-           *(gopherState->buf) = '\0';
-       } else {
-
-           lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
-           if (lpos)
-               *lpos = '\0';
-           else {
-               /* there is no complete line in inbuf */
-               /* copy it to temp buffer */
-               if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
-                   debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
-                       entry->url);
-                   len = TEMP_BUF_SIZE;
-               }
-               if (len > (pos - inbuf)) {
-                   xmemcpy(gopherState->buf, pos, len - (pos - inbuf));
-                   gopherState->len = len - (pos - inbuf);
-               }
-               break;
-           }
-
-           /* skip one line */
-           pos = (char *) memchr(pos, '\n', 256);
-           if (pos)
-               pos++;
-
-       }
-
-       /* at this point. We should have one line in buffer to process */
-
-       if (*line == '.') {
-           /* skip it */
-           memset(line, '\0', TEMP_BUF_SIZE);
-           continue;
-       }
-       switch (gopherState->conversion) {
-
-       case HTML_INDEX_RESULT:
-       case HTML_DIR:{
-               tline = line;
-               gtype = *tline++;
-               name = tline;
-               selector = strchr(tline, TAB);
-               if (selector) {
-                   *selector++ = '\0';
-                   host = strchr(selector, TAB);
-                   if (host) {
-                       *host++ = '\0';
-                       port = strchr(host, TAB);
-                       if (port) {
-                           char *junk;
-                           port[0] = ':';
-                           junk = strchr(host, TAB);
-                           if (junk)
-                               *junk++ = 0;    /* Chop port */
-                           else {
-                               junk = strchr(host, '\r');
-                               if (junk)
-                                   *junk++ = 0;        /* Chop port */
-                               else {
-                                   junk = strchr(host, '\n');
-                                   if (junk)
-                                       *junk++ = 0;    /* Chop port */
-                               }
-                           }
-                           if ((port[1] == '0') && (!port[2]))
-                               port[0] = 0;    /* 0 means none */
-                       }
-                       /* escape a selector here */
-                       escaped_selector = url_escape(selector);
-
-                       switch (gtype) {
-                       case GOPHER_DIRECTORY:
-                           icon_type = "internal-gopher-menu";
-                           break;
-                       case GOPHER_FILE:
-                           icon_type = "internal-gopher-text";
-                           break;
-                       case GOPHER_INDEX:
-                       case GOPHER_CSO:
-                           icon_type = "internal-gopher-index";
-                           break;
-                       case GOPHER_IMAGE:
-                       case GOPHER_GIF:
-                       case GOPHER_PLUS_IMAGE:
-                           icon_type = "internal-gopher-image";
-                           break;
-                       case GOPHER_SOUND:
-                       case GOPHER_PLUS_SOUND:
-                           icon_type = "internal-gopher-sound";
-                           break;
-                       case GOPHER_PLUS_MOVIE:
-                           icon_type = "internal-gopher-movie";
-                           break;
-                       case GOPHER_TELNET:
-                       case GOPHER_3270:
-                           icon_type = "internal-gopher-telnet";
-                           break;
-                       case GOPHER_BIN:
-                       case GOPHER_MACBINHEX:
-                       case GOPHER_DOSBIN:
-                       case GOPHER_UUENCODED:
-                           icon_type = "internal-gopher-binary";
-                           break;
-                       default:
-                           icon_type = "internal-gopher-unknown";
-                           break;
-                       }
-
-
-                       memset(tmpbuf, '\0', TEMP_BUF_SIZE);
-                       if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
-                           if (strlen(escaped_selector) != 0)
-                               snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n",
-                                   icon_type, escaped_selector, host, name);
-                           else
-                               snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n",
-                                   icon_type, host, name);
-
-                       } else {
-                           snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
-                               icon_type, host, gtype, escaped_selector, name);
-                       }
-                       safe_free(escaped_selector);
-                       strcat(outbuf, tmpbuf);
-                       gopherState->data_in = 1;
-                   } else {
-                       memset(line, '\0', TEMP_BUF_SIZE);
-                       continue;
-                   }
-               } else {
-                   memset(line, '\0', TEMP_BUF_SIZE);
-                   continue;
-               }
-               break;
-           }                   /* HTML_DIR, HTML_INDEX_RESULT */
-
-
-       case HTML_CSO_RESULT:{
-               int t;
-               int code;
-               int recno;
-               LOCAL_ARRAY(char, result, MAX_CSO_RESULT);
-
-               tline = line;
-
-               if (tline[0] == '-') {
-                   t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result);
-                   if (t < 3)
-                       break;
-
-                   if (code != 200)
-                       break;
-
-                   if (gopherState->cso_recno != recno) {
-                       snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result);
-                       gopherState->cso_recno = recno;
-                   } else {
-                       snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", result);
-                   }
-                   strcat(outbuf, tmpbuf);
-                   gopherState->data_in = 1;
-                   break;
-               } else {
-                   /* handle some error codes */
-                   t = sscanf(tline, "%d:%[^\n]", &code, result);
-
-                   if (t < 2)
-                       break;
-
-                   switch (code) {
-
-                   case 200:{
-                           /* OK */
-                           /* Do nothing here */
-                           break;
-                       }
-
-                   case 102:   /* Number of matches */
-                   case 501:   /* No Match */
-                   case 502:   /* Too Many Matches */
-                       {
-                           /* Print the message the server returns */
-                           snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR><H2>%s</H2>\n<PRE>", result);
-                           strcat(outbuf, tmpbuf);
-                           gopherState->data_in = 1;
-                           break;
-                       }
-
-
-                   }
-               }
-
-           }                   /* HTML_CSO_RESULT */
-       default:
-           break;              /* do nothing */
-
-       }                       /* switch */
+        if (gopherState->len != 0) {
+            /* there is something left from last tx. */
+            xstrncpy(line, gopherState->buf, gopherState->len + 1);
+            lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len);
+
+            if (lpos)
+                *lpos = '\0';
+            else {
+                /* there is no complete line in inbuf */
+                /* copy it to temp buffer */
+
+                if (gopherState->len + len > TEMP_BUF_SIZE) {
+                    debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
+                                  storeUrl(entry));
+                    len = TEMP_BUF_SIZE - gopherState->len;
+                }
+
+                xmemcpy(gopherState->buf + gopherState->len, inbuf, len);
+                gopherState->len += len;
+                return;
+            }
+
+            /* skip one line */
+            pos = (char *) memchr(pos, '\n', len);
+
+            if (pos)
+                pos++;
+
+            /* we're done with the remain from last tx. */
+            gopherState->len = 0;
+
+            *(gopherState->buf) = '\0';
+        } else {
+
+            lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
+
+            if (lpos)
+                *lpos = '\0';
+            else {
+                /* there is no complete line in inbuf */
+                /* copy it to temp buffer */
+
+                if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
+                    debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
+                                  storeUrl(entry));
+                    len = TEMP_BUF_SIZE;
+                }
+
+                if (len > (pos - inbuf)) {
+                    xmemcpy(gopherState->buf, pos, len - (pos - inbuf));
+                    gopherState->len = len - (pos - inbuf);
+                }
+
+                break;
+            }
+
+            /* skip one line */
+            pos = (char *) memchr(pos, '\n', len);
+
+            if (pos)
+                pos++;
+
+        }
+
+        /* at this point. We should have one line in buffer to process */
+
+        if (*line == '.') {
+            /* skip it */
+            memset(line, '\0', TEMP_BUF_SIZE);
+            continue;
+        }
+
+        switch (gopherState->conversion) {
+
+        case gopher_ds::HTML_INDEX_RESULT:
+
+        case gopher_ds::HTML_DIR: {
+                tline = line;
+                gtype = *tline++;
+                name = tline;
+                selector = strchr(tline, TAB);
+
+                if (selector) {
+                    *selector++ = '\0';
+                    host = strchr(selector, TAB);
+
+                    if (host) {
+                        *host++ = '\0';
+                        port = strchr(host, TAB);
+
+                        if (port) {
+                            char *junk;
+                            port[0] = ':';
+                            junk = strchr(host, TAB);
+
+                            if (junk)
+                                *junk++ = 0;   /* Chop port */
+                            else {
+                                junk = strchr(host, '\r');
+
+                                if (junk)
+                                    *junk++ = 0;       /* Chop port */
+                                else {
+                                    junk = strchr(host, '\n');
+
+                                    if (junk)
+                                        *junk++ = 0;   /* Chop port */
+                                }
+                            }
+
+                            if ((port[1] == '0') && (!port[2]))
+                                port[0] = 0;   /* 0 means none */
+                        }
+
+                        /* escape a selector here */
+                        escaped_selector = xstrdup(rfc1738_escape_part(selector));
+
+                        switch (gtype) {
+
+                        case GOPHER_DIRECTORY:
+                            icon_url = mimeGetIconURL("internal-menu");
+                            break;
+
+                        case GOPHER_HTML:
+
+                        case GOPHER_FILE:
+                            icon_url = mimeGetIconURL("internal-text");
+                            break;
+
+                        case GOPHER_INDEX:
+
+                        case GOPHER_CSO:
+                            icon_url = mimeGetIconURL("internal-index");
+                            break;
+
+                        case GOPHER_IMAGE:
+
+                        case GOPHER_GIF:
+
+                        case GOPHER_PLUS_IMAGE:
+                            icon_url = mimeGetIconURL("internal-image");
+                            break;
+
+                        case GOPHER_SOUND:
+
+                        case GOPHER_PLUS_SOUND:
+                            icon_url = mimeGetIconURL("internal-sound");
+                            break;
+
+                        case GOPHER_PLUS_MOVIE:
+                            icon_url = mimeGetIconURL("internal-movie");
+                            break;
+
+                        case GOPHER_TELNET:
+
+                        case GOPHER_3270:
+                            icon_url = mimeGetIconURL("internal-telnet");
+                            break;
+
+                        case GOPHER_BIN:
+
+                        case GOPHER_MACBINHEX:
+
+                        case GOPHER_DOSBIN:
+
+                        case GOPHER_UUENCODED:
+                            icon_url = mimeGetIconURL("internal-binary");
+                            break;
+
+                        case GOPHER_INFO:
+                            icon_url = NULL;
+                            break;
+
+                        default:
+                            icon_url = mimeGetIconURL("internal-unknown");
+                            break;
+                        }
+
+                        memset(tmpbuf, '\0', TEMP_BUF_SIZE);
+
+                        if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
+                            if (strlen(escaped_selector) != 0)
+                                snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
+                                         icon_url, escaped_selector, rfc1738_escape_part(host),
+                                         *port ? ":" : "", port, html_quote(name));
+                            else
+                                snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
+                                         icon_url, rfc1738_escape_part(host), *port ? ":" : "",
+                                         port, html_quote(name));
+
+                        } else if (gtype == GOPHER_INFO) {
+                            snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
+                        } else {
+                            if (strncmp(selector, "GET /", 5) == 0) {
+                                /* WWW link */
+                                snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
+                                         icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name));
+                            } else {
+                                /* Standard link */
+                                snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
+                                         icon_url, host, gtype, escaped_selector, html_quote(name));
+                            }
+                        }
+
+                        safe_free(escaped_selector);
+                        outbuf.append(tmpbuf);
+                        gopherState->data_in = 1;
+                    } else {
+                        memset(line, '\0', TEMP_BUF_SIZE);
+                        continue;
+                    }
+                } else {
+                    memset(line, '\0', TEMP_BUF_SIZE);
+                    continue;
+                }
+
+                break;
+            }                  /* HTML_DIR, HTML_INDEX_RESULT */
+
+
+        case gopher_ds::HTML_CSO_RESULT: {
+                if (line[0] == '-') {
+                    int code, recno;
+                    char *s_code, *s_recno, *result;
+
+                    s_code = strtok(line + 1, ":\n");
+                    s_recno = strtok(NULL, ":\n");
+                    result = strtok(NULL, "\n");
+
+                    if (!result)
+                        break;
+
+                    code = atoi(s_code);
+
+                    recno = atoi(s_recno);
+
+                    if (code != 200)
+                        break;
+
+                    if (gopherState->cso_recno != recno) {
+                        snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
+                        gopherState->cso_recno = recno;
+                    } else {
+                        snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
+                    }
+
+                    outbuf.append(tmpbuf);
+                    gopherState->data_in = 1;
+                    break;
+                } else {
+                    int code;
+                    char *s_code, *result;
+
+                    s_code = strtok(line, ":");
+                    result = strtok(NULL, "\n");
+
+                    if (!result)
+                        break;
+
+                    code = atoi(s_code);
+
+                    switch (code) {
+
+                    case 200: {
+                            /* OK */
+                            /* Do nothing here */
+                            break;
+                        }
+
+                    case 102:  /* Number of matches */
+
+                    case 501:  /* No Match */
+
+                    case 502:  /* Too Many Matches */
+                        {
+                            /* Print the message the server returns */
+                            snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
+                            outbuf.append(tmpbuf);
+                            gopherState->data_in = 1;
+                            break;
+                        }
+
+
+                    }
+                }
+
+            }                  /* HTML_CSO_RESULT */
+
+        default:
+            break;             /* do nothing */
+
+        }                      /* switch */
 
     }                          /* while loop */
 
-    if ((int) strlen(outbuf) > 0) {
-       storeAppend(entry, outbuf, strlen(outbuf));
-       /* now let start sending stuff to client */
-       BIT_RESET(entry->flag, DELAY_SENDING);
+    if (outbuf.size() > 0) {
+        storeAppend(entry, outbuf.buf(), outbuf.size());
+        /* now let start sending stuff to client */
+        storeBufferFlush(entry);
     }
+
+    outbuf.clean();
     return;
 }
 
 static void
 gopherTimeout(int fd, void *data)
 {
-    GopherStateData *gopherState = data;
+    GopherStateData *gopherState = (GopherStateData *)data;
     StoreEntry *entry = gopherState->entry;
-    ErrorState *err;
-
-    debug(10, 4) ("gopherTimeout: FD %d: '%s'\n", fd, entry->url);
-    /* was assert */
-    if (entry->mem_obj->inmem_hi == 0) {
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_READ_TIMEOUT;
-       err->http_status = HTTP_GATEWAY_TIMEOUT;
-       err->url = gopherState->request;
-       errorAppendEntry(entry, err);
+    debug(10, 4) ("gopherTimeout: FD %d: '%s'\n", fd, storeUrl(entry));
+
+    if (entry->store_status == STORE_PENDING) {
+        if (entry->isEmpty()) {
+            fwdFail(gopherState->fwdState,
+                    errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT));
+        }
     }
-    storeAbort(entry, 0);
+
     comm_close(fd);
 }
 
 /* This will be called when data is ready to be read from fd.  Read until
  * error or connection closed. */
 static void
-gopherReadReply(int fd, void *data)
+gopherReadReply(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
 {
-    GopherStateData *gopherState = data;
+    GopherStateData *gopherState = (GopherStateData *)data;
     StoreEntry *entry = gopherState->entry;
-    char *buf = NULL;
-    int len;
     int clen;
-    int off;
     int bin;
-    if (protoAbortFetch(entry)) {
-       storeAbort(entry, 0);
-       comm_close(fd);
-       return;
+    size_t read_sz = BUFSIZ;
+    int do_next_read = 0;
+#if DELAY_POOLS
+
+    DelayId delayId = entry->mem_obj->mostBytesAllowed();
+#endif
+
+    /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
+
+    if (flag == COMM_ERR_CLOSING) {
+        return;
     }
-    /* check if we want to defer reading */
-    clen = entry->mem_obj->inmem_hi;
-    off = storeLowestMemReaderOffset(entry);
-    if ((clen - off) > READ_AHEAD_GAP) {
-       IOStats.Gopher.reads_deferred++;
-       debug(10, 3) ("gopherReadReply: Read deferred for Object: %s\n",
-           entry->url);
-       debug(10, 3) ("                Current Gap: %d bytes\n", clen - off);
-       /* reschedule, so it will automatically reactivated when
-        * Gap is big enough.  */
-       commSetSelect(fd,
-           COMM_SELECT_READ,
-           gopherReadReply,
-           data, 0);
-       /* don't install read timeout until we are below the GAP */
-       if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
-           commSetTimeout(fd, Config.Timeout.defer, NULL, NULL);
-           BIT_SET(entry->flag, READ_DEFERRED);
-       }
-       /* dont try reading again for a while */
-       comm_set_stall(fd, 1);
-       return;
-    } else {
-       BIT_RESET(entry->flag, READ_DEFERRED);
+
+    assert(buf == gopherState->replybuf);
+
+    if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+        comm_close(fd);
+        return;
     }
-    buf = get_free_4k_page();
+
     errno = 0;
+#if DELAY_POOLS
+
+    read_sz = delayId.bytesWanted(1, read_sz);
+#endif
+
     /* leave one space for \0 in gopherToHTML */
-    len = read(fd, buf, TEMP_BUF_SIZE - 1);
-    fd_bytes(fd, len, FD_READ);
-    debug(10, 5) ("gopherReadReply: FD %d read len=%d\n", fd, len);
-    if (len > 0) {
-       commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
-       IOStats.Gopher.reads++;
-       for (clen = len - 1, bin = 0; clen; bin++)
-           clen >>= 1;
-       IOStats.Gopher.read_hist[bin]++;
+
+    if (flag == COMM_OK && len > 0) {
+#if DELAY_POOLS
+        delayId.bytesIn(len);
+#endif
+
+        kb_incr(&statCounter.server.all.kbytes_in, len);
+        kb_incr(&statCounter.server.other.kbytes_in, len);
     }
-    if (len < 0) {
-       debug(50, 1) ("gopherReadReply: error reading: %s\n", xstrerror());
-       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
-           /* reinstall handlers */
-           /* XXX This may loop forever */
-           commSetSelect(fd,
-               COMM_SELECT_READ,
-               gopherReadReply,
-               data, 0);
-       } else {
-           /* was  assert */
-           ErrorState *err;
-           err = xcalloc(1, sizeof(ErrorState));
-           err->type = ERR_READ_ERROR;
-           err->errno = errno;
-           err->http_status = HTTP_INTERNAL_SERVER_ERROR;
-           err->url = entry->url;
-           errorAppendEntry(entry, err);
-           storeAbort(entry, 0);
-           comm_close(fd);
-       }
-    } else if (len == 0 && entry->mem_obj->inmem_hi == 0) {
-       /* was assert */
-       ErrorState *err;
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_ZERO_SIZE_OBJECT;
-       err->errno = errno;
-       err->http_status = HTTP_SERVICE_UNAVAILABLE;
-       err->url = gopherState->request;
-       errorAppendEntry(entry, err);
-       storeAbort(entry, 0);
-       comm_close(fd);
+
+    debug(10, 5) ("gopherReadReply: FD %d read len=%d\n", fd, (int)len);
+
+    if (flag == COMM_OK && len > 0) {
+        commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
+        IOStats.Gopher.reads++;
+
+        for (clen = len - 1, bin = 0; clen; bin++)
+            clen >>= 1;
+
+        IOStats.Gopher.read_hist[bin]++;
+    }
+
+    if (flag != COMM_OK || len < 0) {
+        debug(50, 1) ("gopherReadReply: error reading: %s\n", xstrerror());
+
+        if (ignoreErrno(errno)) {
+            do_next_read = 1;
+        } else if (entry->isEmpty()) {
+            ErrorState *err;
+            err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
+            err->xerrno = errno;
+            err->url = xstrdup(storeUrl(entry));
+            errorAppendEntry(entry, err);
+            comm_close(fd);
+            do_next_read = 0;
+        } else {
+            comm_close(fd);
+            do_next_read = 0;
+        }
+    } else if (len == 0 && entry->isEmpty()) {
+        ErrorState *err;
+        err = errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE);
+        err->xerrno = errno;
+        err->url = xstrdup(gopherState->request);
+        errorAppendEntry(entry, err);
+        comm_close(fd);
+        do_next_read = 0;
     } else if (len == 0) {
-       /* Connection closed; retrieval done. */
-       /* flush the rest of data in temp buf if there is one. */
-       if (gopherState->conversion != NORMAL)
-           gopherEndHTML(data);
-       storeTimestampsSet(entry);
-       BIT_RESET(entry->flag, DELAY_SENDING);
-       storeComplete(entry);
-       comm_close(fd);
+        /* Connection closed; retrieval done. */
+        /* flush the rest of data in temp buf if there is one. */
+
+        if (gopherState->conversion != gopher_ds::NORMAL)
+            gopherEndHTML(gopherState);
+
+        storeTimestampsSet(entry);
+
+        storeBufferFlush(entry);
+
+        fwdComplete(gopherState->fwdState);
+
+        comm_close(fd);
+
+        do_next_read = 0;
     } else {
-       if (gopherState->conversion != NORMAL) {
-           gopherToHTML(data, buf, len);
-       } else {
-           storeAppend(entry, buf, len);
-       }
-       commSetSelect(fd,
-           COMM_SELECT_READ,
-           gopherReadReply,
-           data, 0);
+        if (gopherState->conversion != gopher_ds::NORMAL) {
+            gopherToHTML(gopherState, buf, len);
+        } else {
+            storeAppend(entry, buf, len);
+        }
+
+        do_next_read = 1;
     }
-    put_free_4k_page(buf);
+
+    if (do_next_read)
+        comm_read(fd, buf, read_sz, gopherReadReply, gopherState);
+
     return;
 }
 
 /* This will be called when request write is complete. Schedule read of
  * reply. */
 static void
-gopherSendComplete(int fd, char *buf, int size, int errflag, void *data)
+gopherSendComplete(int fd, char *buf, size_t size, comm_err_t errflag, int xerrno, void *data)
 {
     GopherStateData *gopherState = (GopherStateData *) data;
-    StoreEntry *entry = NULL;
-    entry = gopherState->entry;
+    StoreEntry *entry = gopherState->entry;
     debug(10, 5) ("gopherSendComplete: FD %d size: %d errflag: %d\n",
-       fd, size, errflag);
+                  fd, (int) size, errflag);
+
+    if (size > 0) {
+        fd_bytes(fd, size, FD_WRITE);
+        kb_incr(&statCounter.server.all.kbytes_out, size);
+        kb_incr(&statCounter.server.other.kbytes_out, size);
+    }
+
     if (errflag) {
-       /* was assert */
-       ErrorState *err;
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_CONNECT_FAIL;
-       err->errno = errno;
-       err->host = xstrdup(gopherState->host);
-       err->port = gopherState->port;
-       err->http_status = HTTP_SERVICE_UNAVAILABLE;
-       err->url = entry->url;
-       errorAppendEntry(entry, err);
-
-       storeAbort(entry, 0);
-       comm_close(fd);
-       if (buf)
-           put_free_4k_page(buf);      /* Allocated by gopherSendRequest. */
-       return;
+        ErrorState *err;
+        err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
+        err->xerrno = errno;
+        err->host = xstrdup(gopherState->req->host);
+        err->port = gopherState->req->port;
+        err->url = xstrdup(storeUrl(entry));
+        errorAppendEntry(entry, err);
+        comm_close(fd);
+
+        if (buf)
+            memFree(buf, MEM_4K_BUF);  /* Allocated by gopherSendRequest. */
+
+        return;
     }
-    /* 
+
+    /*
      * OK. We successfully reach remote site.  Start MIME typing
      * stuff.  Do it anyway even though request is not HTML type.
      */
     gopherMimeCreate(gopherState);
+
     switch (gopherState->type_id) {
+
     case GOPHER_DIRECTORY:
-       /* we got to convert it first */
-       BIT_SET(entry->flag, DELAY_SENDING);
-       gopherState->conversion = HTML_DIR;
-       gopherState->HTML_header_added = 0;
-       break;
+        /* we got to convert it first */
+        storeBuffer(entry);
+        gopherState->conversion = gopher_ds::HTML_DIR;
+        gopherState->HTML_header_added = 0;
+        break;
+
     case GOPHER_INDEX:
-       /* we got to convert it first */
-       BIT_SET(entry->flag, DELAY_SENDING);
-       gopherState->conversion = HTML_INDEX_RESULT;
-       gopherState->HTML_header_added = 0;
-       break;
+        /* we got to convert it first */
+        storeBuffer(entry);
+        gopherState->conversion = gopher_ds::HTML_INDEX_RESULT;
+        gopherState->HTML_header_added = 0;
+        break;
+
     case GOPHER_CSO:
-       /* we got to convert it first */
-       BIT_SET(entry->flag, DELAY_SENDING);
-       gopherState->conversion = HTML_CSO_RESULT;
-       gopherState->cso_recno = 0;
-       gopherState->HTML_header_added = 0;
-       break;
+        /* we got to convert it first */
+        storeBuffer(entry);
+        gopherState->conversion = gopher_ds::HTML_CSO_RESULT;
+        gopherState->cso_recno = 0;
+        gopherState->HTML_header_added = 0;
+        break;
+
     default:
-       gopherState->conversion = NORMAL;
+        gopherState->conversion = gopher_ds::NORMAL;
     }
+
     /* Schedule read reply. */
-    commSetSelect(fd, COMM_SELECT_READ, gopherReadReply, gopherState, 0);
+    entry->delayAwareRead(fd, gopherState->replybuf, BUFSIZ, gopherReadReply, gopherState);
+
     if (buf)
-       put_free_4k_page(buf);  /* Allocated by gopherSendRequest. */
+        memFree(buf, MEM_4K_BUF);      /* Allocated by gopherSendRequest. */
 }
 
 /* This will be called when connect completes. Write request. */
 static void
 gopherSendRequest(int fd, void *data)
 {
-    GopherStateData *gopherState = data;
-    LOCAL_ARRAY(char, query, MAX_URL);
-    char *buf = get_free_4k_page();
-    char *t;
+    GopherStateData *gopherState = (GopherStateData *)data;
+    char *buf = (char *)memAllocate(MEM_4K_BUF);
+
     if (gopherState->type_id == GOPHER_CSO) {
-       sscanf(gopherState->request, "?%s", query);
-       snprintf(buf, 4096, "query %s\r\nquit\r\n", query);
+        const char *t = strchr(gopherState->request, '?');
+
+        if (t != NULL)
+            t++;               /* skip the ? */
+        else
+            t = "";
+
+        snprintf(buf, 4096, "query %s\r\nquit\r\n", t);
     } else if (gopherState->type_id == GOPHER_INDEX) {
-       if ((t = strchr(gopherState->request, '?')))
-           *t = '\t';
-       snprintf(buf, 4096, "%s\r\n", gopherState->request);
+        char *t = strchr(gopherState->request, '?');
+
+        if (t != NULL)
+            *t = '\t';
+
+        snprintf(buf, 4096, "%s\r\n", gopherState->request);
     } else {
-       snprintf(buf, 4096, "%s\r\n", gopherState->request);
+        snprintf(buf, 4096, "%s\r\n", gopherState->request);
     }
+
     debug(10, 5) ("gopherSendRequest: FD %d\n", fd);
     comm_write(fd,
-       buf,
-       strlen(buf),
-       gopherSendComplete,
-       data,
-       put_free_4k_page);
-    if (BIT_TEST(gopherState->entry->flag, ENTRY_CACHABLE))
-       storeSetPublicKey(gopherState->entry);  /* Make it public */
+               buf,
+               strlen(buf),
+               gopherSendComplete,
+               gopherState);
+
+    if (EBIT_TEST(gopherState->entry->flags, ENTRY_CACHABLE))
+        storeSetPublicKey(gopherState->entry); /* Make it public */
 }
 
+CBDATA_TYPE(GopherStateData);
+
 void
-gopherStart(StoreEntry * entry)
+gopherStart(FwdState * fwdState)
 {
-    char *url = entry->url;
-    GopherStateData *gopherState = CreateGopherStateData();
-    ErrorState *err;
-    int fd;
+    int fd = fwdState->server_fd;
+    StoreEntry *entry = fwdState->entry;
+    GopherStateData *gopherState;
+    CBDATA_INIT_TYPE(GopherStateData);
+    gopherState = cbdataAlloc(GopherStateData);
+    gopherState->buf = (char *)memAllocate(MEM_4K_BUF);
     storeLockObject(entry);
     gopherState->entry = entry;
-    debug(10, 3) ("gopherStart: url: %s\n", url);
+    gopherState->fwdState = fwdState;
+    debug(10, 3) ("gopherStart: %s\n", storeUrl(entry));
+    statCounter.server.all.requests++;
+    statCounter.server.other.requests++;
     /* Parse url. */
-    if (gopher_url_parser(url, gopherState->host, &gopherState->port,
-           &gopherState->type_id, gopherState->request)) {
-       /* was assert */
-       ErrorState *err;
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_INVALID_URL;
-       err->http_status = HTTP_BAD_REQUEST;
-       err->url = url;
-       errorAppendEntry(entry, err);
-       storeAbort(entry, 0);
-       gopherStateFree(-1, gopherState);
-       return;
+    gopher_request_parse(fwdState->request,
+                         &gopherState->type_id, gopherState->request);
+#if OLD_PARSE_ERROR_CODE
+
+    if (...) {
+        ErrorState *err;
+        err = errorCon(ERR_INVALID_URL, HTTP_BAD_REQUEST);
+        err->url = xstrdup(storeUrl(entry));
+        errorAppendEntry(entry, err);
+        gopherStateFree(-1, gopherState);
+        return;
     }
-    /* Create socket. */
-    fd = comm_open(SOCK_STREAM,
-       0,
-       Config.Addrs.tcp_outgoing,
-       0,
-       COMM_NONBLOCKING,
-       url);
-    if (fd == COMM_ERROR) {
-       debug(10, 4) ("gopherStart: Failed because we're out of sockets.\n");
-       /* was assert */
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_SOCKET_FAILURE;
-       err->errno = errno;
-       err->http_status = HTTP_INTERNAL_SERVER_ERROR;
-       if (entry && entry->url)
-           err->url = entry->url;
-       errorAppendEntry(entry, err);
-       storeAbort(entry, 0);
-       gopherStateFree(-1, gopherState);
-       return;
-    }
-    comm_add_close_handler(fd, gopherStateFree, gopherState);
-    storeRegisterAbort(entry, gopherAbort, gopherState);
-    if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
-       && (strchr(gopherState->request, '?') == NULL)) {
-       /* Index URL without query word */
-       /* We have to generate search page back to client. No need for connection */
-       gopherMimeCreate(gopherState);
-       if (gopherState->type_id == GOPHER_INDEX) {
-           gopherState->conversion = HTML_INDEX_PAGE;
-       } else {
-           if (gopherState->type_id == GOPHER_CSO) {
-               gopherState->conversion = HTML_CSO_PAGE;
-           } else {
-               gopherState->conversion = HTML_INDEX_PAGE;
-           }
-       }
-       gopherToHTML(gopherState, (char *) NULL, 0);
-       storeComplete(entry);
-       comm_close(fd);
-       return;
-    }
-    commSetTimeout(fd, Config.Timeout.connect, gopherTimeout, gopherState);
-    commConnectStart(fd,
-       gopherState->host,
-       gopherState->port,
-       gopherConnectDone,
-       gopherState);
-    gopherState->fd = fd;
-}
 
-static void
-gopherConnectDone(int fd, int status, void *data)
-{
-    GopherStateData *gopherState = data;
-    StoreEntry *entry = gopherState->entry;
+#endif
+    comm_add_close_handler(fd, gopherStateFree, gopherState);
 
-    ErrorState *err;
-    if (status == COMM_ERR_DNS) {
-       debug(10, 4) ("gopherConnectDone: Unknown host: %s\n", gopherState->host);
-       /* was assert */
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_DNS_FAIL;
-       err->dnsserver_msg = xstrdup(dns_error_message);
-       err->http_status = HTTP_SERVICE_UNAVAILABLE;
-       err->url = entry->url;
-       errorAppendEntry(entry, err);
-       storeAbort(gopherState->entry, 0);
-       comm_close(fd);
-    } else if (status != COMM_OK) {
-       /* was assert */
-       ErrorState *err;
-       err = xcalloc(1, sizeof(ErrorState));
-       err->type = ERR_CONNECT_FAIL;
-       err->http_status = HTTP_SERVICE_UNAVAILABLE;
-       err->errno = errno;
-       err->host = xstrdup(gopherState->host);
-       err->port = gopherState->port;
-       err->url = entry->url;
-       errorAppendEntry(entry, err);
-       storeAbort(gopherState->entry, 0);
-       comm_close(fd);
-    } else {
-       commSetSelect(fd, COMM_SELECT_WRITE, gopherSendRequest, gopherState, 0);
+    if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
+            && (strchr(gopherState->request, '?') == NULL)) {
+        /* Index URL without query word */
+        /* We have to generate search page back to client. No need for connection */
+        gopherMimeCreate(gopherState);
+
+        if (gopherState->type_id == GOPHER_INDEX) {
+            gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
+        } else {
+            if (gopherState->type_id == GOPHER_CSO) {
+                gopherState->conversion = gopher_ds::HTML_CSO_PAGE;
+            } else {
+                gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
+            }
+        }
+
+        gopherToHTML(gopherState, (char *) NULL, 0);
+        fwdComplete(fwdState);
+        comm_close(fd);
+        return;
     }
-}
-
 
-static GopherStateData *
-CreateGopherStateData(void)
-{
-    GopherStateData *gd = xcalloc(1, sizeof(GopherStateData));
-    cbdataAdd(gd);
-    gd->buf = get_free_4k_page();
-    return (gd);
-}
-
-static void
-gopherAbort(void *data)
-{
-    GopherStateData *gopherState = data;
-    debug(10, 1) ("gopherAbort: %s\n", gopherState->entry->url);
-    comm_close(gopherState->fd);
+    gopherState->fd = fd;
+    gopherState->fwdState = fwdState;
+    gopherSendRequest(fd, gopherState);
+    commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState);
 }