]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
mostly done inlining all FTP requsts
authorwessels <>
Mon, 26 May 1997 10:04:52 +0000 (10:04 +0000)
committerwessels <>
Mon, 26 May 1997 10:04:52 +0000 (10:04 +0000)
include/util.h
lib/Makefile.in
lib/rfc1738.c
src/Makefile.in
src/cache_cf.cc
src/comm.cc
src/errorpage.cc
src/ftp.cc
src/main.cc
src/url.cc

index 5d15187ed6b05512f8982e1e39d3b72f4eaf7918..96a4f91803777f2a7f716476daee2f2a744bebd2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: util.h,v 1.29 1997/04/28 04:30:02 wessels Exp $
+ * $Id: util.h,v 1.30 1997/05/26 04:04:52 wessels Exp $
  *
  * AUTHOR: Harvest Derived
  *
@@ -153,5 +153,7 @@ void malloc_statistics _PARAMS((void (*)_PARAMS((int, int, void *)), void *));
 
 typedef struct in_addr SIA;
 extern int safe_inet_addr _PARAMS((const char *, SIA *));
+extern time_t parse_iso3307_time _PARAMS((const char *buf));
+extern char *base64_decode _PARAMS((const char *coded));
 
 #endif /* ndef _UTIL_H_ */
index 7e72840985e9c455dfcb88dacfeaddd3d30a66df..49b5c74c32e0014eb93962258b946d28532fc073 100644 (file)
@@ -3,7 +3,7 @@
 #
 #  Darren Hardy, hardy@cs.colorado.edu, April 1994
 #
-#  $Id: Makefile.in,v 1.23 1997/05/14 21:08:14 wessels Exp $
+#  $Id: Makefile.in,v 1.24 1997/05/26 04:04:53 wessels Exp $
 #
 prefix         = @prefix@
 srcdir         = @srcdir@
@@ -33,6 +33,7 @@ UTILOBJS      = rfc1123.o \
                  tree.o \
                  splay.o \
                  safe_inet_addr.o \
+                 iso3307.o \
                  $(LIBOBJS)
 REGEXOBJS      = GNUregex.o
 LIBS           = libmiscutil.a @LIBREGEX@
index 381a9632a0bb2306b5b45f12906715ad1f83b410..d1bd68beea4eb1297a4be9a1fd54a90bfb76770e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: rfc1738.c,v 1.8 1996/11/06 23:14:15 wessels Exp $
+ * $Id: rfc1738.c,v 1.9 1997/05/26 04:04:53 wessels Exp $
  *
  * DEBUG: 
  * AUTHOR: Harvest Derived
 
 #include "ansiproto.h"
 #include "util.h"
-#define BIG_BUFSIZ (BUFSIZ * 4)
 
 /*  
  *  RFC 1738 defines that these characters should be escaped, as well
@@ -147,12 +146,19 @@ static char rfc1738_unsafe_chars[] =
 char *
 rfc1738_escape(const char *url)
 {
-    static char buf[BIG_BUFSIZ];
+    static char *buf;
+    static size_t bufsize = 0;
     const char *p;
     char *q;
     int i, do_escape;
 
-    for (p = url, q = &buf[0]; *p != '\0'; p++, q++) {
+    if (buf == NULL || strlen(url) * 3 > bufsize) {
+       safe_free(buf);
+       bufsize = strlen(url) * 3 + 1;
+       buf = xcalloc (bufsize, 1);
+    }
+
+    for (p = url, q = buf; *p != '\0'; p++, q++) {
        do_escape = 0;
 
        /* RFC 1738 defines these chars as unsafe */
index e4486101da71931486c625f9fe65041001cbc247..0ab033e387b7decacb518cd841a5ab403a921810 100644 (file)
@@ -1,7 +1,7 @@
 #
 #  Makefile for the Squid Object Cache server
 #
-#  $Id: Makefile.in,v 1.71 1997/05/15 01:06:49 wessels Exp $
+#  $Id: Makefile.in,v 1.72 1997/05/26 04:04:54 wessels Exp $
 #
 #  Uncomment and customize the following to suit your needs:
 #
@@ -33,7 +33,6 @@ VPATH         = @srcdir@
 
 DEFAULT_CONFIG_FILE     = $(sysconfdir)/squid.conf
 DEFAULT_MIME_TABLE     = $(sysconfdir)/mime.conf
-DEFAULT_FTPGET          = $(libexecdir)/ftpget
 DEFAULT_DNSSERVER       = $(libexecdir)/dnsserver
 DEFAULT_CACHE_LOG       = $(localstatedir)/logs/cache.log
 DEFAULT_ACCESS_LOG      = $(localstatedir)/logs/access.log
@@ -72,7 +71,7 @@ FTPGET_LIBS   = -L../lib -lmiscutil $(XTRA_LIBS)
 PINGER_LIBS    = -L../lib -lmiscutil $(XTRA_LIBS)
 
 PROGS           = squid client
-UTILS           = dnsserver ftpget unlinkd
+UTILS           = dnsserver unlinkd
 SUID_UTILS     = pinger
 CGIPROGS       = cachemgr.cgi
 OBJS           = \
@@ -156,9 +155,6 @@ dnsserver: dnsserver.o
 cachemgr.cgi:  cachemgr.o
        $(CC) -o $@ $(LDFLAGS) cachemgr.o $(CLIENT_LIBS)
 
-ftpget: ftpget.o
-       $(CC) -o $@ $(LDFLAGS) ftpget.o debug.o mime.o $(FTPGET_LIBS) $(REGEXLIB)
-
 pinger: pinger.o
        $(CC) -o $@ $(LDFLAGS) pinger.o debug.o $(PINGER_LIBS)
 
index 13fdecb28b32db7a624fcd48922953ba47d978b2..c9902ca8dd27e18f790b435f815ba12d169393e0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: cache_cf.cc,v 1.191 1997/05/23 19:45:25 wessels Exp $
+ * $Id: cache_cf.cc,v 1.192 1997/05/26 04:04:55 wessels Exp $
  *
  * DEBUG: section 3     Configuration File Parsing
  * AUTHOR: Harvest Derived
@@ -209,6 +209,11 @@ struct SquidConfig Config;
 #define DefaultOptionsClientDb 1       /* default on */
 #define DefaultOptionsQueryIcmp        0       /* default off */
 
+#define DefaultFtpIconPrefix   "internal-"
+#define DefaultFtpIconSuffix   null_string
+#define DefaultFtpListWidth    32
+#define DefaultFtpListWrap     0
+
 static const char *const T_SECOND_STR = "second";
 static const char *const T_MINUTE_STR = "minute";
 static const char *const T_HOUR_STR = "hour";
@@ -1430,6 +1435,10 @@ configSetFactoryDefaults(void)
     Config.Options.enable_purge = DefaultOptionsEnablePurge;
     Config.Options.client_db = DefaultOptionsClientDb;
     Config.Options.query_icmp = DefaultOptionsQueryIcmp;
+    Config.Ftp.icon_prefix = safe_xstrdup(DefaultFtpIconPrefix);
+    Config.Ftp.icon_suffix = safe_xstrdup(DefaultFtpIconSuffix);
+    Config.Ftp.list_width = DefaultFtpListWidth;
+    Config.Ftp.list_wrap = DefaultFtpListWrap;
 }
 
 static void
index ffc3e250bcdcad0be3302db3c8e526de947ced22..bca1aecbac4d431cb3d31eea8e25418d68f6e8f5 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: comm.cc,v 1.154 1997/05/22 15:51:51 wessels Exp $
+ * $Id: comm.cc,v 1.155 1997/05/26 04:04:56 wessels Exp $
  *
  * DEBUG: section 5     Socket Functions
  * AUTHOR: Harvest Derived
@@ -773,7 +773,6 @@ comm_poll(time_t sec)
            fatal_dump(NULL);
        if (shutdown_pending || reconfigure_pending) {
            serverConnectionsClose();
-           ftpServerClose();
            dnsShutdownServers();
            redirectShutdownServers();
            /* shutdown_pending will be set to
@@ -926,7 +925,6 @@ comm_select(time_t sec)
 
        if (shutdown_pending || reconfigure_pending) {
            serverConnectionsClose();
-           ftpServerClose();
            dnsShutdownServers();
            redirectShutdownServers();
            /* shutdown_pending will be set to
index 5ceb5346ebf42fa5f568a6288f5c30dd6c984f90..22f4edb6d2d1d61e933aa9756568a2da06ee6b3f 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: errorpage.cc,v 1.54 1997/02/28 21:33:36 wessels Exp $
+ * $Id: errorpage.cc,v 1.55 1997/05/26 04:04:58 wessels Exp $
  *
  * DEBUG: section 4     Error Generation
  * AUTHOR: Duane Wessels
@@ -86,6 +86,9 @@ static error_data ErrorData[] =
     {"ERR_READ_ERROR",
        "Read Error",
        "The remote site or network may be down.  Please try again."},
+    {"ERR_WRITE_ERROR",
+       "Write Error",
+       "The remote site or network may be down.  Please try again."},
     {"ERR_CLIENT_ABORT",
        "Client Aborted",
        "Client(s) dropped connection before transmission completed.\nObject fetching is aborted.",},
index 6460a76a879e2eb2f40a59d6a01da099bac0f358..02467952f25fcdf6168eb51c3da025321b3484c0 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: ftp.cc,v 1.109 1997/05/23 05:33:18 wessels Exp $
+ * $Id: ftp.cc,v 1.110 1997/05/26 04:04:58 wessels Exp $
  *
  * DEBUG: section 9     File Transfer Protocol (FTP)
  * AUTHOR: Harvest Derived
  *  
  */
 
-/*
- * 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.  
- */
-
 #include "squid.h"
 
 #define FTP_DELETE_GAP  (1<<18)
-#define MAGIC_MARKER    "\004\004\004" /* No doubt this should be more configurable */
-#define MAGIC_MARKER_SZ 3
 
-static int ftpget_server_read = -1;
-static int ftpget_server_write = -1;
-static u_short ftpget_port = 0;
+enum {
+    FTP_ISDIR,
+    FTP_PASV_SUPPORTED,
+    FTP_SKIP_WHITESPACE,
+    FTP_REST_SUPPORTED,
+    FTP_PASV_ONLY,
+    FTP_AUTHENTICATED,
+    FTP_IP_LOOKUP_PENDING,
+    FTP_HTTP_HEADER_SENT,
+    FTP_TRIED_NLST,
+    FTP_USE_BASE,
+    FTP_ROOT_DIR,
+    FTP_HTML_HEADER_SENT
+};
+
+static const char *const crlf = "\r\n";
+static char cbuf[1024];
+
+typedef enum {
+    BEGIN,
+    SENT_USER,
+    SENT_PASS,
+    SENT_TYPE,
+    SENT_MDTM,
+    SENT_SIZE,
+    SENT_PORT,
+    SENT_PASV,
+    SENT_CWD,
+    SENT_LIST,
+    SENT_NLST,
+    SENT_REST,
+    SENT_RETR,
+    SENT_QUIT,
+    READING_DATA
+} ftp_state_t;
 
 typedef struct _Ftpdata {
     StoreEntry *entry;
@@ -120,31 +75,106 @@ typedef struct _Ftpdata {
     char user[MAX_URL];
     char password[MAX_URL];
     char *reply_hdr;
-    int ftp_fd;
-    int got_marker;            /* denotes end of successful request */
     int reply_hdr_state;
-    int authenticated;         /* This ftp request is authenticated */
+    char *title_url;
+    int conn_att;
+    int login_att;
+    ftp_state_t state;
+    char *errmsg;
+    time_t mdtm;
+    int size;
+    int flags;
+    wordlist *pathcomps;
+    char *filepath;
+    int restart_offset;
+    int rest_att;
+    char *proxy_host;
+    size_t list_width;
+    wordlist *cwd_message;
+    struct {
+       int fd;
+       char *buf;
+       size_t size;
+       off_t offset;
+       FREE *freefunc;
+       wordlist *message;
+       char *last_message;
+       int replycode;
+    } ctrl;
+    struct {
+       int fd;
+       char *buf;
+       size_t size;
+       off_t offset;
+       FREE *freefunc;
+    } data;
 } FtpStateData;
 
-typedef struct ftp_ctrl_t {
-    request_t *request;
-    StoreEntry *entry;
-} ftp_ctrl_t;
+typedef struct {
+    char type;
+    int size;
+    char *date;
+    char *name;
+    char *showname;
+    char *link;
+} ftpListParts;
+
+typedef void (FTPSM) _PARAMS((FtpStateData *));
 
 /* Local functions */
 static CNCB ftpConnectDone;
-static CWCB ftpSendComplete;
-static PF ftpReadReply;
-static PF ftpSendRequest;
-static PF ftpServerClosed;
+static CNCB ftpPasvCallback;
+static IPH ftpConnect;
+static PF ftpReadData;
 static PF ftpStateFree;
 static PF ftpTimeout;
+static PF ftpReadControlReply;
+static CWCB ftpWriteCommandCallback;
 static char *ftpGetBasicAuth _PARAMS((const char *));
 static void ftpProcessReplyHeader _PARAMS((FtpStateData *, const char *, int));
 static void ftpLoginParser _PARAMS((const char *, FtpStateData *));
+static void ftpFail _PARAMS((FtpStateData * ftpState));
+static wordlist *ftpParseControlReply _PARAMS((char *buf, size_t len, int *code));
+static void ftpSendPasv _PARAMS((FtpStateData * ftpState));
+static void ftpSendCwd _PARAMS((FtpStateData * ftpState));
+static void ftpSendPort _PARAMS((FtpStateData * ftpState));
+static void ftpRestOrList _PARAMS((FtpStateData * ftpState));
+static void ftpReadQuit _PARAMS((FtpStateData * ftpState));
+static void ftpDataTransferDone _PARAMS((FtpStateData * ftpState));
+static void ftpAppendSuccessHeader _PARAMS((FtpStateData * ftpState));
 
-/* External functions */
-extern char *base64_decode _PARAMS((const char *coded));
+static FTPSM ftpReadWelcome;
+static FTPSM ftpReadUser;
+static FTPSM ftpReadPass;
+static FTPSM ftpReadType;
+static FTPSM ftpReadMdtm;
+static FTPSM ftpReadSize;
+static FTPSM ftpReadPort;
+static FTPSM ftpReadPasv;
+static FTPSM ftpReadCwd;
+static FTPSM ftpReadList;
+static FTPSM ftpReadRest;
+static FTPSM ftpReadRetr;
+static FTPSM ftpReadTransferDone;
+
+FTPSM *FTP_SM_FUNCS[] =
+{
+    ftpReadWelcome,
+    ftpReadUser,
+    ftpReadPass,
+    ftpReadType,
+    ftpReadMdtm,
+    ftpReadSize,
+    ftpReadPort,
+    ftpReadPasv,
+    ftpReadCwd,
+    ftpReadList,               /* SENT_LIST */
+    ftpReadList,               /* SENT_NLST */
+    ftpReadRest,
+    ftpReadRetr,
+    ftpReadQuit,
+    ftpReadTransferDone
+};
 
 static void
 ftpStateFree(int fd, void *data)
@@ -158,6 +188,16 @@ ftpStateFree(int fd, void *data)
        ftpState->reply_hdr = NULL;
     }
     requestUnlink(ftpState->request);
+    if (ftpState->ctrl.buf)
+       ftpState->ctrl.freefunc(ftpState->ctrl.buf);
+    if (ftpState->data.buf)
+       ftpState->data.freefunc(ftpState->data.buf);
+    if (ftpState->pathcomps)
+       wordlistDestroy(&ftpState->pathcomps);
+    if (ftpState->ctrl.message)
+       wordlistDestroy(&ftpState->ctrl.message);
+    if (ftpState->cwd_message)
+       wordlistDestroy(&ftpState->cwd_message);
     xfree(ftpState);
 }
 
@@ -185,7 +225,9 @@ ftpTimeout(int fd, void *data)
     StoreEntry *entry = ftpState->entry;
     debug(9, 4, "ftpLifeTimeExpire: FD %d: '%s'\n", fd, entry->url);
     squid_error_entry(entry, ERR_READ_TIMEOUT, NULL);
-    comm_close(fd);
+    if (ftpState->data.fd >= 0)
+       comm_close(ftpState->data.fd);
+    comm_close(ftpState->ctrl.fd);
 }
 
 /* This is too much duplicated code from httpProcessReplyHeader.  Only
@@ -199,7 +241,7 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size)
     int hdr_len;
     struct _http_reply *reply = entry->mem_obj->reply;
 
-    debug(11, 3, "ftpProcessReplyHeader: key '%s'\n", entry->key);
+    debug(9, 3, "ftpProcessReplyHeader: key '%s'\n", entry->key);
 
     if (ftpState->reply_hdr == NULL)
        ftpState->reply_hdr = get_free_8k_page();
@@ -209,7 +251,7 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size)
        strncat(ftpState->reply_hdr, buf, room < size ? room : size);
        hdr_len += room < size ? room : size;
        if (hdr_len > 4 && strncmp(ftpState->reply_hdr, "HTTP/", 5)) {
-           debug(11, 3, "ftpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key);
+           debug(9, 3, "ftpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key);
            ftpState->reply_hdr_state += 2;
            return;
        }
@@ -222,14 +264,14 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size)
     }
     if (ftpState->reply_hdr_state == 1) {
        ftpState->reply_hdr_state++;
-       debug(11, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
+       debug(9, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
            ftpState->reply_hdr);
        /* Parse headers into reply structure */
        httpParseReplyHeaders(ftpState->reply_hdr, reply);
        storeTimestampsSet(entry);
        /* Check if object is cacheable or not based on reply code */
        if (reply->code)
-           debug(11, 3, "ftpProcessReplyHeader: HTTP CODE: %d\n", reply->code);
+           debug(9, 3, "ftpProcessReplyHeader: HTTP CODE: %d\n", reply->code);
        switch (reply->code) {
        case 200:               /* OK */
        case 203:               /* Non-Authoritative Information */
@@ -259,11 +301,367 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size)
     }
 }
 
+static void
+ftpListingStart(FtpStateData * ftpState)
+{
+    StoreEntry *e = ftpState->entry;
+    wordlist *w;
+    storeAppendPrintf(e, "<!-- HTML listing generated by Squid %s -->\n",
+       version_string);
+    storeAppendPrintf(e, "<!-- %s -->\n", mkrfc1123(squid_curtime));
+    storeAppendPrintf(e, "<HTML><HEAD><TITLE>\n");
+    storeAppendPrintf(e, "FTP Directory: %s\n",
+       ftpState->title_url);
+    storeAppendPrintf(e, "</TITLE>\n");
+    if (EBIT_TEST(ftpState->flags, FTP_USE_BASE))
+       storeAppendPrintf(e, "<BASE HREF=\"%s\">\n",
+           rfc1738_escape(ftpState->title_url));
+    storeAppendPrintf(e, "</HEAD><BODY>\n");
+    if (ftpState->cwd_message) {
+       storeAppendPrintf(e, "<PRE>\n");
+       for (w = ftpState->cwd_message; w; w = w->next)
+           storeAppendPrintf(e, "%s\n", w->key);
+       storeAppendPrintf(e, "</PRE>\n");
+       storeAppendPrintf(e, "<HR>\n");
+       wordlistDestroy(&ftpState->cwd_message);
+    }
+    storeAppendPrintf(e, "<H2>\n");
+    storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url);
+    storeAppendPrintf(e, "</H2>\n");
+    storeAppendPrintf(e, "<PRE>\n");
+    EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT);
+}
+
+static void
+ftpListingFinish(FtpStateData * ftpState)
+{
+    StoreEntry *e = ftpState->entry;
+    storeAppendPrintf(e, "</PRE>\n");
+    storeAppendPrintf(e, "<HR>\n");
+    storeAppendPrintf(e, "<ADDRESS>\n");
+    storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n",
+       mkrfc1123(squid_curtime),
+       appname,
+       version_string,
+       getMyHostname());
+    storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
+}
+
+static const char *Month[] =
+{
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static int
+is_month(const char *buf)
+{
+    int i;
+    for (i = 0; i < 12; i++)
+       if (!strcasecmp(buf, Month[i]))
+           return 1;
+    return 0;
+}
+
+
+static void
+ftpListPartsFree(ftpListParts ** parts)
+{
+    safe_free((*parts)->date);
+    safe_free((*parts)->name);
+    safe_free((*parts)->showname);
+    safe_free((*parts)->link);
+    safe_free(*parts);
+}
+
+#define MAX_TOKENS 64
+
+static ftpListParts *
+ftpListParseParts(const char *buf, int flags)
+{
+    ftpListParts *p = NULL;
+    char *t = NULL;
+    const char *ct = NULL;
+    char *tokens[MAX_TOKENS];
+    int i;
+    int n_tokens;
+    static char sbuf[128];
+    char *xbuf = NULL;
+    if (buf == NULL)
+       return NULL;
+    if (*buf == '\0')
+       return NULL;
+    p = xcalloc(1, sizeof(ftpListParts));
+    n_tokens = 0;
+    for (i = 0; i < MAX_TOKENS; i++)
+       tokens[i] = (char *) NULL;
+    xbuf = xstrdup(buf);
+    for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
+       tokens[n_tokens++] = xstrdup(t);
+    xfree(xbuf);
+    /* locate the Month field */
+    for (i = 3; i < n_tokens - 3; i++) {
+       if (!is_month(tokens[i]))       /* Month */
+           continue;
+       if (!sscanf(tokens[i - 1], "%[0-9]", sbuf))     /* Size */
+           continue;
+       if (!sscanf(tokens[i + 1], "%[0-9]", sbuf))     /* Day */
+           continue;
+       if (!sscanf(tokens[i + 2], "%[0-9:]", sbuf))    /* Yr | hh:mm */
+           continue;
+       p->type = *tokens[0];
+       p->size = atoi(tokens[i - 1]);
+       sprintf(sbuf, "%s %2s %5s",
+           tokens[i], tokens[i + 1], tokens[i + 2]);
+       if (!strstr(buf, sbuf))
+           sprintf(sbuf, "%s %2s %-5s",
+               tokens[i], tokens[i + 1], tokens[i + 2]);
+       if ((t = strstr(buf, sbuf))) {
+           p->date = xstrdup(sbuf);
+           if (BIT_TEST(flags, FTP_SKIP_WHITESPACE)) {
+               t += strlen(sbuf);
+               while (strchr(w_space, *t))
+                   t++;
+           } else {
+               /* XXX assumes a single space between date and filename
+                * suggested by:  Nathan.Bailey@cc.monash.edu.au and
+                * Mike Battersby <mike@starbug.bofh.asn.au> */
+               t += strlen(sbuf) + 1;
+           }
+           p->name = xstrdup(t);
+           if ((t = strstr(p->name, " -> "))) {
+               *t = '\0';
+               p->link = xstrdup(t + 4);
+           }
+       }
+       break;
+    }
+    /* try it as a DOS listing */
+    if (n_tokens > 3 && p->name == NULL &&
+       sscanf(tokens[0], "%[0-9]-%[0-9]-%[0-9]", sbuf, sbuf, sbuf) == 3 &&
+    /* 04-05-70 */
+       sscanf(tokens[1], "%[0-9]:%[0-9]%[AaPp]%[Mm]", sbuf, sbuf, sbuf, sbuf) == 4) {
+       /* 09:33PM */
+       if (!strcasecmp(tokens[2], "<dir>")) {
+           p->type = 'd';
+       } else {
+           p->type = '-';
+           p->size = atoi(tokens[2]);
+       }
+       sprintf(sbuf, "%s %s", tokens[0], tokens[1]);
+       p->date = xstrdup(sbuf);
+       p->name = xstrdup(tokens[3]);
+    }
+    /* Try EPLF format; carson@lehman.com */
+    if (p->name == NULL && buf[0] == '+') {
+       ct = buf + 1;
+       p->type = 0;
+       while (ct && *ct) {
+           switch (*ct) {
+           case '\t':
+               sscanf(ct + 1, "%[^,]", sbuf);
+               p->name = xstrdup(sbuf);
+               break;
+           case 's':
+               sscanf(ct + 1, "%d", &(p->size));
+               break;
+           case 'm':
+               sscanf(ct + 1, "%d", &i);
+               p->date = xstrdup(ctime((time_t *) & i));
+               *(strstr(p->date, "\n")) = '\0';
+               break;
+           case '/':
+               p->type = 'd';
+               break;
+           case 'r':
+               p->type = '-';
+               break;
+           case 'i':
+               break;
+           default:
+               break;
+           }
+           ct = strstr(ct, ",");
+           if (ct) {
+               ct++;
+           }
+       }
+       if (p->type == 0) {
+           p->type = '-';
+       }
+    }
+    for (i = 0; i < n_tokens; i++)
+       xfree(tokens[i]);
+    if (p->name == NULL) {
+       xfree(p->date);
+       xfree(p);
+       p = NULL;
+    }
+    return p;
+}
+
+static const char *
+dots_fill(size_t len)
+{
+    static char buf[256];
+    int 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 = (int) len; i < Config.Ftp.list_width; i++)
+       buf[i - len] = (i % 2) ? '.' : ' ';
+    buf[i - len] = '\0';
+    return buf;
+}
+
+static char *
+ftpHtmlifyListEntry(char *line, int flags)
+{
+    LOCAL_ARRAY(char, link, 2048);
+    LOCAL_ARRAY(char, icon, 2048);
+    LOCAL_ARRAY(char, html, 8192);
+    char *ename = NULL;
+    size_t width = Config.Ftp.list_width;
+    ftpListParts *parts;
+    /* check .. as special case */
+    if (!strcmp(line, "..")) {
+       sprintf(icon, "<IMG BORDER=0 SRC=\"%s%s%s\" ALT=\"%-6s\">",
+           Config.Ftp.icon_prefix,
+           "gopher-menu",
+           Config.Ftp.icon_suffix,
+           "[DIR]");
+       sprintf(link, "<A HREF=\"%s\">%s</A>", "../", "Parent Directory");
+       sprintf(html, "%s %s\n", icon, link);
+       return html;
+    }
+    if (strlen(line) > 1024) {
+       sprintf(html, "%s\n", line);
+       return html;
+    }
+    if ((parts = ftpListParseParts(line, flags)) == NULL) {
+       sprintf(html, "%s\n", line);
+       return html;
+    }
+    if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
+       *html = '\0';
+       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';
+       }
+    }
+    ename = xstrdup(rfc1738_escape(parts->name));
+    switch (parts->type) {
+    case 'd':
+       sprintf(icon, "<IMG SRC=\"%sgopher-%s%s\" ALT=\"%-6s\">",
+           Config.Ftp.icon_prefix,
+           "menu",
+           Config.Ftp.icon_suffix,
+           "[DIR]");
+       sprintf(link, "<A HREF=\"%s/\">%s</A>%s",
+           ename,
+           parts->showname,
+           dots_fill(strlen(parts->showname)));
+       sprintf(html, "%s %s  [%s]\n",
+           icon,
+           link,
+           parts->date);
+       break;
+    case 'l':
+       sprintf(icon, "<IMG SRC=\"%sgopher-%s%s\" ALT=\"%-6s\">",
+           Config.Ftp.icon_prefix,
+           mimeGetIcon(parts->link),
+           Config.Ftp.icon_suffix,
+           "[LINK]");
+       sprintf(link, "<A HREF=\"%s\">%s</A>%s",
+           ename,
+           parts->showname,
+           dots_fill(strlen(parts->showname)));
+       sprintf(html, "%s %s  [%s]\n",
+           icon,
+           link,
+           parts->date);
+       break;
+    case '-':
+    default:
+       sprintf(icon, "<IMG SRC=\"%sgopher-%s%s\" ALT=\"%-6s\">",
+           Config.Ftp.icon_prefix,
+           mimeGetIcon(parts->name),
+           Config.Ftp.icon_suffix,
+           "[FILE]");
+       sprintf(link, "<A HREF=\"%s\">%s</A>%s",
+           ename,
+           parts->showname,
+           dots_fill(strlen(parts->showname)));
+       sprintf(html, "%s %s  [%s] %6dk\n",
+           icon,
+           link,
+           parts->date,
+           parts->size);
+       break;
+    }
+    ftpListPartsFree(&parts);
+    xfree(ename);
+    return html;
+}
+
+static void
+ftpParseListing(FtpStateData * ftpState, int len)
+{
+    char *buf = ftpState->data.buf;
+    char *end = buf + ftpState->data.offset + len - 1;
+    char *line = get_free_4k_page();
+    char *s;
+    char *t;
+    size_t linelen;
+    StoreEntry *e = ftpState->entry;
+    debug(0, 0, "buf=\n%s|\n", buf);
+    while (*end != '\r' && *end != '\n' && end > buf)
+       end--;
+    if (end == buf) {
+       debug(0, 0, "ftpParseListing: didn't find end\n");
+       return;
+    }
+    end++;
+    for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
+       linelen = strcspn(s, crlf) + 1;
+       if (linelen > 4096)
+           linelen = 4096;
+       xstrncpy(line, s, linelen);
+       debug(0, 0, "%s\n", line);
+       if (!strncmp(line, "total", 5))
+           continue;
+       t = ftpHtmlifyListEntry(line, ftpState->flags);
+       assert(t != NULL);
+       storeAppend(e, t, strlen(t));
+    }
+    if (end - buf == len)
+       return;
+    /* must copy partial line to beginning of buf */
+    linelen = ftpState->data.offset + len - (end - buf) + 1;
+    debug(0, 0, "len=%d\n", len);
+    debug(0, 0, "buf=%d\n", buf);
+    debug(0, 0, "end=%d\n", end);
+    debug(0, 0, "linelen=%d\n", linelen);
+    assert(0 < linelen);
+    if (linelen > 4096)
+       linelen = 4096;
+    xstrncpy(line, end, linelen);
+    xstrncpy(ftpState->data.buf, line, ftpState->data.size);
+    ftpState->data.offset = strlen(ftpState->data.buf);
+    debug(0, 0, "offset=%d\n", ftpState->data.offset);
+}
 
-/* This will be called when data is ready to be read from fd.  Read until
- * error or connection closed. */
 static void
-ftpReadReply(int fd, void *data)
+ftpReadData(int fd, void *data)
 {
     FtpStateData *ftpState = data;
     LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF);
@@ -272,11 +670,11 @@ ftpReadReply(int fd, void *data)
     int off;
     int bin;
     StoreEntry *entry = NULL;
-
+    assert(fd == ftpState->data.fd);
     entry = ftpState->entry;
-    if (protoAbortFetch(entry)) { 
+    if (protoAbortFetch(entry)) {
        squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
-       comm_close(fd);
+       ftpDataTransferDone(ftpState);
        return;
     }
     /* check if we want to defer reading */
@@ -285,18 +683,12 @@ ftpReadReply(int fd, void *data)
     if ((clen - off) > FTP_DELETE_GAP) {
        if (entry->flag & CLIENT_ABORT_REQUEST) {
            squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
-           comm_close(fd);
+           ftpDataTransferDone(ftpState);
        }
        IOStats.Ftp.reads_deferred++;
-       debug(11, 3, "ftpReadReply: Read deferred for Object: %s\n",
+       debug(9, 3, "ftpReadData: Read deferred for Object: %s\n",
            entry->url);
-       debug(11, 3, "                Current Gap: %d bytes\n", clen - off);
-       /* reschedule, so it will be automatically reactivated
-        * when Gap is big enough. */
-       commSetSelect(fd,
-           COMM_SELECT_READ,
-           ftpReadReply,
-           data, 0);
+       commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0);
        if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
            commSetTimeout(fd, Config.Timeout.defer, NULL, NULL);
            BIT_SET(entry->flag, READ_DEFERRED);
@@ -307,10 +699,17 @@ ftpReadReply(int fd, void *data)
     } else {
        BIT_RESET(entry->flag, READ_DEFERRED);
     }
+    if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
+       if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
+           ftpListingStart(ftpState);
     errno = 0;
-    len = read(fd, buf, SQUID_TCP_SO_RCVBUF);
+    memset(ftpState->data.buf + ftpState->data.offset, '\0',
+       ftpState->data.size - ftpState->data.offset);
+    len = read(fd,
+       ftpState->data.buf + ftpState->data.offset,
+       ftpState->data.size - ftpState->data.offset);
     fd_bytes(fd, len, FD_READ);
-    debug(9, 5, "ftpReadReply: FD %d, Read %d bytes\n", fd, len);
+    debug(9, 5, "ftpReadData: FD %d, Read %d bytes\n", fd, len);
     if (len > 0) {
        commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
        IOStats.Ftp.reads++;
@@ -319,171 +718,40 @@ ftpReadReply(int fd, void *data)
        IOStats.Ftp.read_hist[bin]++;
     }
     if (len < 0) {
-       debug(50, 1, "ftpReadReply: read error: %s\n", xstrerror());
+       debug(50, 1, "ftpReadData: read error: %s\n", xstrerror());
        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
-           /* reinstall handlers */
-           /* XXX This may loop forever */
            commSetSelect(fd, COMM_SELECT_READ,
-               ftpReadReply, data, 0);
-           /* note there is no ftpReadReplyTimeout.  Timeouts are handled
-            * by `ftpget'. */
+               ftpReadData, data, 0);
        } else {
            BIT_RESET(entry->flag, ENTRY_CACHABLE);
            storeReleaseRequest(entry);
            squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
-           comm_close(fd);
+           ftpDataTransferDone(ftpState);
        }
     } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
        squid_error_entry(entry,
            ERR_ZERO_SIZE_OBJECT,
            errno ? xstrerror() : NULL);
-       comm_close(fd);
+       ftpDataTransferDone(ftpState);
     } else if (len == 0) {
        /* Connection closed; retrieval done. */
-       if (!ftpState->got_marker) {
-           /* If we didn't see the magic marker, assume the transfer
-            * failed and arrange so the object gets ejected and
-            * never gets to disk. */
-           debug(9, 1, "ftpReadReply: Purging '%s'\n", entry->url);
-           storeNegativeCache(entry);
-           BIT_RESET(entry->flag, ENTRY_CACHABLE);
-           storeReleaseRequest(entry);
-       } else {
-           storeTimestampsSet(entry);
-       }
+       if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
+           ftpListingFinish(ftpState);
+       storeTimestampsSet(entry);
        storeComplete(entry);
-       comm_close(fd);
+       ftpDataTransferDone(ftpState);
     } else if (entry->flag & CLIENT_ABORT_REQUEST) {
        squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
-       comm_close(fd);
+       ftpDataTransferDone(ftpState);
     } else {
-       if (ftpState->got_marker) {
-           /* oh, this is so gross -- we found the marker at the
-            * end of the previous read, but theres more data!
-            * So put the marker back in. */
-           storeAppend(entry, MAGIC_MARKER, MAGIC_MARKER_SZ);
-       }
-       /* check for a magic marker at the end of the read */
-       ftpState->got_marker = 0;
-       if (len >= MAGIC_MARKER_SZ) {
-           if (!memcmp(MAGIC_MARKER, buf + len - MAGIC_MARKER_SZ, MAGIC_MARKER_SZ)) {
-               ftpState->got_marker = 1;
-               len -= MAGIC_MARKER_SZ;
-           }
-       }
-       storeAppend(entry, buf, len);
-       if (ftpState->reply_hdr_state < 2 && len > 0)
-           ftpProcessReplyHeader(data, buf, len);
-       commSetSelect(fd,
-           COMM_SELECT_READ,
-           ftpReadReply,
-           data, 0);
+       if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
+           ftpParseListing(ftpState, len);
+       else
+           storeAppend(entry, buf, len);
+       commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0);
     }
 }
 
-static void
-ftpSendComplete(int fd, char *buf, int size, int errflag, void *data)
-{
-    FtpStateData *ftpState = (FtpStateData *) data;
-    StoreEntry *entry = NULL;
-
-    entry = ftpState->entry;
-    debug(9, 5, "ftpSendComplete: FD %d: size %d: errflag %d.\n",
-       fd, size, errflag);
-
-    if (buf) {
-       put_free_8k_page(buf);  /* Allocated by ftpSendRequest. */
-       buf = NULL;
-    }
-    if (errflag) {
-       squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
-       comm_close(fd);
-       return;
-    }
-    commSetSelect(ftpState->ftp_fd,
-       COMM_SELECT_READ,
-       ftpReadReply,
-       ftpState, 0);
-}
-
-static void
-ftpSendRequest(int fd, void *data)
-{
-    FtpStateData *ftpState = data;
-    char *path = NULL;
-    const char *mode = NULL;
-    char *buf = NULL;
-    LOCAL_ARRAY(char, tbuf, BUFSIZ);
-    LOCAL_ARRAY(char, opts, BUFSIZ);
-    const char *const space = " ";
-    char *s = NULL;
-    int got_timeout = 0;
-    int got_negttl = 0;
-
-    debug(9, 5, "ftpSendRequest: FD %d\n", fd);
-
-    buf = get_free_8k_page();
-
-    path = ftpState->request->urlpath;
-    mode = "-";                        /* let ftpget figure it out */
-
-    /* Start building the buffer ... */
-    strcat(buf, Config.Program.ftpget);
-    strcat(buf, space);
-
-    xstrncpy(opts, Config.Program.ftpget_opts, BUFSIZ);
-    for (s = strtok(opts, w_space); s; s = strtok(NULL, w_space)) {
-       strcat(buf, s);
-       strcat(buf, space);
-       if (!strncmp(s, "-t", 2))
-           got_timeout = 1;
-       if (!strncmp(s, "-n", 2))
-           got_negttl = 1;
-    }
-    if (!got_timeout) {
-       sprintf(tbuf, "-t %d ", Config.Timeout.read);
-       strcat(buf, tbuf);
-    }
-    if (!got_negttl) {
-       sprintf(tbuf, "-n %d ", Config.negativeTtl);
-       strcat(buf, tbuf);
-    }
-    if (ftpState->request->port) {
-       sprintf(tbuf, "-P %d ", ftpState->request->port);
-       strcat(buf, tbuf);
-    }
-    if ((s = Config.visibleHostname)) {
-       sprintf(tbuf, "-H %s ", s);
-       strcat(buf, tbuf);
-    }
-    if (ftpState->authenticated) {
-       strcat(buf, "-a ");
-    }
-    if (Config.Addrs.tcp_outgoing.s_addr != no_addr.s_addr) {
-       sprintf(tbuf, "-o %s ", inet_ntoa(Config.Addrs.tcp_outgoing));
-       strcat(buf, tbuf);
-    }
-    strcat(buf, "-h ");                /* httpify */
-    strcat(buf, "- ");         /* stdout */
-    strcat(buf, ftpState->request->host);
-    strcat(buf, space);
-    strcat(buf, *path ? path : "\"\"");
-    strcat(buf, space);
-    strcat(buf, mode);         /* A or I */
-    strcat(buf, space);
-    strcat(buf, *ftpState->user ? ftpState->user : "\"\"");
-    strcat(buf, space);
-    strcat(buf, *ftpState->password ? ftpState->password : "\"\"");
-    strcat(buf, "\n");
-    debug(9, 5, "ftpSendRequest: FD %d: buf '%s'\n", fd, buf);
-    comm_write(fd,
-       buf,
-       strlen(buf),
-       ftpSendComplete,
-       ftpState,
-       put_free_8k_page);
-}
-
 static char *
 ftpGetBasicAuth(const char *req_hdr)
 {
@@ -534,6 +802,89 @@ ftpCheckAuth(FtpStateData * ftpState, char *req_hdr)
     return 0;                  /* different username */
 }
 
+static void
+ftpCleanupUrlpath(FtpStateData * ftpState)
+{
+    request_t *request = ftpState->request;
+    int again;
+    int l;
+    char *t = NULL;
+    char *s = NULL;
+    do {
+       again = 0;
+       l = strlen(request->urlpath);
+       /* check for null path */
+       if (*request->urlpath == '\0') {
+           xstrncpy(request->urlpath, ".", MAX_URL);
+           EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
+           again = 1;
+       } else if ((l >= 1) && (*(request->urlpath + l - 1) == '/')) {
+           /* remove any trailing slashes from path */
+           *(request->urlpath + l - 1) = '\0';
+           EBIT_SET(ftpState->flags, FTP_ISDIR);
+           EBIT_RESET(ftpState->flags, FTP_USE_BASE);
+           again = 1;
+       } else if ((l >= 2) && (!strcmp(request->urlpath + l - 2, "/."))) {
+           /* remove trailing /. */
+           *(request->urlpath + l - 2) = '\0';
+           EBIT_SET(ftpState->flags, FTP_ISDIR);
+           EBIT_RESET(ftpState->flags, FTP_USE_BASE);
+           again = 1;
+       } else if (*request->urlpath == '/') {
+           /* remove any leading slashes from path */
+           t = xstrdup(request->urlpath + 1);
+           xstrncpy(request->urlpath, t, MAX_URL);
+           xfree(t);
+           again = 1;
+       } else if (!strncmp(request->urlpath, "./", 2)) {
+           /* remove leading ./ */
+           t = xstrdup(request->urlpath + 2);
+           xstrncpy(request->urlpath, t, MAX_URL);
+           xfree(t);
+           again = 1;
+       } else if ((t = strstr(request->urlpath, "/./"))) {
+           /* remove /./ */
+           s = xstrdup(t + 2);
+           xstrncpy(t, s, strlen(s));
+           xfree(s);
+           again = 1;
+       } else if ((t = strstr(request->urlpath, "//"))) {
+           /* remove // */
+           s = xstrdup(t + 1);
+           xstrncpy(t, s, strlen(s));
+           xfree(s);
+           again = 1;
+       }
+    } while (again);
+}
+
+
+static void
+ftpBuildTitleUrl(FtpStateData * ftpState)
+{
+    request_t *request = ftpState->request;
+    size_t len;
+    char *t;
+    len = 64
+       + strlen(ftpState->user)
+       + strlen(ftpState->password)
+       + strlen(request->host)
+       + strlen(request->urlpath);
+    t = ftpState->title_url = xcalloc(len, 1);
+    strcat(t, "ftp://");
+    if (strcmp(ftpState->user, "anonymous")) {
+       strcat(t, ftpState->user);
+       strcat(t, "@");
+    }
+    strcat(t, request->host);
+    if (request->port != urlDefaultPort(PROTO_FTP))
+       sprintf(&t[strlen(t)], ":%d", request->port);
+    strcat(t, "/");
+    if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR))
+       strcat(t, request->urlpath);
+}
+
+
 void
 ftpStart(request_t * request, StoreEntry * entry)
 {
@@ -542,15 +893,16 @@ ftpStart(request_t * request, StoreEntry * entry)
     FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData));
     char *req_hdr;
     char *response;
+    int fd;
     debug(9, 3, "FtpStart: '%s'\n", entry->url);
-    if (ftpget_server_write < 0) {
-       squid_error_entry(entry, ERR_FTP_DISABLED, NULL);
-       return;
-    }
     storeLockObject(entry);
     ftpState->entry = entry;
     req_hdr = entry->mem_obj->request_hdr;
     ftpState->request = requestLink(request);
+    ftpState->ctrl.fd = -1;
+    ftpState->data.fd = -1;
+    EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED);
+    EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED);
     if (!ftpCheckAuth(ftpState, req_hdr)) {
        /* This request is not fully authenticated */
        if (request->port == 21) {
@@ -566,32 +918,51 @@ ftpStart(request_t * request, StoreEntry * entry)
        ftpStateFree(-1, ftpState);
        return;
     }
+    ftpCleanupUrlpath(ftpState);
+    ftpBuildTitleUrl(ftpState);
     debug(9, 5, "FtpStart: host=%s, path=%s, user=%s, passwd=%s\n",
        ftpState->request->host, ftpState->request->urlpath,
        ftpState->user, ftpState->password);
-    ftpState->ftp_fd = comm_open(SOCK_STREAM,
+    fd = comm_open(SOCK_STREAM,
        0,
-       local_addr,
+       Config.Addrs.tcp_outgoing,
        0,
        COMM_NONBLOCKING,
        url);
-    if (ftpState->ftp_fd == COMM_ERROR) {
-       squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
-       ftpStateFree(-1, ftpState);
+    if (fd == COMM_ERROR) {
+       debug(9, 4, "ftpStart: Failed because we're out of sockets.\n");
+       squid_error_entry(entry, ERR_NO_FDS, xstrerror());
        return;
     }
-    /* Pipe/socket created ok */
-    /* register close handler */
-    comm_add_close_handler(ftpState->ftp_fd,
-       ftpStateFree,
-       ftpState);
-    commSetTimeout(ftpState->ftp_fd,
-       Config.Timeout.connect,
-       ftpTimeout,
-       ftpState);
-    commConnectStart(ftpState->ftp_fd,
-       localhost,
-       ftpget_port,
+    ftpState->ctrl.fd = fd;
+    comm_add_close_handler(fd, ftpStateFree, ftpState);
+    commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
+    ipcache_nbgethostbyname(request->host, fd, ftpConnect, ftpState);
+}
+
+static void
+ftpConnect(int fd, const ipcache_addrs * ia, void *data)
+{
+    FtpStateData *ftpState = data;
+    request_t *request = ftpState->request;
+    StoreEntry *entry = ftpState->entry;
+    EBIT_RESET(ftpState->flags, FTP_IP_LOOKUP_PENDING);
+    debug(0, 0, "ftpConnect\n");
+    assert(fd == ftpState->ctrl.fd);
+    if (ia == NULL) {
+       debug(9, 4, "ftpConnect: Unknown host: %s\n", request->host);
+       squid_error_entry(entry, ERR_DNS_FAIL, dns_error_message);
+       comm_close(fd);
+       return;
+    }
+    debug(0, 0, "ftpConnect: %s is %s\n", request->host,
+       inet_ntoa(ia->in_addrs[0]));
+    debug(0, 0, "ftpConnect: port %d\n", (int) request->port);
+    /* Open connection. */
+    commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
+    commConnectStart(ftpState->ctrl.fd,
+       request->host,
+       request->port,
        ftpConnectDone,
        ftpState);
 }
@@ -600,149 +971,587 @@ static void
 ftpConnectDone(int fd, int status, void *data)
 {
     FtpStateData *ftpState = data;
+    debug(0, 0, "ftpConnectDone\n");
     if (status == COMM_ERROR) {
        squid_error_entry(ftpState->entry, ERR_CONNECT_FAIL, xstrerror());
        comm_close(fd);
        return;
     }
-    commSetNonBlocking(fd);
-    (void) fd_note(fd, ftpState->entry->url);
-    /* Install connection complete handler. */
-    fd_note(fd, ftpState->entry->url);
-    commSetSelect(fd,
-       COMM_SELECT_WRITE,
-       ftpSendRequest,
-       data, 0);
-    if (opt_no_ipcache)
-       ipcacheInvalidate(ftpState->request->host);
+    ftpState->state = BEGIN;
+    ftpState->ctrl.buf = get_free_4k_page();
+    ftpState->ctrl.freefunc = put_free_4k_page;
+    ftpState->ctrl.size = 4096;
+    ftpState->ctrl.offset = 0;
+    ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF);
+    ftpState->data.size = SQUID_TCP_SO_RCVBUF;
+    ftpState->data.freefunc = xfree;
+    commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
 }
 
+/* ====================================================================== */
+
+
 static void
-ftpServerClosed(int fd, void *nodata)
+ftpWriteCommand(const char *buf, FtpStateData * ftpState)
 {
-    static time_t last_restart = 0;
-    comm_close(fd);
-    if (squid_curtime - last_restart < 2) {
-       debug(9, 0, "ftpget server failing too rapidly\n");
-       debug(9, 0, "WARNING: FTP access is disabled!\n");
-       ftpget_server_write = -1;
-       ftpget_server_read = -1;
+    debug(0, 0, "ftpWriteCommand: %s\n", buf);
+    comm_write(ftpState->ctrl.fd,
+       xstrdup(buf),
+       strlen(buf),
+       ftpWriteCommandCallback,
+       ftpState,
+       xfree);
+    commSetSelect(ftpState->ctrl.fd,
+       COMM_SELECT_READ,
+       ftpReadControlReply,
+       ftpState,
+       0);
+}
+
+static void
+ftpWriteCommandCallback(int fd, char *buf, int size, int errflag, void *data)
+{
+    FtpStateData *ftpState = data;
+    StoreEntry *entry = ftpState->entry;
+    debug(0, 0, "ftpWriteCommandCallback: wrote %d bytes\n", size);
+    if (errflag) {
+       BIT_RESET(entry->flag, ENTRY_CACHABLE);
+       storeReleaseRequest(entry);
+       squid_error_entry(entry, ERR_WRITE_ERROR, xstrerror());
+       comm_close(fd);
+    }
+}
+
+static wordlist *
+ftpParseControlReply(char *buf, size_t len, int *codep)
+{
+    char *s;
+    int complete = 0;
+    wordlist *head;
+    wordlist *list;
+    wordlist **tail = &head;
+    off_t offset;
+    size_t linelen;
+    int code;
+    debug(0, 0, "ftpParseControlReply\n");
+    if (*(buf + len - 1) != '\n')
+       return NULL;
+    for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) {
+       linelen = strcspn(s, crlf) + 1;
+       if (linelen > 3)
+           complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
+       if (complete)
+           code = atoi(s);
+       offset = 0;
+       if (linelen > 3)
+           if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
+               offset = 4;
+       list = xcalloc(1, sizeof(wordlist));
+       list->key = xmalloc(linelen - offset);
+       xstrncpy(list->key, s + offset, linelen - offset);
+       debug(38, 0, "%s\n", list->key);
+       *tail = list;
+       tail = &list->next;
+    }
+    if (!complete)
+       wordlistDestroy(&head);
+    if (codep)
+       *codep = code;
+    return head;
+}
+
+static void
+ftpReadControlReply(int fd, void *data)
+{
+    FtpStateData *ftpState = data;
+    StoreEntry *entry = ftpState->entry;
+    char *oldbuf;
+    wordlist **W;
+    int len;
+    debug(0, 0, "ftpReadControlReply\n");
+    assert(ftpState->ctrl.offset < ftpState->ctrl.size);
+    len = read(fd,
+       ftpState->ctrl.buf + ftpState->ctrl.offset,
+       ftpState->ctrl.size - ftpState->ctrl.offset);
+    fd_bytes(fd, len, FD_READ);
+    debug(9, 5, "ftpReadControlReply: FD %d, Read %d bytes\n", fd, len);
+    if (len > 0)
+       commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
+    if (entry->flag & CLIENT_ABORT_REQUEST) {
+       squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
+       comm_close(fd);
        return;
     }
-    last_restart = squid_curtime;
-    debug(9, 1, "Restarting ftpget server...\n");
-    (void) ftpInitialize();
+    if (len < 0) {
+       debug(50, 1, "ftpReadControlReply: read error: %s\n", xstrerror());
+       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+           commSetSelect(fd,
+               COMM_SELECT_READ,
+               ftpReadControlReply,
+               ftpState,
+               0);
+       } else {
+           BIT_RESET(entry->flag, ENTRY_CACHABLE);
+           storeReleaseRequest(entry);
+           squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
+           comm_close(fd);
+       }
+       return;
+    }
+    if (len == 0) {
+       debug(38, 0, "Read 0 bytes from FTP control socket?\n");
+       BIT_RESET(entry->flag, ENTRY_CACHABLE);
+       storeReleaseRequest(entry);
+       squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
+       comm_close(fd);
+       return;
+    }
+    assert(len <= ftpState->ctrl.size);
+    wordlistDestroy(&ftpState->ctrl.message);
+    ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len,
+       &ftpState->ctrl.replycode);
+    if (ftpState->ctrl.message == NULL) {
+       if (len == ftpState->ctrl.size) {
+           oldbuf = ftpState->ctrl.buf;
+           ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1);
+           xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size);
+           ftpState->ctrl.size <<= 1;
+           ftpState->ctrl.freefunc(oldbuf);
+           ftpState->ctrl.freefunc = xfree;
+       }
+       commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
+       return;
+    }
+    for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next);
+    ftpState->ctrl.last_message = (*W)->key;
+    safe_free(*W);
+    ftpState->ctrl.offset = 0;
+    FTP_SM_FUNCS[ftpState->state] (ftpState);
 }
 
-void
-ftpServerClose(void)
+/* ====================================================================== */
+
+static void
+ftpReadWelcome(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    char *p = NULL;
+    debug(0, 0, "ftpReadWelcome\n");
+    if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY))
+       ftpState->login_att++;
+    if (code == 220) {
+       if (ftpState->ctrl.message)
+           if (strstr(ftpState->ctrl.message->key, "NetWare"))
+               EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
+       if (ftpState->proxy_host != NULL)
+           sprintf(cbuf, "USER %s@%s\r\n",
+               ftpState->user,
+               ftpState->request->host);
+       else
+           sprintf(cbuf, "USER %s\r\n", ftpState->user);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_USER;
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpReadUser(FtpStateData * ftpState)
 {
-    /* NOTE: this function will be called repeatedly while shutdown is
-     * pending */
-    if (ftpget_server_read < 0)
+    int code = ftpState->ctrl.replycode;
+    debug(0, 0, "ftpReadUser\n");
+    if (code == 230) {
+       ftpReadPass(ftpState);
+    } else if (code == 331) {
+       sprintf(cbuf, "PASS %s\r\n", ftpState->password);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_PASS;
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpReadPass(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    char *t;
+    char *filename;
+    char mode;
+    debug(0, 0, "ftpReadPass\n");
+    if (code == 230) {
+       t = strrchr(ftpState->request->urlpath, '/');
+       filename = t ? t + 1 : ftpState->request->urlpath;
+       mode = mimeGetTransferMode(filename);
+       sprintf(cbuf, "TYPE %c\r\n", mode);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_TYPE;
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpReadType(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    wordlist *w;
+    wordlist **T;
+    char *path;
+    char *d;
+    debug(38, 1, "This is ftpReadType\n");
+    if (code == 200) {
+       if (EBIT_TEST(ftpState->flags, FTP_ROOT_DIR)) {
+           ftpSendPasv(ftpState);
+       } else {
+           path = xstrdup(ftpState->request->urlpath);
+           T = &ftpState->pathcomps;
+           for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) {
+               w = xcalloc(1, sizeof(wordlist));
+               w->key = xstrdup(d);
+               *T = w;
+               T = &w->next;
+           }
+           ftpSendCwd(ftpState);
+       }
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpSendCwd(FtpStateData * ftpState)
+{
+    wordlist *w;
+    debug(0, 0, "ftpSendCwd\n");
+    if ((w = ftpState->pathcomps) == NULL) {
+       debug(0, 0, "the final component was a directory\n");
+       EBIT_SET(ftpState->flags, FTP_ISDIR);
+       if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR))
+           strcat(ftpState->title_url, "/");
+       ftpSendPasv(ftpState);
        return;
-    commSetSelect(ftpget_server_read,
-       COMM_SELECT_READ,
-       NULL,
-       NULL, 0);
-    fd_close(ftpget_server_read);
-    close(ftpget_server_read);
-    ftpget_server_read = -1;
-    fd_close(ftpget_server_write);
-    close(ftpget_server_write);
-    ftpget_server_write = -1;
-}
-
-
-int
-ftpInitialize(void)
-{
-    pid_t pid;
-    int cfd;
-    int squid_to_ftpget[2];
-    int ftpget_to_squid[2];
-    LOCAL_ARRAY(char, pbuf, 128);
-    char *ftpget = Config.Program.ftpget;
-    struct sockaddr_in S;
-    int len;
-    struct timeval slp;
+    }
+    sprintf(cbuf, "CWD %s\r\n", w->key);
+    ftpWriteCommand(cbuf, ftpState);
+    ftpState->state = SENT_CWD;
+}
 
-    if (!strcmp(ftpget, "none")) {
-       debug(9, 1, "ftpInitialize: ftpget is disabled.\n");
-       return -1;
+static void
+ftpReadCwd(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    size_t len = 0;
+    wordlist *w;
+    debug(38, 1, "This is ftpReadCwd\n");
+    w = ftpState->pathcomps;
+    assert(w != NULL);
+    if (code >= 200 && code < 300) {
+       if (ftpState->cwd_message)
+           wordlistDestroy(&ftpState->cwd_message);
+       ftpState->cwd_message = ftpState->ctrl.message;
+       ftpState->ctrl.message = NULL;
+       /* CWD OK */
+       ftpState->pathcomps = w->next;
+       xfree(w->key);
+       xfree(w);
+       ftpSendCwd(ftpState);
+    } else {
+       /* CWD FAILED */
+       while (w) {
+           len += (strlen(w->key) + 1);
+           w = w->next;
+       }
+       ftpState->filepath = xcalloc(len, 1);
+       for (w = ftpState->pathcomps; w; w = w->next) {
+           strcat(ftpState->filepath, w->key);
+           if (w->next)
+               strcat(ftpState->filepath, "/");
+       }
+       wordlistDestroy(&ftpState->pathcomps);
+       assert(*ftpState->filepath != '\0');
+       sprintf(cbuf, "MDTM %s\r\n", ftpState->filepath);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_MDTM;
     }
-    debug(9, 5, "ftpInitialize: Initializing...\n");
-    if (pipe(squid_to_ftpget) < 0) {
-       debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror());
-       return -1;
+}
+
+static void
+ftpReadMdtm(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadMdtm\n");
+    if (code == 213) {
+       ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_message);
+       assert(ftpState->filepath != NULL);
+       assert(*ftpState->filepath != '\0');
+       sprintf(cbuf, "SIZE %s\r\n", ftpState->filepath);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_SIZE;
+    } else if (code < 0) {
+       ftpFail(ftpState);
     }
-    if (pipe(ftpget_to_squid) < 0) {
-       debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror());
-       return -1;
+}
+
+static void
+ftpReadSize(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadSize\n");
+    if (code == 213) {
+       ftpState->size = atoi(ftpState->ctrl.last_message);
+       ftpSendPasv(ftpState);
+    } else if (code < 0) {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpSendPasv(FtpStateData * ftpState)
+{
+    int fd;
+    assert(ftpState->data.fd < 0);
+    if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
+       ftpSendPort(ftpState);
+       return;
     }
-    cfd = comm_open(SOCK_STREAM,
+    fd = comm_open(SOCK_STREAM,
        0,
-       local_addr,
+       Config.Addrs.tcp_outgoing,
        0,
-       COMM_NOCLOEXEC,
-       "ftpget -S socket");
-    debug(9, 5, "ftpget -S socket on FD %d\n", cfd);
-    if (cfd == COMM_ERROR) {
-       debug(9, 0, "ftpInitialize: Failed to create socket\n");
-       return -1;
-    }
-    len = sizeof(S);
-    memset(&S, '\0', len);
-    if (getsockname(cfd, (struct sockaddr *) &S, &len) < 0) {
-       debug(50, 0, "ftpInitialize: getsockname: %s\n", xstrerror());
-       comm_close(cfd);
-       return -1;
-    }
-    ftpget_port = ntohs(S.sin_port);
-    listen(cfd, Squid_MaxFD >> 2);
-    if ((pid = fork()) < 0) {
-       debug(50, 0, "ftpInitialize: fork: %s\n", xstrerror());
-       comm_close(cfd);
-       fatal("Failed to fork() for ftpget.");
-    }
-    if (pid != 0) {            /* parent */
-       comm_close(cfd);
-       close(squid_to_ftpget[0]);
-       close(ftpget_to_squid[1]);
-       fd_open(squid_to_ftpget[1], FD_PIPE, "squid -> ftpget");
-       fd_open(ftpget_to_squid[0], FD_PIPE, "squid <- ftpget");
-       commSetCloseOnExec(squid_to_ftpget[1]);
-       commSetCloseOnExec(ftpget_to_squid[0]);
-       /* if ftpget -S goes away, this handler should get called */
-       commSetSelect(ftpget_to_squid[0],
+       COMM_NONBLOCKING,
+       ftpState->entry->url);
+    if (fd < 0) {
+       ftpFail(ftpState);
+       return;
+    }
+    ftpState->data.fd = fd;
+    commSetTimeout(fd, Config.Timeout.read, ftpTimeout, ftpState);
+    sprintf(cbuf, "PASV\r\n");
+    ftpWriteCommand(cbuf, ftpState);
+    ftpState->state = SENT_PASV;
+}
+
+static void
+ftpReadPasv(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    int h1, h2, h3, h4;
+    int p1, p2;
+    int n;
+    u_short port;
+    int fd = ftpState->data.fd;
+    char *buf = ftpState->ctrl.last_message;
+    LOCAL_ARRAY(char, junk, 1024);
+    debug(38, 1, "This is ftpReadPasv\n");
+    if (code != 227) {
+       debug(38, 0, "PASV not supported by remote end\n");
+       ftpSendPort(ftpState);
+       return;
+    }
+    if (strlen(buf) > 1024) {
+       debug(38, 0, "Avoiding potential buffer overflow\n");
+       ftpSendPort(ftpState);
+       return;
+    }
+    /*  227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).  */
+    /*  ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
+    debug(0, 0, "scanning: %s\n", buf);
+    n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d",
+       junk, &h1, &h2, &h3, &h4, &p1, &p2);
+    if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
+       debug(38, 0, "Bad 227 reply\n");
+       debug(38, 0, "n=%d, p1=%d, p2=%d\n", n, p1, p2);
+       ftpSendPort(ftpState);
+       return;
+    }
+    sprintf(junk, "%d.%d.%d.%d", h1, h2, h3, h4);
+    if (!safe_inet_addr(junk, NULL)) {
+       debug(38, 0, "unsafe address (%s)\n", junk);
+       ftpSendPort(ftpState);
+       return;
+    }
+    port = ((p1 << 8) + p2);
+    debug(0, 0, "ftpReadPasv: connecting to %s, port %d\n", junk, port);
+    commConnectStart(fd, junk, port, ftpPasvCallback, ftpState);
+}
+
+static void
+ftpPasvCallback(int fd, int status, void *data)
+{
+    FtpStateData *ftpState = data;
+    debug(0, 0, "ftpPasvCallback\n");
+    if (status == COMM_ERROR) {
+       squid_error_entry(ftpState->entry, ERR_CONNECT_FAIL, xstrerror());
+       comm_close(fd);
+       return;
+    }
+    ftpRestOrList(ftpState);
+}
+
+static void
+ftpSendPort(FtpStateData * ftpState)
+{
+    debug(38, 1, "This is ftpSendPort\n");
+    EBIT_RESET(ftpState->flags, FTP_PASV_SUPPORTED);
+}
+
+static void
+ftpReadPort(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadPort\n");
+}
+
+static void
+ftpRestOrList(FtpStateData * ftpState)
+{
+    debug(38, 1, "This is ftpRestOrList\n");
+    if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
+       sprintf(cbuf, "LIST\r\n");
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_LIST;
+    } else if (ftpState->restart_offset > 0) {
+       sprintf(cbuf, "REST\r\n");
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_REST;
+    } else {
+       assert(ftpState->filepath != NULL);
+       sprintf(cbuf, "RETR %s\r\n", ftpState->filepath);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_RETR;
+    }
+}
+
+static void
+ftpReadRest(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadRest\n");
+    assert(ftpState->restart_offset > 0);
+    if (code == 350) {
+       assert(ftpState->filepath != NULL);
+       sprintf(cbuf, "RETR %s\r\n", ftpState->filepath);
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_RETR;
+    } else if (code > 0) {
+       debug(0, 0, "ftpReadRest: REST not supported\n");
+       EBIT_RESET(ftpState->flags, FTP_REST_SUPPORTED);
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpReadList(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadList\n");
+    if (code == 150 || code == 125) {
+       ftpAppendSuccessHeader(ftpState);
+       commSetSelect(ftpState->data.fd,
            COMM_SELECT_READ,
-           ftpServerClosed,
-           NULL, 0);
-       ftpget_server_write = squid_to_ftpget[1];
-       ftpget_server_read = ftpget_to_squid[0];
-       slp.tv_sec = 0;
-       slp.tv_usec = 250000;
-       select(0, NULL, NULL, NULL, &slp);
-       return 0;
-    }
-    /* child */
-    /* give up all extra priviligies */
-    no_suid();
-    /* set up stdin,stdout */
-    dup2(squid_to_ftpget[0], 0);
-    dup2(ftpget_to_squid[1], 1);
-    dup2(fileno(debug_log), 2);
-    close(squid_to_ftpget[0]);
-    close(squid_to_ftpget[1]);
-    close(ftpget_to_squid[0]);
-    close(ftpget_to_squid[1]);
-    dup2(cfd, 3);              /* pass listening socket to ftpget */
-    /* inherit stdin,stdout,stderr */
-    for (cfd = 4; cfd <= Biggest_FD; cfd++)
-       (void) close(cfd);
-    sprintf(pbuf, "%d", ftpget_port);
-    execlp(ftpget, ftpget, "-S", pbuf, NULL);
-    debug(50, 0, "ftpInitialize: %s: %s\n", ftpget, xstrerror());
-    _exit(1);
-    return (1);                        /* eliminate compiler warning */
+           ftpReadData,
+           ftpState,
+           0);
+       ftpState->state = READING_DATA;
+       return;
+    } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
+       EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
+       sprintf(cbuf, "NLST\r\n");
+       ftpWriteCommand(cbuf, ftpState);
+       ftpState->state = SENT_NLST;
+    } else {
+       ftpFail(ftpState);
+       return;
+    }
+}
+
+static void
+ftpReadRetr(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadRetr\n");
+    if (code >= 100 && code < 200) {
+       debug(0, 0, "reading data channel\n");
+       ftpAppendSuccessHeader(ftpState);
+       commSetSelect(ftpState->data.fd,
+           COMM_SELECT_READ,
+           ftpReadData,
+           ftpState,
+           0);
+       ftpState->state = READING_DATA;
+    } else {
+       ftpFail(ftpState);
+    }
+}
+
+static void
+ftpReadTransferDone(FtpStateData * ftpState)
+{
+    int code = ftpState->ctrl.replycode;
+    debug(38, 1, "This is ftpReadTransferDone\n");
+    if (code != 226) {
+       debug(38, 1, "Got code %d after reading data, releasing entry\n");
+       storeReleaseRequest(ftpState->entry);
+    }
+}
+
+static void
+ftpDataTransferDone(FtpStateData * ftpState)
+{
+    assert(ftpState->data.fd >= 0);
+    comm_close(ftpState->data.fd);
+    ftpState->data.fd = -1;
+    sprintf(cbuf, "QUIT\r\n");
+    ftpWriteCommand(cbuf, ftpState);
+    ftpState->state = SENT_QUIT;
+}
+
+static void
+ftpReadQuit(FtpStateData * ftpState)
+{
+    comm_close(ftpState->ctrl.fd);
+}
+
+static void
+ftpFail(FtpStateData * ftpState)
+{
+    debug(0, 0, "ftpFail\n");
+    comm_close(ftpState->ctrl.fd);
+}
+
+static void
+ftpAppendSuccessHeader(FtpStateData * ftpState)
+{
+    char *mime_type = NULL;
+    char *mime_enc = NULL;
+    char *urlpath = ftpState->request->urlpath;
+    char *filename = NULL;
+    char *t = NULL;
+    StoreEntry *e = ftpState->entry;
+    if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT))
+       return;
+    assert(e->mem_obj->e_current_len == 0);
+    filename = (t = strrchr(urlpath, '/')) ? t + 1 : urlpath;
+    if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
+       mime_type = "text/html";
+    } else {
+       mime_type = mimeGetContentType(filename);
+       mime_enc = mimeGetContentEncoding(filename);
+    }
+    storeAppendPrintf(e, "HTTP/1.0 200 Gatewaying\r\n");
+    storeAppendPrintf(e, "Date: %s\r\n", mkrfc1123(squid_curtime));
+    storeAppendPrintf(e, "MIME-Version: 1.0\r\n");
+    storeAppendPrintf(e, "Server: Squid %s\r\n", version_string);
+    if (ftpState->size > 0)
+       storeAppendPrintf(e, "Content-Length: %d\r\n", ftpState->size);
+    if (mime_type)
+       storeAppendPrintf(e, "Content-Type: %s\r\n", mime_type);
+    if (mime_enc)
+       storeAppendPrintf(e, "Content-Encoding: %s\r\n", mime_enc);
+    if (ftpState->mdtm > 0)
+       storeAppendPrintf(e, "Last-Modified: %s\r\n", mkrfc1123(ftpState->mdtm));
+    storeAppendPrintf(e, "\r\n");
 }
index 92aaca35c59b7ad764c40f5337d73f68e27add9e..a9af7001df515a6fa53ab4fc0a8235a7883bd2c4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: main.cc,v 1.148 1997/05/16 07:42:38 wessels Exp $
+ * $Id: main.cc,v 1.149 1997/05/26 04:05:00 wessels Exp $
  *
  * DEBUG: section 1     Startup and Main Loop
  * AUTHOR: Harvest Derived
@@ -514,7 +514,6 @@ mainReconfigure(void)
     dnsOpenServers();
     redirectOpenServers();
     serverConnectionsOpen();
-    (void) ftpInitialize();
     if (theOutIcpConnection >= 0 && (!httpd_accel_mode || Config.Accel.withProxy))
        neighbors_open(theOutIcpConnection);
     debug(1, 0, "Ready to serve requests.\n");
@@ -564,7 +563,6 @@ mainInitialize(void)
     dnsOpenServers();
     redirectOpenServers();
     useragentOpenLog();
-    (void) ftpInitialize();
 
 #if MALLOC_DBG
     malloc_debug(0, malloc_debug_level);
@@ -692,14 +690,12 @@ main(int argc, char **argv)
     /* main loop */
     for (;;) {
        if (rotate_pending) {
-           ftpServerClose();
            icmpClose();
            _db_rotate_log();   /* cache.log */
            storeWriteCleanLogs();
            storeRotateLog();   /* store.log */
            stat_rotate_log();  /* access.log */
            useragentRotateLog();       /* useragent.log */
-           (void) ftpInitialize();
            icmpOpen();
            rotate_pending = 0;
        }
index ca2103d2af351f15ddc6b5c6688bbdcaad1b5eaf..b9420931e6eb3cdba449310cd2490d72e7fd6985 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: url.cc,v 1.55 1997/04/28 04:23:34 wessels Exp $
+ * $Id: url.cc,v 1.56 1997/05/26 04:05:01 wessels Exp $
  *
  * DEBUG: section 23    URL Parsing
  * AUTHOR: Duane Wessels
@@ -56,7 +56,6 @@ static char *ProtocolStr[] =
 
 static int url_acceptable[256];
 static const char *const hex = "0123456789abcdef";
-static int urlDefaultPort _PARAMS((protocol_t p));
 
 /* convert %xx in url string to a character 
  * Allocate a new string and return a pointer to converted string */
@@ -172,7 +171,7 @@ urlParseProtocol(const char *s)
 }
 
 
-static int
+int
 urlDefaultPort(protocol_t p)
 {
     switch (p) {