]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ftp.cc
SourceLayout: Add Ip namespace for internal libip
[thirdparty/squid.git] / src / ftp.cc
index 54a8520bae06570380612d4904df72dc2bfbfd51..eff4ddc7f6b7562183fa3c7d5b1e21791044951a 100644 (file)
@@ -35,6 +35,7 @@
 #include "squid.h"
 #include "comm.h"
 #include "comm/ListenStateData.h"
+#include "compat/strtoll.h"
 #include "ConnectionDetail.h"
 #include "errorpage.h"
 #include "fde.h"
@@ -45,7 +46,9 @@
 #include "HttpRequest.h"
 #include "HttpReply.h"
 #include "MemBuf.h"
+#include "rfc1738.h"
 #include "Server.h"
+#include "SquidString.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "URLScheme.h"
@@ -116,13 +119,12 @@ struct _ftp_flags {
     bool dir_slash;
     bool root_dir;
     bool no_dotdot;
-    bool html_header_sent;
     bool binary;
     bool try_slash_hack;
     bool put;
     bool put_mkdir;
     bool listformat_unknown;
-    bool listing_started;
+    bool listing;
     bool completed_forwarding;
 };
 
@@ -190,11 +192,12 @@ public:
     int64_t restart_offset;
     char *proxy_host;
     size_t list_width;
-    wordlist *cwd_message;
+    String cwd_message;
     char *old_request;
     char *old_reply;
     char *old_filepath;
     char typecode;
+    MemBuf listing;            ///< FTP directory listing in HTML format.
 
     // \todo: optimize ctrl and data structs member order, to minimize size
     /// FTP control channel info; the channel is opened once per transaction
@@ -231,13 +234,12 @@ public:
     void failed(err_type, int xerrno);
     void failedErrorMessage(err_type, int xerrno);
     void unhack();
-    void listingStart();
-    void listingFinish();
     void scheduleReadControlReply(int);
     void handleControlReply();
     void readStor();
-    char *htmlifyListEntry(const char *line);
     void parseListing();
+    MemBuf *htmlifyListEntry(const char *line);
+    void completedListing(void);
     void dataComplete();
     void dataRead(const CommIoCbParams &io);
     int checkAuth(const HttpHeader * req_hdr);
@@ -518,8 +520,7 @@ FtpStateData::~FtpStateData()
     if (ctrl.message)
         wordlistDestroy(&ctrl.message);
 
-    if (cwd_message)
-        wordlistDestroy(&cwd_message);
+    cwd_message.clean();
 
     safe_free(ctrl.last_reply);
 
@@ -617,115 +618,21 @@ FtpStateData::ftpTimeout(const CommTimeoutCbParams &io)
     /* failed() closes ctrl.fd and frees ftpState */
 }
 
-void
-FtpStateData::listingStart()
-{
-    debugs(9,3, HERE);
-    wordlist *w;
-    char *dirup;
-    int i, j, k;
-    const char *title = title_url.termedBuf();
-    flags.listing_started = true;
-    printfReplyBody("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
-    printfReplyBody("<!-- HTML listing generated by Squid %s -->\n",
-                    version_string);
-    printfReplyBody("<!-- %s -->\n", mkrfc1123(squid_curtime));
-    printfReplyBody("<HTML><HEAD><TITLE>\n");
-    {
-        char *t = xstrdup(title);
-        rfc1738_unescape(t);
-        printfReplyBody("FTP Directory: %s\n", html_quote(t));
-        xfree(t);
-    }
-
-    printfReplyBody("</TITLE>\n");
-    printfReplyBody("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
-
-    if (flags.need_base_href)
-        printfReplyBody("<BASE HREF=\"%s\">\n",
-                        html_quote(base_href.termedBuf()));
-
-    printfReplyBody("</HEAD><BODY>\n");
-
-    if (cwd_message) {
-        printfReplyBody("<PRE>\n");
-
-        for (w = cwd_message; w; w = w->next)
-            printfReplyBody("%s\n", html_quote(w->key));
-
-        printfReplyBody("</PRE>\n");
-
-        printfReplyBody("<HR noshade size=\"1px\">\n");
-
-        wordlistDestroy(&cwd_message);
-    }
-
-    printfReplyBody("<H2>\n");
-    printfReplyBody("FTP Directory: ");
-    /* "ftp://" == 6 characters */
-    assert(title_url.size() >= 6);
-    k = 6 + strcspn(&title[6], "/");
-
-    for (i = 6, j = 0; title[i]; j = i) {
-        printfReplyBody("<A HREF=\"");
-        i += strcspn(&title[i], "/");
-
-        if (i > j) {
-            char *url = xstrdup(title);
-            url[i] = '\0';
-            printfReplyBody("%s", html_quote(url + k));
-            printfReplyBody("/");
-            printfReplyBody("\">");
-            rfc1738_unescape(url + j);
-            printfReplyBody("%s", html_quote(url + j));
-            safe_free(url);
-            printfReplyBody("</A>");
-        }
-
-        printfReplyBody("/");
-
-        if (title[i] == '/')
-            i++;
-
-        if (i == j) {
-            /* Error guard, or "assert" */
-            printfReplyBody("ERROR: Failed to parse URL: %s\n",
-                            html_quote(title));
-            debugs(9, DBG_CRITICAL, "Failed to parse URL: " << title);
-            break;
-        }
-    }
-
-    printfReplyBody("</H2>\n");
-    printfReplyBody("<PRE>\n");
-    dirup = htmlifyListEntry("<internal-dirup>");
-    writeReplyBody(dirup, strlen(dirup));
-    flags.html_header_sent = 1;
-}
-
+#if DEAD_CODE // obsoleted by ERR_DIR_LISTING
 void
 FtpStateData::listingFinish()
 {
-    debugs(9,3,HERE);
-    entry->buffer();
-    printfReplyBody("</PRE>\n");
+    // TODO: figure out what this means and how to show it ...
 
     if (flags.listformat_unknown && !flags.tried_nlst) {
-        printfReplyBody("<A HREF=\"%s/;type=d\">[As plain directory]</A>\n",
+        printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
                         flags.dir_slash ? rfc1738_escape_part(old_filepath) : ".");
     } else if (typecode == 'D') {
         const char *path = flags.dir_slash ? filepath : ".";
-        printfReplyBody("<A HREF=\"%s/\">[As extended directory]</A>\n", rfc1738_escape_part(path));
+        printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path));
     }
-
-    printfReplyBody("<HR noshade size=\"1px\">\n");
-    printfReplyBody("<ADDRESS>\n");
-    printfReplyBody("Generated %s by %s (%s)\n",
-                    mkrfc1123(squid_curtime),
-                    getMyHostname(),
-                    visible_appname_string);
-    printfReplyBody("</ADDRESS></BODY></HTML>\n");
 }
+#endif /* DEAD_CODE */
 
 /// \ingroup ServerProtocolFTPInternal
 static const char *Month[] = {
@@ -914,7 +821,7 @@ ftpListParseParts(const char *buf, struct _ftp_flags flags)
         p->type = 0;
 
         while (ct && *ct) {
-            time_t t;
+            time_t tm;
             int l = strcspn(ct, ",");
             char *tmp;
 
@@ -932,12 +839,12 @@ ftpListParseParts(const char *buf, struct _ftp_flags flags)
                 break;
 
             case 'm':
-                t = (time_t) strtol(ct + 1, &tmp, 0);
+                tm = (time_t) strtol(ct + 1, &tmp, 0);
 
                 if (tmp != ct + 1)
                     break;     /* not a valid integer */
 
-                p->date = xstrdup(ctime(&t));
+                p->date = xstrdup(ctime(&tm));
 
                 *(strstr(p->date, "\n")) = '\0';
 
@@ -989,47 +896,28 @@ found:
     return p;
 }
 
-/// \ingroup ServerProtocolFTPInternal
-static const char *
-dots_fill(size_t len)
-{
-    static char buf[256];
-    size_t i = 0;
-
-    if (len > Config.Ftp.list_width) {
-        memset(buf, ' ', 256);
-        buf[0] = '\n';
-        buf[Config.Ftp.list_width + 4] = '\0';
-        return buf;
-    }
-
-    for (i = len; i < Config.Ftp.list_width; i++)
-        buf[i - len] = (i % 2) ? '.' : ' ';
-
-    buf[i - len] = '\0';
-
-    return buf;
-}
-
-char *
+MemBuf *
 FtpStateData::htmlifyListEntry(const char *line)
 {
-    LOCAL_ARRAY(char, icon, 2048);
-    LOCAL_ARRAY(char, href, 2048 + 40);
-    LOCAL_ARRAY(char, text, 2048);
-    LOCAL_ARRAY(char, size, 2048);
-    LOCAL_ARRAY(char, chdir, 2048 + 40);
-    LOCAL_ARRAY(char, view, 2048 + 40);
-    LOCAL_ARRAY(char, download, 2048 + 40);
-    LOCAL_ARRAY(char, link, 2048 + 40);
-    LOCAL_ARRAY(char, html, 8192);
-    LOCAL_ARRAY(char, prefix, 2048);
-    size_t width = Config.Ftp.list_width;
+    char icon[2048];
+    char href[2048 + 40];
+    char text[ 2048];
+    char size[ 2048];
+    char chdir[ 2048 + 40];
+    char view[ 2048 + 40];
+    char download[ 2048 + 40];
+    char link[ 2048 + 40];
+    MemBuf *html;
+    char prefix[2048];
     ftpListParts *parts;
-    *icon = *href = *text = *size = *chdir = *view = *download = *link = *html = '\0';
+    *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
+
+    debugs(9, 7, HERE << " line ={" << line << "}");
 
-    if ((int) strlen(line) > 1024) {
-        snprintf(html, 8192, "%s\n", line);
+    if (strlen(line) > 1024) {
+        html = new MemBuf();
+        html->init();
+        html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
         return html;
     }
 
@@ -1038,64 +926,12 @@ FtpStateData::htmlifyListEntry(const char *line)
     else
         prefix[0] = '\0';
 
-    /* Handle builtin <dirup> */
-    if (strcmp(line, "<internal-dirup>") == 0) {
-        /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> {link} */
-        snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
-                 mimeGetIconURL("internal-dirup"),
-                 "[DIRUP]");
-
-        if (!flags.no_dotdot && !flags.root_dir) {
-            /* Normal directory */
-
-            if (!flags.dir_slash)
-                strcpy(href, "../");
-            else
-                strcpy(href, "./");
-
-            strcpy(text, "Parent Directory");
-        } else if (!flags.no_dotdot && flags.root_dir) {
-            /* "Top level" directory */
-            strcpy(href, "%2e%2e/");
-            strcpy(text, "Parent Directory");
-            snprintf(link, 2048, "(<A HREF=\"%s\">%s</A>)",
-                     "%2f/",
-                     "Root Directory");
-        } else if (flags.no_dotdot && !flags.root_dir) {
-            char *url;
-            /* Normal directory where last component is / or ..  */
-            strcpy(href, "%2e%2e/");
-            strcpy(text, "Parent Directory");
-
-            if (flags.dir_slash) {
-                url = xstrdup("./");
-            } else {
-                const char *title = title_url.termedBuf();
-                int k = 6 + strcspn(&title[6], "/");
-                char *t;
-                url = xstrdup(title + k);
-                t = url + strlen(url) - 2;
-
-                while (t > url && *t != '/')
-                    *t-- = '\0';
-            }
-
-            snprintf(link, 2048, "(<A HREF=\"%s\">%s</A>)", url, "Back");
-            safe_free(url);
-        } else {               /* NO_DOTDOT && ROOT_DIR */
-            /* "UNIX Root" directory */
-            strcpy(href, "/");
-            strcpy(text, "Home Directory");
-        }
-
-        snprintf(html, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A> %s\n",
-                 href, icon, href, text, link);
-        return html;
-    }
-
     if ((parts = ftpListParseParts(line, flags)) == NULL) {
         const char *p;
-        snprintf(html, 8192, "%s\n", line);
+
+        html = new MemBuf();
+        html->init();
+        html->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
 
         for (p = line; *p && xisspace(*p); p++);
         if (*p && !xisspace(*p))
@@ -1105,22 +941,14 @@ FtpStateData::htmlifyListEntry(const char *line)
     }
 
     if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
-        *html = '\0';
         ftpListPartsFree(&parts);
-        return html;
+        return NULL;
     }
 
     parts->size += 1023;
     parts->size >>= 10;
     parts->showname = xstrdup(parts->name);
 
-    if (!Config.Ftp.list_wrap) {
-        if (strlen(parts->showname) > width - 1) {
-            *(parts->showname + width - 1) = '>';
-            *(parts->showname + width - 0) = '\0';
-        }
-    }
-
     /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n  */
     xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
 
@@ -1129,21 +957,21 @@ FtpStateData::htmlifyListEntry(const char *line)
     switch (parts->type) {
 
     case 'd':
-        snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
+        snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
                  mimeGetIconURL("internal-dir"),
                  "[DIR]");
         strcat(href, "/");     /* margin is allocated above */
         break;
 
     case 'l':
-        snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
+        snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
                  mimeGetIconURL("internal-link"),
                  "[LINK]");
         /* sometimes there is an 'l' flag, but no "->" link */
 
         if (parts->link) {
             char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
-            snprintf(link, 2048, " -> <A HREF=\"%s%s\">%s</A>",
+            snprintf(link, 2048, " -&gt; <a href=\"%s%s\">%s</a>",
                      *link2 != '/' ? prefix : "", link2,
                      html_quote(parts->link));
             safe_free(link2);
@@ -1152,11 +980,11 @@ FtpStateData::htmlifyListEntry(const char *line)
         break;
 
     case '\0':
-        snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
+        snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
                  mimeGetIconURL(parts->name),
                  "[UNKNOWN]");
-        snprintf(chdir, 2048, " <A HREF=\"%s/;type=d\"><IMG border=\"0\" SRC=\"%s\" "
-                 "ALT=\"[DIR]\"></A>",
+        snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
+                 "alt=\"[DIR]\"></a>",
                  rfc1738_escape_part(parts->name),
                  mimeGetIconURL("internal-dir"));
         break;
@@ -1164,7 +992,7 @@ FtpStateData::htmlifyListEntry(const char *line)
     case '-':
 
     default:
-        snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
+        snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
                  mimeGetIconURL(parts->name),
                  "[FILE]");
         snprintf(size, 2048, " %6"PRId64"k", parts->size);
@@ -1173,31 +1001,33 @@ FtpStateData::htmlifyListEntry(const char *line)
 
     if (parts->type != 'd') {
         if (mimeGetViewOption(parts->name)) {
-            snprintf(view, 2048, " <A HREF=\"%s%s;type=a\"><IMG border=\"0\" SRC=\"%s\" "
-                     "ALT=\"[VIEW]\"></A>",
+            snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
+                     "alt=\"[VIEW]\"></a>",
                      prefix, href, mimeGetIconURL("internal-view"));
         }
 
         if (mimeGetDownloadOption(parts->name)) {
-            snprintf(download, 2048, " <A HREF=\"%s%s;type=i\"><IMG border=\"0\" SRC=\"%s\" "
-                     "ALT=\"[DOWNLOAD]\"></A>",
+            snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
+                     "alt=\"[DOWNLOAD]\"></a>",
                      prefix, href, mimeGetIconURL("internal-download"));
         }
     }
 
-    /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> . . . {date}{size}{chdir}{view}{download}{link}\n  */
-    if (parts->type != '\0') {
-        snprintf(html, 8192, "<A HREF=\"%s%s\">%s</A> <A HREF=\"%s%s\">%s</A>%s "
-                 "%s%8s%s%s%s%s\n",
-                 prefix, href, icon, prefix, href, html_quote(text), dots_fill(strlen(text)),
-                 parts->date, size, chdir, view, download, link);
-    } else {
-        /* Plain listing. {icon} {text} ... {chdir}{view}{download} */
-        snprintf(html, 8192, "<A HREF=\"%s%s\">%s</A> <A HREF=\"%s%s\">%s</A>%s "
-                 "%s%s%s%s\n",
-                 prefix, href, icon, prefix, href, html_quote(text), dots_fill(strlen(text)),
+    /* construct the table row from parts. */
+    html = new MemBuf();
+    html->init();
+    html->Printf("<tr class=\"entry\">"
+                 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
+                 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
+                 "<td class=\"date\">%s</td>"
+                 "<td class=\"size\">%s</td>"
+                 "<td class=\"actions\">%s%s%s%s</td>"
+                 "</tr>\n",
+                 prefix, href, icon,
+                 prefix, href, html_quote(text),
+                 parts->date,
+                 size,
                  chdir, view, download, link);
-    }
 
     ftpListPartsFree(&parts);
     return html;
@@ -1211,14 +1041,13 @@ FtpStateData::parseListing()
     char *end;
     char *line;
     char *s;
-    char *t;
+    MemBuf *t;
     size_t linelen;
     size_t usable;
-    StoreEntry *e = entry;
     size_t len = data.readBuf->contentSize();
 
     if (!len) {
-        debugs(9, 3, HERE << "no content to parse for " << e->url()  );
+        debugs(9, 3, HERE << "no content to parse for " << entry->url()  );
         return;
     }
 
@@ -1237,7 +1066,7 @@ FtpStateData::parseListing()
     debugs(9, 3, HERE << "usable = " << usable);
 
     if (usable == 0) {
-        debugs(9, 3, HERE << "didn't find end for " << e->url()  );
+        debugs(9, 3, HERE << "didn't find end for " << entry->url()  );
         xfree(sbuf);
         return;
     }
@@ -1246,7 +1075,6 @@ FtpStateData::parseListing()
 
     line = (char *)memAllocate(MEM_4K_BUF);
     end++;
-    e->buffer();       /* released when done processing current data payload */
     s = sbuf;
     s += strspn(s, crlf);
 
@@ -1269,11 +1097,14 @@ FtpStateData::parseListing()
 
         t = htmlifyListEntry(line);
 
-        assert(t != NULL);
-
-        writeReplyBody(t, strlen(t));
+        if ( t != NULL) {
+            debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
+            listing.append(t->content(), t->contentSize());
+//leak?            delete t;
+        }
     }
 
+    debugs(9, 7, HERE << "Done.");
     data.readBuf->consume(usable);
     memFree(line, MEM_4K_BUF);
     xfree(sbuf);
@@ -1390,7 +1221,7 @@ FtpStateData::dataRead(const CommIoCbParams &io)
 
             maybeReadVirginBody();
         } else {
-            if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported) {
+            if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported && !flags.listing) {
                 fwd->dontRetry(false); /* this is a retryable error */
                 fwd->ftpPasvFailed(true);
             }
@@ -1427,7 +1258,8 @@ FtpStateData::processReplyBody()
         return;
     }
 
-    if (!flags.http_header_sent && data.readBuf->contentSize() >= 0)
+    /* Directory listings are special. They write ther own headers via the error objects */
+    if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
         appendSuccessHeader();
 
     if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
@@ -1448,11 +1280,14 @@ FtpStateData::processReplyBody()
 
 #endif
 
-    if (flags.isdir && !flags.listing_started)
-        listingStart();
-
     if (flags.isdir) {
+        if (!flags.listing) {
+            flags.listing = 1;
+            listing.reset();
+        }
         parseListing();
+        maybeReadVirginBody();
+        return;
     } else if (const int csize = data.readBuf->contentSize()) {
         writeReplyBody(data.readBuf->content(), csize);
         debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
@@ -1942,7 +1777,12 @@ FtpStateData::handleControlReply()
     /* Copy the rest of the message to cwd_message to be printed in
      * error messages
      */
-    wordlistAddWl(&cwd_message, ctrl.message);
+    if (ctrl.message) {
+        for (wordlist *w = ctrl.message; w; w = w->next) {
+            cwd_message.append('\n');
+            cwd_message.append(w->key);
+        }
+    }
 
     debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
 
@@ -2286,13 +2126,13 @@ ftpReadCwd(FtpStateData * ftpState)
     if (code >= 200 && code < 300) {
         /* CWD OK */
         ftpState->unhack();
-        /* Reset cwd_message to only include the last message */
-
-        if (ftpState->cwd_message)
-            wordlistDestroy(&ftpState->cwd_message);
-
-        ftpState->cwd_message = ftpState->ctrl.message;
 
+        /* Reset cwd_message to only include the last message */
+        ftpState->cwd_message.reset("");
+        for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
+            ftpState->cwd_message.append(' ');
+            ftpState->cwd_message.append(w->key);
+        }
         ftpState->ctrl.message = NULL;
 
         /* Continue to traverse the path */
@@ -2457,7 +2297,7 @@ ftpReadEPSV(FtpStateData* ftpState)
     char h1, h2, h3, h4;
     int n;
     u_short port;
-    IpAddress ipa_remote;
+    Ip::Address ipa_remote;
     int fd = ftpState->data.fd;
     char *buf;
     debugs(9, 3, HERE);
@@ -2578,7 +2418,7 @@ ftpReadEPSV(FtpStateData* ftpState)
 static void
 ftpSendPassive(FtpStateData * ftpState)
 {
-    IpAddress addr;
+    Ip::Address addr;
     struct addrinfo *AI = NULL;
 
     /** Checks the server control channel is still available before running. */
@@ -2763,7 +2603,7 @@ ftpReadPasv(FtpStateData * ftpState)
     int p1, p2;
     int n;
     u_short port;
-    IpAddress ipa_remote;
+    Ip::Address ipa_remote;
     int fd = ftpState->data.fd;
     char *buf;
     LOCAL_ARRAY(char, ipaddr, 1024);
@@ -2869,8 +2709,7 @@ static int
 ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
 {
     int fd;
-
-    IpAddress addr;
+    Ip::Address addr;
     struct addrinfo *AI = NULL;
     int on = 1;
     int x = 0;
@@ -2940,8 +2779,7 @@ static void
 ftpSendPORT(FtpStateData * ftpState)
 {
     int fd;
-
-    IpAddress ipa;
+    Ip::Address ipa;
     struct addrinfo *AI = NULL;
     unsigned char *addrptr;
     unsigned char *portptr;
@@ -3012,7 +2850,7 @@ static void
 ftpSendEPRT(FtpStateData * ftpState)
 {
     int fd;
-    IpAddress addr;
+    Ip::Address addr;
     struct addrinfo *AI = NULL;
     char buf[MAX_IPSTRLEN];
 
@@ -3025,10 +2863,10 @@ ftpSendEPRT(FtpStateData * ftpState)
     ftpState->flags.pasv_supported = 0;
     fd = ftpOpenListenSocket(ftpState, 0);
 
-    addr.InitAddrInfo(AI);
+    Ip::Address::InitAddrInfo(AI);
 
     if (getsockname(fd, AI->ai_addr, &AI->ai_addrlen)) {
-        addr.FreeAddrInfo(AI);
+        Ip::Address::FreeAddrInfo(AI);
         debugs(9, DBG_CRITICAL, HERE << "getsockname(" << fd << ",..): " << xstrerror());
 
         /* XXX Need to set error message */
@@ -3048,7 +2886,7 @@ ftpSendEPRT(FtpStateData * ftpState)
     ftpState->writeCommand(cbuf);
     ftpState->state = SENT_EPRT;
 
-    addr.FreeAddrInfo(AI);
+    Ip::Address::FreeAddrInfo(AI);
 }
 
 static void
@@ -3466,6 +3304,28 @@ ftpReadRetr(FtpStateData * ftpState)
     }
 }
 
+/**
+ * Generate the HTTP headers and template fluff around an FTP
+ * directory listing display.
+ */
+void
+FtpStateData::completedListing()
+{
+    assert(entry);
+    entry->lock();
+    ErrorState *ferr = errorCon(ERR_DIR_LISTING, HTTP_OK, request);
+    ferr->ftp.listing = &listing;
+    ferr->ftp.cwd_msg = xstrdup(cwd_message.termedBuf());
+    ferr->ftp.server_msg = ctrl.message;
+    ctrl.message = NULL;
+    entry->replaceHttpReply( ferr->BuildHttpReply() );
+    errorStateFree(ferr);
+    EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+    entry->flush();
+    entry->unlock();
+}
+
+
 /// \ingroup ServerProtocolFTPInternal
 static void
 ftpReadTransferDone(FtpStateData * ftpState)
@@ -3475,10 +3335,10 @@ ftpReadTransferDone(FtpStateData * ftpState)
 
     if (code == 226 || code == 250) {
         /* Connection closed; retrieval done. */
-
-        if (ftpState->flags.html_header_sent)
-            ftpState->listingFinish();
-
+        if (ftpState->flags.listing) {
+            ftpState->completedListing();
+            /* QUIT operation handles sending the reply to client */
+        }
         ftpSendQuit(ftpState);
     } else {                   /* != 226 */
         debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
@@ -3538,11 +3398,15 @@ ftpSendQuit(FtpStateData * ftpState)
     ftpState->state = SENT_QUIT;
 }
 
-/// \ingroup ServerProtocolFTPInternal
+/**
+ * \ingroup ServerProtocolFTPInternal
+ *
+ *  This completes a client FTP operation with success or other page
+ *  generated and stored in the entry field by the code issuing QUIT.
+ */
 static void
 ftpReadQuit(FtpStateData * ftpState)
 {
-    /** \todo XXX should this just be a case of abortTransaction? */
     ftpState->serverComplete();
 }
 
@@ -3657,10 +3521,11 @@ FtpStateData::failed(err_type error, int xerrno)
 void
 FtpStateData::failedErrorMessage(err_type error, int xerrno)
 {
-    ErrorState *err;
+    ErrorState *ftperr;
     const char *command, *reply;
+
     /* Translate FTP errors into HTTP errors */
-    err = NULL;
+    ftperr = NULL;
 
     switch (error) {
 
@@ -3674,12 +3539,12 @@ FtpStateData::failedErrorMessage(err_type error, int xerrno)
 
             if (ctrl.replycode > 500)
                 if (password_url)
-                    err = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
+                    ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
                 else
-                    err = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
+                    ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
 
             else if (ctrl.replycode == 421)
-                err = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
+                ftperr = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
 
             break;
 
@@ -3687,7 +3552,7 @@ FtpStateData::failedErrorMessage(err_type error, int xerrno)
 
         case SENT_RETR:
             if (ctrl.replycode == 550)
-                err = errorCon(ERR_FTP_NOT_FOUND, HTTP_NOT_FOUND, fwd->request);
+                ftperr = errorCon(ERR_FTP_NOT_FOUND, HTTP_NOT_FOUND, fwd->request);
 
             break;
 
@@ -3698,21 +3563,20 @@ FtpStateData::failedErrorMessage(err_type error, int xerrno)
         break;
 
     case ERR_READ_TIMEOUT:
-        err = errorCon(error, HTTP_GATEWAY_TIMEOUT, fwd->request);
+        ftperr = errorCon(error, HTTP_GATEWAY_TIMEOUT, fwd->request);
         break;
 
     default:
-        err = errorCon(error, HTTP_BAD_GATEWAY, fwd->request);
+        ftperr = errorCon(error, HTTP_BAD_GATEWAY, fwd->request);
         break;
     }
 
-    if (err == NULL)
-        err = errorCon(ERR_FTP_FAILURE, HTTP_BAD_GATEWAY, fwd->request);
-
-    err->xerrno = xerrno;
+    if (ftperr == NULL)
+        ftperr = errorCon(ERR_FTP_FAILURE, HTTP_BAD_GATEWAY, fwd->request);
 
-    err->ftp.server_msg = ctrl.message;
+    ftperr->xerrno = xerrno;
 
+    ftperr->ftp.server_msg = ctrl.message;
     ctrl.message = NULL;
 
     if (old_request)
@@ -3729,12 +3593,13 @@ FtpStateData::failedErrorMessage(err_type error, int xerrno)
         reply = ctrl.last_reply;
 
     if (command)
-        err->ftp.request = xstrdup(command);
+        ftperr->ftp.request = xstrdup(command);
 
     if (reply)
-        err->ftp.reply = xstrdup(reply);
+        ftperr->ftp.reply = xstrdup(reply);
 
-    fwd->fail(err);
+    entry->replaceHttpReply( ftperr->BuildHttpReply() );
+    errorStateFree(ftperr);
 }
 
 /// \ingroup ServerProtocolFTPInternal
@@ -3776,7 +3641,8 @@ ftpSendReply(FtpStateData * ftpState)
     else
         err->ftp.reply = xstrdup("");
 
-    errorAppendEntry(ftpState->entry, err);
+    ftpState->entry->replaceHttpReply( err->BuildHttpReply() );
+    errorStateFree(err);
 
     ftpSendQuit(ftpState);
 }
@@ -3789,7 +3655,6 @@ FtpStateData::appendSuccessHeader()
     String urlpath = request->urlpath;
     const char *filename = NULL;
     const char *t = NULL;
-    StoreEntry *e = entry;
 
     debugs(9, 3, HERE);
 
@@ -3800,11 +3665,11 @@ FtpStateData::appendSuccessHeader()
 
     flags.http_header_sent = 1;
 
-    assert(e->isEmpty());
+    assert(entry->isEmpty());
 
-    EBIT_CLR(e->flags, ENTRY_FWD_HDR_WAIT);
+    EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
 
-    e->buffer();       /* released when done processing current data payload */
+    entry->buffer();   /* released when done processing current data payload */
 
     filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
 
@@ -3831,11 +3696,9 @@ FtpStateData::appendSuccessHeader()
 
     /* set standard stuff */
 
-    HttpVersion version(1, 0);
     if (0 == getCurrentOffset()) {
         /* Full reply */
-        reply->setHeaders(version, HTTP_OK, "Gatewaying",
-                          mime_type, theSize, mdtm, -2);
+        reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
     } else if (theSize < getCurrentOffset()) {
         /*
          * DPW 2007-05-04
@@ -3847,15 +3710,13 @@ FtpStateData::appendSuccessHeader()
                " current offset=" << getCurrentOffset() <<
                ", but theSize=" << theSize <<
                ".  assuming full content response");
-        reply->setHeaders(version, HTTP_OK, "Gatewaying",
-                          mime_type, theSize, mdtm, -2);
+        reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
     } else {
         /* Partial reply */
         HttpHdrRangeSpec range_spec;
         range_spec.offset = getCurrentOffset();
         range_spec.length = theSize - getCurrentOffset();
-        reply->setHeaders(version, HTTP_PARTIAL_CONTENT, "Gatewaying",
-                          mime_type, theSize - getCurrentOffset(), mdtm, -2);
+        reply->setHeaders(HTTP_PARTIAL_CONTENT, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
         httpHeaderAddContRange(&reply->header, range_spec, theSize);
     }
 
@@ -3948,10 +3809,10 @@ FtpStateData::printfReplyBody(const char *fmt, ...)
  * which should be sent to either StoreEntry, or to ICAP...
  */
 void
-FtpStateData::writeReplyBody(const char *data, size_t len)
+FtpStateData::writeReplyBody(const char *dataToWrite, size_t dataLength)
 {
-    debugs(9, 5, HERE << "writing " << len << " bytes to the reply");
-    addVirginReplyBody(data, len);
+    debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
+    addVirginReplyBody(dataToWrite, dataLength);
 }
 
 /**
@@ -4093,8 +3954,7 @@ FtpChannel::close()
         comm_remove_close_handler(fd, closer);
         closer = NULL;
         fd = -1;
-    }
-    else if (fd >= 0) {
+    } else if (fd >= 0) {
         comm_remove_close_handler(fd, closer);
         closer = NULL;
         comm_close(fd); // we do not expect to be called back