]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Remove FTP listing experiment. Will continue in other branch
authorAmos Jeffries <squid3@treenet.co.nz>
Sun, 18 Jan 2009 04:59:55 +0000 (17:59 +1300)
committerAmos Jeffries <squid3@treenet.co.nz>
Sun, 18 Jan 2009 04:59:55 +0000 (17:59 +1300)
src/MemBuf.h
src/cache_cf.cc
src/cf.data.pre
src/enums.h
src/errorpage.cc
src/errorpage.h
src/ftp.cc

index 2fff10579ee65f7f91bb1869fbcb596c2a61ff13..4da921b3a899bb8fdd9dce4ecfcd2878610dd45e 100644 (file)
@@ -72,8 +72,8 @@ public:
 
     /**
      * Whether the buffer contains any data space available.
-     \retval true      if data can be added to the buffer
-     \retval false     if the buffer is full
+     \retval true      if data can be added to teh buffer
+     \retval false     if teh buffer is full
      */
     bool hasSpace() const { return size+1 < capacity; }
 
index ba81de5aa9449e3f5820c3c03d827e73aa9a3b2f..c48cceaf2cd4a7315ec86a594763eebe2332f1b4 100644 (file)
@@ -2475,13 +2475,11 @@ free_time_t(time_t * var)
     *var = 0;
 }
 
-#if UNUSED_CODE
 static void
 dump_size_t(StoreEntry * entry, const char *name, size_t var)
 {
     storeAppendPrintf(entry, "%s %d\n", name, (int) var);
 }
-#endif
 
 static void
 dump_b_size_t(StoreEntry * entry, const char *name, size_t var)
@@ -2509,7 +2507,6 @@ dump_kb_int64_t(StoreEntry * entry, const char *name, int64_t var)
     storeAppendPrintf(entry, "%s %"PRId64" %s\n", name, var, B_KBYTES_STR);
 }
 
-#if UNUSED_CODE
 static void
 parse_size_t(size_t * var)
 {
@@ -2517,7 +2514,6 @@ parse_size_t(size_t * var)
     i = GetInteger();
     *var = (size_t) i;
 }
-#endif
 
 static void
 parse_b_size_t(size_t * var)
index 472a30e9e8095a44c0aa63f9768238f20b43a39b..9c55908b85e76029c59f7b28ce33e372961cb8c2 100644 (file)
@@ -2630,6 +2630,16 @@ DOC_START
        (for example perl.com).
 DOC_END
 
+NAME: ftp_list_width
+TYPE: size_t
+DEFAULT: 32
+LOC: Config.Ftp.list_width
+DOC_START
+       Sets the width of ftp listings. This should be set to fit in
+       the width of a standard browser. Setting this too small
+       can cut off long filenames when browsing ftp sites.
+DOC_END
+
 NAME: ftp_passive
 TYPE: onoff
 DEFAULT: on
index 4e453ecf3342807b33fb0767be0de8060aad3fd3..559c0ee8c4b14b0c71c45f854ef518d52aa08a79 100644 (file)
@@ -98,7 +98,6 @@ typedef enum {
     ERR_ESI,                    /* Failure to perform ESI processing */
     ERR_INVALID_RESP,
     ERR_ICAP_FAILURE,
-    ERR_FTP_LISTING,           /* Display of FTP remote directory */
     ERR_MAX
 } err_type;
 
index 0dfb16520cb1baf6484d08187ff5055623561d86..848c60042a39b5336adb45a07d6ed4322529a830 100644 (file)
@@ -655,10 +655,7 @@ ErrorState::Convert(char token)
 
     case 'g':
         /* FTP SERVER MESSAGE */
-        if(ftp.server_msg)
-            wordlistCat(ftp.server_msg, &mb);
-        else if(ftp.listing)
-            mb.append(ftp.listing->content(), ftp.listing->contentSize());
+        wordlistCat(ftp.server_msg, &mb);
 
         break;
 
@@ -809,8 +806,6 @@ ErrorState::Convert(char token)
     case 'z':
         if (dnsserver_msg)
             p = dnsserver_msg;
-        else if (ftp.cwd_msg)
-            p = ftp.cwd_msg;
         else
             p = "[unknown]";
 
index b1340e7e26b187449b9aafd02a4d480205cc70fe..b73564124f25ab9b168822e19ef10e59d092dc2a 100644 (file)
@@ -136,8 +136,6 @@ public:
         wordlist *server_msg;
         char *request;
         char *reply;
-        char *cwd_msg;
-        MemBuf *listing;
     } ftp;
 
     char *request_hdrs;
index 9cf90d8946ccba9632e523df2bbacd1924a2c3ac..9ca5faaa12dcc9416182160ca0b41565b4761302 100644 (file)
@@ -53,7 +53,6 @@
 #include "wordlist.h"
 #include "SquidTime.h"
 #include "URLScheme.h"
-#include "SquidString.h"
 
 /**
  \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
@@ -107,12 +106,13 @@ 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;
+    bool listing_started;
     bool completed_forwarding;
 };
 
@@ -171,12 +171,11 @@ public:
     int64_t restarted_offset;
     char *proxy_host;
     size_t list_width;
-    String cwd_message;
+    wordlist *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
@@ -213,11 +212,13 @@ 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 dataComplete();
     void dataRead(const CommIoCbParams &io);
     int checkAuth(const HttpHeader * req_hdr);
@@ -489,7 +490,8 @@ FtpStateData::~FtpStateData()
     if (ctrl.message)
         wordlistDestroy(&ctrl.message);
 
-    cwd_message.clean();
+    if (cwd_message)
+        wordlistDestroy(&cwd_message);
 
     safe_free(ctrl.last_reply);
 
@@ -558,12 +560,98 @@ FtpStateData::ftpTimeout(const CommTimeoutCbParams &io)
     /* failed() closes ctrl.fd and frees ftpState */
 }
 
-#if DEAD_CODE // obsoleted by ERR_FTP_LISTING template.
 void
-FtpStateData::listingFinish()
+FtpStateData::listingStart()
 {
+    debugs(9,3, HERE);
+    wordlist *w;
+    char *dirup;
+    int i, j, k;
+    const char *title = title_url.buf();
+    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.buf()));
+
+    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>");
+        }
 
-// TODO: figure out what this means and how to show it ...
+        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;
+}
+
+void
+FtpStateData::listingFinish()
+{
+    debugs(9,3,HERE);
+    entry->buffer();
+    printfReplyBody("</PRE>\n");
 
     if (flags.listformat_unknown && !flags.tried_nlst) {
         printfReplyBody("<A HREF=\"%s/;type=d\">[As plain directory]</A>\n",
@@ -572,8 +660,15 @@ FtpStateData::listingFinish()
         const char *path = flags.dir_slash ? filepath : ".";
         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[] = {
@@ -837,25 +932,47 @@ found:
     return p;
 }
 
-MemBuf *
+/// \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 *
 FtpStateData::htmlifyListEntry(const char *line)
 {
-    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];
+    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;
     ftpListParts *parts;
-    *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
+    *icon = *href = *text = *size = *chdir = *view = *download = *link = *html = '\0';
 
-    if (strlen(line) > 1024) {
-        html = new MemBuf();
-        html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
+    if ((int) strlen(line) > 1024) {
+        snprintf(html, 8192, "%s\n", line);
         return html;
     }
 
@@ -864,10 +981,64 @@ 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.buf();
+                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;
-        html = new MemBuf();
-        html->Printf("<tr class=\"entry\"<td colspan=\"5\">%s</td></tr>\n", line);
+        snprintf(html, 8192, "%s\n", line);
 
         for (p = line; *p && xisspace(*p); p++);
         if (*p && !xisspace(*p))
@@ -877,14 +1048,22 @@ FtpStateData::htmlifyListEntry(const char *line)
     }
 
     if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
+        *html = '\0';
         ftpListPartsFree(&parts);
-        return NULL;
+        return html;
     }
 
     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);
 
@@ -893,21 +1072,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, " -&gt; <a href=\"%s%s\">%s</a>",
+            snprintf(link, 2048, " -> <A HREF=\"%s%s\">%s</A>",
                      *link2 != '/' ? prefix : "", link2,
                      html_quote(parts->link));
             safe_free(link2);
@@ -916,11 +1095,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;
@@ -928,7 +1107,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);
@@ -937,32 +1116,31 @@ 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"));
         }
     }
 
-    /* construct the table row from parts. */
-    html = new MemBuf();
-    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);
+    /* <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)),
+                 chdir, view, download, link);
+    }
 
     ftpListPartsFree(&parts);
     return html;
@@ -976,7 +1154,7 @@ FtpStateData::parseListing()
     char *end;
     char *line;
     char *s;
-    MemBuf *t;
+    char *t;
     size_t linelen;
     size_t usable;
     StoreEntry *e = entry;
@@ -1034,9 +1212,9 @@ FtpStateData::parseListing()
 
         t = htmlifyListEntry(line);
 
-        if( t != NULL) {
-            listing.append(t->content(), t->contentSize());
-        }
+        assert(t != NULL);
+
+        writeReplyBody(t, strlen(t));
     }
 
     data.readBuf->consume(usable);
@@ -1171,7 +1349,7 @@ FtpStateData::dataRead(const CommIoCbParams &io)
 
             maybeReadVirginBody();
         } else {
-            if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported && !flags.listing) {
+            if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported) {
                 fwd->dontRetry(false); /* this is a retryable error */
                 fwd->ftpPasvFailed(true);
             }
@@ -1229,16 +1407,17 @@ FtpStateData::processReplyBody()
 
 #endif
 
+    if (flags.isdir && !flags.listing_started)
+        listingStart();
+
     if (flags.isdir) {
-        flags.listing = 1;
-        listing.reset();
         parseListing();
-        return;
-    } else if (const int csize = data.readBuf->contentSize()) {
-        writeReplyBody(data.readBuf->content(), csize);
-        debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
-        data.readBuf->consume(csize);
-    }
+    } else
+        if (const int csize = data.readBuf->contentSize()) {
+            writeReplyBody(data.readBuf->content(), csize);
+            debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
+            data.readBuf->consume(csize);
+        }
 
     entry->flush();
 
@@ -1710,11 +1889,7 @@ FtpStateData::handleControlReply()
     /* Copy the rest of the message to cwd_message to be printed in
      * error messages
      */
-    cwd_message.append('\n');
-    for (wordlist *w = ctrl.message; w; w = w->next) {
-        cwd_message.append(' ');
-        cwd_message.append(w->key);
-    }
+    wordlistAddWl(&cwd_message, ctrl.message);
 
     debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
 
@@ -1983,13 +2158,13 @@ ftpReadCwd(FtpStateData * ftpState)
     if (code >= 200 && code < 300) {
         /* CWD OK */
         ftpState->unhack();
-
         /* 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);
-        }
+
+        if (ftpState->cwd_message)
+            wordlistDestroy(&ftpState->cwd_message);
+
+        ftpState->cwd_message = ftpState->ctrl.message;
+
         ftpState->ctrl.message = NULL;
 
         /* Continue to traverse the path */
@@ -3135,8 +3310,9 @@ ftpReadTransferDone(FtpStateData * ftpState)
     if (code == 226 || code == 250) {
         /* Connection closed; retrieval done. */
 
-        if (ftpState->flags.listing)
-            ftpState->failed(ERR_FTP_LISTING, 0);
+        if (ftpState->flags.html_header_sent)
+            ftpState->listingFinish();
+
         ftpSendQuit(ftpState);
     } else {                   /* != 226 */
         debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
@@ -3369,14 +3545,9 @@ FtpStateData::failedErrorMessage(err_type error, int xerrno)
 
     err->xerrno = xerrno;
 
-    if(error == ERR_FTP_LISTING) {
-        err->ftp.listing = &listing;
-        err->ftp.cwd_msg = xstrdup(html_quote(cwd_message.buf()));
-    }
-    else {
-        err->ftp.server_msg = ctrl.message;
-        ctrl.message = NULL;
-    }
+    err->ftp.server_msg = ctrl.message;
+
+    ctrl.message = NULL;
 
     if (old_request)
         command = old_request;