]> sjero.net Git - wget/blobdiff - src/ftp.c
[svn] Commit IPv6 support by Thomas Lussnig.
[wget] / src / ftp.c
index 7b33efd4ef36a3ebeedf68c6e1aa7b824b9188ba..e02c018b8478daa89ccaa8af299f54b0123ef66e 100644 (file)
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -1,20 +1,21 @@
 /* File Transfer Protocol support.
-   Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001
+   Free Software Foundation, Inc.
 
-This file is part of Wget.
+This file is part of GNU Wget.
 
-This program is free software; you can redistribute it and/or modify
+GNU Wget is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
-This program is distributed in the hope that it will be useful,
+GNU Wget is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
+along with Wget; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 
 #include <config.h>
@@ -32,9 +33,6 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include <sys/types.h>
 #include <assert.h>
 #include <errno.h>
-#ifndef WINDOWS
-# include <netdb.h>            /* for h_errno */
-#endif
 
 #include "wget.h"
 #include "utils.h"
@@ -50,17 +48,24 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #ifndef errno
 extern int errno;
 #endif
-#ifndef h_errno
-# ifndef __CYGWIN__
-extern int h_errno;
-# endif
-#endif
 
 /* File where the "ls -al" listing will be saved.  */
 #define LIST_FILENAME ".listing"
 
 extern char ftp_last_respline[];
 
+typedef struct
+{
+  int st;                      /* connection status */
+  int cmd;                     /* command code */
+  struct rbuf rbuf;            /* control connection buffer */
+  long dltime;                 /* time of the download */
+  enum stype rs;               /* remote system reported by ftp server */ 
+  char *id;                    /* initial directory */
+  char *target;                        /* target file name */
+} ccon;
+
+
 /* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
    the string S, and return the number converted to long, if found, 0
    otherwise.  */
@@ -107,20 +112,20 @@ ftp_expected_bytes (const char *s)
    connection to the server.  It always closes the data connection,
    and closes the control connection in case of error.  */
 static uerr_t
-getftp (struct urlinfo *u, long *len, long restval, ccon *con)
+getftp (struct url *u, long *len, long restval, ccon *con)
 {
   int csock, dtsock, res;
   uerr_t err;
   FILE *fp;
   char *user, *passwd, *respline;
   char *tms, *tmrate;
-  unsigned char pasv_addr[6];
   int cmd = con->cmd;
-  int passive_mode_open = 0;
+  int pasv_mode_open = 0;
   long expected_bytes = 0L;
 
   assert (con != NULL);
-  assert (u->local != NULL);
+  assert (con->target != NULL);
+
   /* Debug-check of the sanity of the request by making sure that LIST
      and RETR are never both requested (since we can handle only one
      at a time.  */
@@ -132,8 +137,6 @@ getftp (struct urlinfo *u, long *len, long restval, ccon *con)
   passwd = u->passwd;
   search_netrc (u->host, (const char **)&user, (const char **)&passwd, 1);
   user = user ? user : opt.ftp_acc;
-  if (!opt.ftp_pass)
-    opt.ftp_pass = ftp_getaddress ();
   passwd = passwd ? passwd : opt.ftp_pass;
   assert (user && passwd);
 
@@ -144,53 +147,34 @@ getftp (struct urlinfo *u, long *len, long restval, ccon *con)
     csock = RBUF_FD (&con->rbuf);
   else                         /* cmd & DO_LOGIN */
     {
+      char type_char;
+      struct address_list *al;
+
       /* Login to the server: */
 
       /* First: Establish the control connection.  */
-      logprintf (LOG_VERBOSE, _("Connecting to %s:%hu... "), u->host, u->port);
-      err = make_connection (&csock, u->host, u->port);
+
+      al = lookup_host (u->host, 0);
+      if (!al)
+       return HOSTERR;
+      set_connection_host_name (u->host);
+      csock = connect_to_many (al, u->port, 0);
+      set_connection_host_name (NULL);
+      address_list_release (al);
+
+      if (csock < 0)
+       return errno == ECONNREFUSED ? CONREFUSED : CONERROR;
+
       if (cmd & LEAVE_PENDING)
        rbuf_initialize (&con->rbuf, csock);
       else
        rbuf_uninitialize (&con->rbuf);
-      switch (err)
-       {
-         /* Do not close the socket in first several cases, since it
-            wasn't created at all.  */
-       case HOSTERR:
-         logputs (LOG_VERBOSE, "\n");
-         logprintf (LOG_NOTQUIET, "%s: %s\n", u->host, herrmsg (h_errno));
-         return HOSTERR;
-         break;
-       case CONSOCKERR:
-         logputs (LOG_VERBOSE, "\n");
-         logprintf (LOG_NOTQUIET, "socket: %s\n", strerror (errno));
-         return CONSOCKERR;
-         break;
-       case CONREFUSED:
-         logputs (LOG_VERBOSE, "\n");
-         logprintf (LOG_NOTQUIET, _("Connection to %s:%hu refused.\n"),
-                    u->host, u->port);
-         CLOSE (csock);
-         rbuf_uninitialize (&con->rbuf);
-         return CONREFUSED;
-       case CONERROR:
-         logputs (LOG_VERBOSE, "\n");
-         logprintf (LOG_NOTQUIET, "connect: %s\n", strerror (errno));
-         CLOSE (csock);
-         rbuf_uninitialize (&con->rbuf);
-         return CONERROR;
-         break;
-       default:
-         DO_NOTHING;
-         /* #### Hmm?  */
-       }
+
       /* Since this is a new connection, we may safely discard
         anything left in the buffer.  */
       rbuf_discard (&con->rbuf);
 
       /* Second: Login with proper USER/PASS sequence.  */
-      logputs (LOG_VERBOSE, _("connected!\n"));
       logprintf (LOG_VERBOSE, _("Logging in as %s ... "), user);
       if (opt.server_response)
        logputs (LOG_ALWAYS, "\n");
@@ -271,7 +255,7 @@ Error in server response, closing control connection.\n"));
          abort ();
          break;
        }
-      if (!opt.server_response)
+      if (!opt.server_response && err != FTPSRVERR)
        logputs (LOG_VERBOSE, _("done.    "));
 
       /* Fourth: Find the initial ftp directory */
@@ -281,8 +265,8 @@ Error in server response, closing control connection.\n"));
       err = ftp_pwd(&con->rbuf, &con->id);
       /* FTPRERR */
       switch (err)
-      {
-       case FTPRERR || FTPSRVERR :
+       {
+       case FTPRERR:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET, _("\
 Error in server response, closing control connection.\n"));
@@ -290,20 +274,49 @@ Error in server response, closing control connection.\n"));
          rbuf_uninitialize (&con->rbuf);
          return err;
          break;
+       case FTPSRVERR :
+         /* PWD unsupported -- assume "/". */
+         FREE_MAYBE (con->id);
+         con->id = xstrdup ("/");
+         break;
        case FTPOK:
          /* Everything is OK.  */
          break;
        default:
          abort ();
          break;
-      }
+       }
+      /* VMS will report something like "PUB$DEVICE:[INITIAL.FOLDER]".
+         Convert it to "/INITIAL/FOLDER" */ 
+      if (con->rs == ST_VMS)
+        {
+          char *path = strchr (con->id, '[');
+         char *pathend = path ? strchr (path + 1, ']') : NULL;
+         if (!path || !pathend)
+           DEBUGP (("Initial VMS directory not in the form [...]!\n"));
+         else
+           {
+             char *idir = con->id;
+             DEBUGP (("Preprocessing the initial VMS directory\n"));
+             DEBUGP (("  old = '%s'\n", con->id));
+             /* We do the conversion in-place by copying the stuff
+                between [ and ] to the beginning, and changing dots
+                to slashes at the same time.  */
+             *idir++ = '/';
+             for (++path; path < pathend; path++, idir++)
+               *idir = *path == '.' ? '/' : *path;
+             *idir = '\0';
+             DEBUGP (("  new = '%s'\n\n", con->id));
+           }
+       }
       if (!opt.server_response)
        logputs (LOG_VERBOSE, _("done.\n"));
 
       /* Fifth: Set the FTP type.  */
+      type_char = ftp_process_type (u->params);
       if (!opt.server_response)
-       logprintf (LOG_VERBOSE, "==> TYPE %c ... ", TOUPPER (u->ftp_type));
-      err = ftp_type (&con->rbuf, TOUPPER (u->ftp_type));
+       logprintf (LOG_VERBOSE, "==> TYPE %c ... ", type_char);
+      err = ftp_type (&con->rbuf, type_char);
       /* FTPRERR, WRITEFAILED, FTPUNKNOWNTYPE */
       switch (err)
        {
@@ -327,7 +340,7 @@ Error in server response, closing control connection.\n"));
          logputs (LOG_VERBOSE, "\n");
          logprintf (LOG_NOTQUIET,
                     _("Unknown type `%c', closing control connection.\n"),
-                    TOUPPER (u->ftp_type));
+                    type_char);
          CLOSE (csock);
          rbuf_uninitialize (&con->rbuf);
          return err;
@@ -348,56 +361,55 @@ Error in server response, closing control connection.\n"));
        logputs (LOG_VERBOSE, _("==> CWD not needed.\n"));
       else
        {
-         /* Change working directory. If the FTP host runs VMS and
-             the path specified is absolute, we will have to convert
-             it to VMS style as VMS does not like leading slashes */
+         char *target = u->dir;
+
          DEBUGP (("changing working directory\n"));
-         if (*(u->dir) == '/')
+
+         /* Change working directory.  To change to a non-absolute
+            Unix directory, we need to prepend initial directory
+            (con->id) to it.  Absolute directories "just work".  */
+
+         if (*target != '/')
            {
-             int pwd_len = strlen (con->id);
-             char *result = (char *)alloca (strlen (u->dir) + pwd_len + 10);
-             *result = '\0';
-             switch (con->rs)
-               {
-               case ST_VMS:
-                 {
-                   char *tmp_dir, *tmpp;
-                   STRDUP_ALLOCA (tmp_dir, u->dir);
-                   for (tmpp = tmp_dir; *tmpp; tmpp++)
-                     if (*tmpp=='/')
-                       *tmpp = '.';
-                   strcpy (result, con->id);
-                   /* pwd ends with ']', we have to get rid of it */
-                   result[pwd_len - 1]= '\0';
-                   strcat (result, tmp_dir);
-                   strcat (result, "]");
-                 }
-                 break;
-               case ST_UNIX:
-               case ST_WINNT:
-               case ST_MACOS:
-                 /* pwd_len == 1 means pwd = "/", but u->dir begins with '/'
-                    already */
-                 if (pwd_len > 1)
-                   strcpy (result, con->id);
-                 strcat (result, u->dir);
-                 DEBUGP(("\npwd=\"%s\"", con->id));
-                 DEBUGP(("\nu->dir=\"%s\"", u->dir));
-                 break;
-               default:
-                 abort ();
-                 break;
-               }
-             if (!opt.server_response)
-               logprintf (LOG_VERBOSE, "==> CWD %s ... ", result);
-             err = ftp_cwd (&con->rbuf, result);
+             int idlen = strlen (con->id);
+             char *ntarget = (char *)alloca (idlen + 1 + strlen (u->dir) + 1);
+             /* idlen == 1 means con->id = "/" */
+             sprintf (ntarget, "%s%s%s", con->id, idlen == 1 ? "" : "/",
+                      target);
+              DEBUGP (("Prepended initial PWD to relative path:\n"));
+              DEBUGP (("  old: '%s'\n  new: '%s'\n", target, ntarget));
+             target = ntarget;
            }
-         else
+
+         /* If the FTP host runs VMS, we will have to convert the absolute
+             directory path in UNIX notation to absolute directory path in
+             VMS notation as VMS FTP servers do not like UNIX notation of
+             absolute paths.  "VMS notation" is [dir.subdir.subsubdir]. */
+
+         if (con->rs == ST_VMS)
            {
-             if (!opt.server_response)
-               logprintf (LOG_VERBOSE, "==> CWD %s ... ", u->dir);
-             err = ftp_cwd (&con->rbuf, u->dir);
+             char *tmpp;
+             char *ntarget = (char *)alloca (strlen (target) + 2);
+             /* We use a converted initial dir, so directories in
+                 TARGET will be separated with slashes, something like
+                 "/INITIAL/FOLDER/DIR/SUBDIR".  Convert that to
+                 "[INITIAL.FOLDER.DIR.SUBDIR]".  */
+             strcpy (ntarget, target);
+             assert (*ntarget == '/');
+             *ntarget = '[';
+             for (tmpp = ntarget + 1; *tmpp; tmpp++)
+               if (*tmpp == '/')
+                 *tmpp = '.';
+             *tmpp++ = ']';
+             *tmpp = '\0';
+              DEBUGP (("Changed file name to VMS syntax:\n"));
+              DEBUGP (("  Unix: '%s'\n  VMS: '%s'\n", target, ntarget));
+             target = ntarget;
            }
+
+         if (!opt.server_response)
+           logprintf (LOG_VERBOSE, "==> CWD %s ... ", target);
+         err = ftp_cwd (&con->rbuf, target);
          /* FTPRERR, WRITEFAILED, FTPNSFOD */
          switch (err)
            {
@@ -439,17 +451,48 @@ Error in server response, closing control connection.\n"));
   else /* do not CWD */
     logputs (LOG_VERBOSE, _("==> CWD not required.\n"));
 
+  if ((cmd & DO_RETR) && restval && *len == 0)
+    {
+      if (opt.verbose)
+       {
+          if (!opt.server_response)
+           logprintf (LOG_VERBOSE, "==> SIZE %s ... ", u->file);
+       }
+
+      err = ftp_size(&con->rbuf, u->file, len);
+      /* FTPRERR */
+      switch (err)
+       {
+       case FTPRERR:
+       case FTPSRVERR :
+         logputs (LOG_VERBOSE, "\n");
+         logputs (LOG_NOTQUIET, _("\
+Error in server response, closing control connection.\n"));
+         CLOSE (csock);
+         rbuf_uninitialize (&con->rbuf);
+         return err;
+         break;
+       case FTPOK:
+         /* Everything is OK.  */
+         break;
+       default:
+         abort ();
+         break;
+       }
+       if (!opt.server_response)
+         logputs (LOG_VERBOSE, _("done.\n"));
+    }
+
   /* If anything is to be retrieved, PORT (or PASV) must be sent.  */
   if (cmd & (DO_LIST | DO_RETR))
     {
       if (opt.ftp_pasv > 0)
        {
-         char thost[256];
-         unsigned short tport;
-
+         ip_address     passive_addr;
+         unsigned short passive_port;
          if (!opt.server_response)
            logputs (LOG_VERBOSE, "==> PASV ... ");
-         err = ftp_pasv (&con->rbuf, pasv_addr);
+         err = ftp_pasv (&con->rbuf, &passive_addr, &passive_port);
          /* FTPRERR, WRITEFAILED, FTPNOPASV, FTPINVPASV */
          switch (err)
            {
@@ -483,62 +526,28 @@ Error in server response, closing control connection.\n"));
            default:
              abort ();
              break;
-           }
+           }   /* switch(err) */
          if (err==FTPOK)
            {
-             sprintf (thost, "%d.%d.%d.%d",
-                      pasv_addr[0], pasv_addr[1], pasv_addr[2], pasv_addr[3]);
-             tport = (pasv_addr[4] << 8) + pasv_addr[5];
-             DEBUGP ((_("Will try connecting to %s:%hu.\n"), thost, tport));
-             err = make_connection (&dtsock, thost, tport);
-             switch (err)
+             dtsock = connect_to_one (&passive_addr, passive_port, 1);
+             if (dtsock < 0)
                {
-                 /* Do not close the socket in first several cases,
-                    since it wasn't created at all.  */
-               case HOSTERR:
-                 logputs (LOG_VERBOSE, "\n");
-                 logprintf (LOG_NOTQUIET, "%s: %s\n", thost,
-                            herrmsg (h_errno));
-                 CLOSE (csock);
-                 rbuf_uninitialize (&con->rbuf);
-                 return HOSTERR;
-                 break;
-               case CONSOCKERR:
-                 logputs (LOG_VERBOSE, "\n");
-                 logprintf (LOG_NOTQUIET, "socket: %s\n", strerror (errno));
-                 CLOSE (csock);
-                 rbuf_uninitialize (&con->rbuf);
-                 return CONSOCKERR;
-                 break;
-               case CONREFUSED:
-                 logputs (LOG_VERBOSE, "\n");
-                 logprintf (LOG_NOTQUIET,
-                            _("Connection to %s:%hu refused.\n"),
-                            thost, tport);
+                 int save_errno = errno;
                  CLOSE (csock);
                  rbuf_uninitialize (&con->rbuf);
-                 closeport (dtsock);
-                 return CONREFUSED;
-               case CONERROR:
-                 logputs (LOG_VERBOSE, "\n");
-                 logprintf (LOG_NOTQUIET, "connect: %s\n",
-                            strerror (errno));
-                 CLOSE (csock);
-                 rbuf_uninitialize (&con->rbuf);
-                 closeport (dtsock);
-                 return CONERROR;
-                 break;
-               default:
-                 /* #### What?!  */
-                 DO_NOTHING;
+                 logprintf (LOG_VERBOSE, _("couldn't connect to %s:%hu: %s\n"),
+                            pretty_print_address (&passive_addr), passive_port,
+                            strerror (save_errno));
+                 return save_errno == ECONNREFUSED ? CONREFUSED : CONERROR;
                }
-             passive_mode_open= 1;  /* Flag to avoid accept port */
+
+             pasv_mode_open = 1;  /* Flag to avoid accept port */
              if (!opt.server_response)
                logputs (LOG_VERBOSE, _("done.    "));
            } /* err==FTP_OK */
        }
 
-      if (!passive_mode_open)   /* Try to use a port command if PASV failed */
+      if (!pasv_mode_open)   /* Try to use a port command if PASV failed */
        {
          if (!opt.server_response)
            logputs (LOG_VERBOSE, "==> PORT ... ");
@@ -581,15 +590,6 @@ Error in server response, closing control connection.\n"));
              closeport (dtsock);
              return err;
              break;
-           case HOSTERR:
-             logputs (LOG_VERBOSE, "\n");
-             logprintf (LOG_NOTQUIET, "%s: %s\n", u->host,
-                        herrmsg (h_errno));
-             CLOSE (csock);
-             closeport (dtsock);
-             rbuf_uninitialize (&con->rbuf);
-             return HOSTERR;
-             break;
            case FTPPORTERR:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET, _("Invalid PORT.\n"));
@@ -639,6 +639,19 @@ Error in server response, closing control connection.\n"));
          return err;
          break;
        case FTPRESTFAIL:
+         /* If `-c' is specified and the file already existed when
+            Wget was started, it would be a bad idea for us to start
+            downloading it from scratch, effectively truncating it.  */
+         if (opt.always_rest && (cmd & NO_TRUNCATE))
+           {
+             logprintf (LOG_NOTQUIET,
+                        _("\nREST failed; will not truncate `%s'.\n"),
+                        con->target);
+             CLOSE (csock);
+             closeport (dtsock);
+             rbuf_uninitialize (&con->rbuf);
+             return CONTNOTSUPPORTED;
+           }
          logputs (LOG_VERBOSE, _("\nREST failed, starting from scratch.\n"));
          restval = 0L;
          break;
@@ -766,7 +779,7 @@ Error in server response, closing control connection.\n"));
   if (!(cmd & (DO_LIST | DO_RETR)))
     return RETRFINISHED;
 
-  if (!passive_mode_open)  /* we are not using pasive mode so we need
+  if (!pasv_mode_open)  /* we are not using pasive mode so we need
                              to accept */
     {
       /* Open the data transmission socket by calling acceptport().  */
@@ -782,16 +795,16 @@ Error in server response, closing control connection.\n"));
   /* Open the file -- if opt.dfp is set, use it instead.  */
   if (!opt.dfp || con->cmd & DO_LIST)
     {
-      mkalldirs (u->local);
+      mkalldirs (con->target);
       if (opt.backups)
-       rotate_backups (u->local);
+       rotate_backups (con->target);
       /* #### Is this correct? */
-      chmod (u->local, 0600);
+      chmod (con->target, 0600);
 
-      fp = fopen (u->local, restval ? "ab" : "wb");
+      fp = fopen (con->target, restval ? "ab" : "wb");
       if (!fp)
        {
-         logprintf (LOG_NOTQUIET, "%s: %s\n", u->local, strerror (errno));
+         logprintf (LOG_NOTQUIET, "%s: %s\n", con->target, strerror (errno));
          CLOSE (csock);
          rbuf_uninitialize (&con->rbuf);
          closeport (dtsock);
@@ -800,12 +813,20 @@ Error in server response, closing control connection.\n"));
     }
   else
     {
+      extern int global_download_count;
       fp = opt.dfp;
-      if (!restval)
+
+      /* Rewind the output document if the download starts over and if
+        this is the first download.  See gethttp() for a longer
+        explanation.  */
+      if (!restval && global_download_count == 0)
        {
          /* This will silently fail for streams that don't correspond
             to regular files, but that's OK.  */
          rewind (fp);
+         /* ftruncate is needed because opt.dfp is opened in append
+            mode if opt.always_rest is set.  */
+         ftruncate (fileno (fp), 0);
          clearerr (fp);
        }
     }
@@ -816,6 +837,7 @@ Error in server response, closing control connection.\n"));
       if (restval)
        logprintf (LOG_VERBOSE, _(" [%s to go]"), legible (*len - restval));
       logputs (LOG_VERBOSE, "\n");
+      expected_bytes = *len;   /* for get_contents/show_progress */
     }
   else if (expected_bytes)
     {
@@ -825,12 +847,12 @@ Error in server response, closing control connection.\n"));
                   legible (expected_bytes - restval));
       logputs (LOG_VERBOSE, _(" (unauthoritative)\n"));
     }
-  reset_timer ();
+
   /* Get the contents of the document.  */
-  res = get_contents (dtsock, fp, len, restval, expected_bytes, &con->rbuf, 0);
-  con->dltime = elapsed_time ();
+  res = get_contents (dtsock, fp, len, restval, expected_bytes, &con->rbuf,
+                     0, &con->dltime);
   tms = time_str (NULL);
-  tmrate = rate (*len - restval, con->dltime, 0);
+  tmrate = retr_rate (*len - restval, con->dltime, 0);
   /* Close data connection socket.  */
   closeport (dtsock);
   /* Close the local file.  */
@@ -850,7 +872,7 @@ Error in server response, closing control connection.\n"));
   if (res == -2)
     {
       logprintf (LOG_NOTQUIET, _("%s: %s, closing control connection.\n"),
-                u->local, strerror (errno));
+                con->target, strerror (errno));
       CLOSE (csock);
       rbuf_uninitialize (&con->rbuf);
       return FWRITEERR;
@@ -915,10 +937,10 @@ Error in server response, closing control connection.\n"));
      print it out.  */
   if (opt.server_response && (con->cmd & DO_LIST))
     {
-      mkalldirs (u->local);
-      fp = fopen (u->local, "r");
+      mkalldirs (con->target);
+      fp = fopen (con->target, "r");
       if (!fp)
-       logprintf (LOG_ALWAYS, "%s: %s\n", u->local, strerror (errno));
+       logprintf (LOG_ALWAYS, "%s: %s\n", con->target, strerror (errno));
       else
        {
          char *line;
@@ -942,7 +964,7 @@ Error in server response, closing control connection.\n"));
    This loop either gets commands from con, or (if ON_YOUR_OWN is
    set), makes them up to retrieve the file given by the URL.  */
 static uerr_t
-ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
+ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
 {
   int count, orig_lp;
   long restval, len;
@@ -950,21 +972,21 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
   uerr_t err;
   struct stat st;
 
-  if (!u->local)
-    u->local = url_filename (u);
+  if (!con->target)
+    con->target = url_filename (u);
 
-  if (opt.noclobber && file_exists_p (u->local))
+  if (opt.noclobber && file_exists_p (con->target))
     {
       logprintf (LOG_VERBOSE,
-                _("File `%s' already there, not retrieving.\n"), u->local);
+                _("File `%s' already there, not retrieving.\n"), con->target);
       /* If the file is there, we suppose it's retrieved OK.  */
       return RETROK;
     }
 
   /* Remove it if it's a link.  */
-  remove_link (u->local);
+  remove_link (con->target);
   if (!opt.output_document)
-    locf = u->local;
+    locf = con->target;
   else
     locf = opt.output_document;
 
@@ -1001,19 +1023,28 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
          else
            con->cmd |= DO_CWD;
        }
+
       /* Assume no restarting.  */
       restval = 0L;
       if ((count > 1 || opt.always_rest)
          && !(con->cmd & DO_LIST)
-         && file_exists_p (u->local))
-       if (stat (u->local, &st) == 0)
+         && file_exists_p (locf))
+       if (stat (locf, &st) == 0 && S_ISREG (st.st_mode))
          restval = st.st_size;
+
+      /* In `-c' is used, check whether the file we're writing to
+        exists and is of non-zero length.  If so, we'll refuse to
+        truncate it if the server doesn't support continued
+        downloads.  */
+      if (opt.always_rest && restval > 0)
+       con->cmd |= NO_TRUNCATE;
+
       /* Get the current time string.  */
       tms = time_str (NULL);
       /* Print fetch message, if opt.verbose.  */
       if (opt.verbose)
        {
-         char *hurl = str_url (u->proxy ? u->proxy : u, 1);
+         char *hurl = url_string (u, 1);
          char tmp[15];
          strcpy (tmp, "        ");
          if (count > 1)
@@ -1031,9 +1062,6 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
       else
        len = 0;
       err = getftp (u, &len, restval, con);
-      /* Time?  */
-      tms = time_str (NULL);
-      tmrate = rate (len - restval, con->dltime, 0);
 
       if (!rbuf_initialized_p (&con->rbuf))
        con->st &= ~DONE_CWD;
@@ -1043,7 +1071,7 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
       switch (err)
        {
        case HOSTERR: case CONREFUSED: case FWRITEERR: case FOPENERR:
-       case FTPNSFOD: case FTPLOGINC: case FTPNOPASV:
+       case FTPNSFOD: case FTPLOGINC: case FTPNOPASV: case CONTNOTSUPPORTED:
          /* Fatal errors, give up.  */
          return err;
          break;
@@ -1071,6 +1099,9 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
          /* Not as great.  */
          abort ();
        }
+      /* Time?  */
+      tms = time_str (NULL);
+      tmrate = retr_rate (len - restval, con->dltime, 0);
 
       /* If we get out of the switch above without continue'ing, we've
         successfully downloaded a file.  Remember this fact. */
@@ -1088,7 +1119,7 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
          /* Need to hide the password from the URL.  The `if' is here
              so that we don't do the needless allocation every
              time. */
-         char *hurl = str_url (u->proxy ? u->proxy : u, 1);
+         char *hurl = url_string (u, 1);
          logprintf (LOG_NONVERBOSE, "%s URL: %s [%ld] -> \"%s\" [%d]\n",
                     tms, hurl, len, locf, count);
          xfree (hurl);
@@ -1147,45 +1178,49 @@ ftp_loop_internal (struct urlinfo *u, struct fileinfo *f, ccon *con)
 
 /* Return the directory listing in a reusable format.  The directory
    is specifed in u->dir.  */
-static struct fileinfo *
-ftp_get_listing (struct urlinfo *u, ccon *con)
+uerr_t
+ftp_get_listing (struct url *u, ccon *con, struct fileinfo **f)
 {
-  struct fileinfo *f;
   uerr_t err;
-  char *olocal = u->local;
-  char *list_filename, *ofile;
+  char *uf;                    /* url file name */
+  char *lf;                    /* list file name */
+  char *old_target = con->target;
 
   con->st &= ~ON_YOUR_OWN;
   con->cmd |= (DO_LIST | LEAVE_PENDING);
   con->cmd &= ~DO_RETR;
-  /* Get the listing filename.  */
-  ofile = u->file;
-  u->file = LIST_FILENAME;
-  list_filename = url_filename (u);
-  u->file = ofile;
-  u->local = list_filename;
-  DEBUGP ((_("Using `%s' as listing tmp file.\n"), list_filename));
+
+  /* Find the listing file name.  We do it by taking the file name of
+     the URL and replacing the last component with the listing file
+     name.  */
+  uf = url_filename (u);
+  lf = file_merge (uf, LIST_FILENAME);
+  xfree (uf);
+  DEBUGP ((_("Using `%s' as listing tmp file.\n"), lf));
+
+  con->target = lf;
   err = ftp_loop_internal (u, NULL, con);
-  u->local = olocal;
+  con->target = old_target;
+
   if (err == RETROK)
-    f = ftp_parse_ls (list_filename, con->rs);
+    *f = ftp_parse_ls (lf, con->rs);
   else
-    f = NULL;
+    *f = NULL;
   if (opt.remove_listing)
     {
-      if (unlink (list_filename))
+      if (unlink (lf))
        logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
       else
-       logprintf (LOG_VERBOSE, _("Removed `%s'.\n"), list_filename);
+       logprintf (LOG_VERBOSE, _("Removed `%s'.\n"), lf);
     }
-  xfree (list_filename);
+  xfree (lf);
   con->cmd &= ~DO_LIST;
-  return f;
+  return err;
 }
 
-static uerr_t ftp_retrieve_dirs PARAMS ((struct urlinfo *, struct fileinfo *,
+static uerr_t ftp_retrieve_dirs PARAMS ((struct url *, struct fileinfo *,
                                         ccon *));
-static uerr_t ftp_retrieve_glob PARAMS ((struct urlinfo *, ccon *, int));
+static uerr_t ftp_retrieve_glob PARAMS ((struct url *, ccon *, int));
 static struct fileinfo *delelement PARAMS ((struct fileinfo *,
                                            struct fileinfo **));
 static void freefileinfo PARAMS ((struct fileinfo *f));
@@ -1198,11 +1233,10 @@ static void freefileinfo PARAMS ((struct fileinfo *f));
    If opt.recursive is set, after all files have been retrieved,
    ftp_retrieve_dirs will be called to retrieve the directories.  */
 static uerr_t
-ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
+ftp_retrieve_list (struct url *u, struct fileinfo *f, ccon *con)
 {
   static int depth = 0;
   uerr_t err;
-  char *olocal, *ofile;
   struct fileinfo *orig;
   long local_size;
   time_t tml;
@@ -1237,15 +1271,19 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
 
   while (f)
     {
+      char *old_target, *ofile;
+
       if (downloaded_exceeds_quota ())
        {
          --depth;
          return QUOTEXC;
        }
-      olocal = u->local;
-      ofile = u->file;
-      u->file = f->name;
-      u->local = url_filename (u);
+      old_target = con->target;
+
+      ofile = xstrdup (u->file);
+      url_set_file (u, f->name);
+
+      con->target = url_filename (u);
       err = RETROK;
 
       dlthis = 1;
@@ -1257,7 +1295,7 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
             I'm not implementing it now since files on an FTP server are much
             more likely than files on an HTTP server to legitimately have a
             .orig suffix. */
-         if (!stat (u->local, &st))
+         if (!stat (con->target, &st))
            {
               int eq_size;
               int cor_val;
@@ -1274,7 +1312,7 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
                  /* Remote file is older, file sizes can be compared and
                      are both equal. */
                   logprintf (LOG_VERBOSE, _("\
-Remote file no newer than local file `%s' -- not retrieving.\n"), u->local);
+Remote file no newer than local file `%s' -- not retrieving.\n"), con->target);
                  dlthis = 0;
                }
              else if (eq_size)
@@ -1282,7 +1320,7 @@ Remote file no newer than local file `%s' -- not retrieving.\n"), u->local);
                   /* Remote file is newer or sizes cannot be matched */
                   logprintf (LOG_VERBOSE, _("\
 Remote file is newer than local file `%s' -- retrieving.\n\n"),
-                             u->local);
+                             con->target);
                 }
               else
                 {
@@ -1310,30 +1348,30 @@ The sizes do not match (local %ld) -- retrieving.\n\n"), local_size);
                   struct stat st;
                  /* Check whether we already have the correct
                      symbolic link.  */
-                  int rc = lstat (u->local, &st);
+                  int rc = lstat (con->target, &st);
                   if (rc == 0)
                    {
                      size_t len = strlen (f->linkto) + 1;
                      if (S_ISLNK (st.st_mode))
                        {
                          char *link_target = (char *)alloca (len);
-                         size_t n = readlink (u->local, link_target, len);
+                         size_t n = readlink (con->target, link_target, len);
                          if ((n == len - 1)
                              && (memcmp (link_target, f->linkto, n) == 0))
                            {
                              logprintf (LOG_VERBOSE, _("\
 Already have correct symlink %s -> %s\n\n"),
-                                        u->local, f->linkto);
+                                        con->target, f->linkto);
                               dlthis = 0;
                              break;
                            }
                        }
                    }
                  logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"),
-                            u->local, f->linkto);
+                            con->target, f->linkto);
                  /* Unlink before creating symlink!  */
-                 unlink (u->local);
-                 if (symlink (f->linkto, u->local) == -1)
+                 unlink (con->target);
+                 if (symlink (f->linkto, con->target) == -1)
                    logprintf (LOG_NOTQUIET, "symlink: %s\n",
                               strerror (errno));
                  logputs (LOG_VERBOSE, "\n");
@@ -1341,7 +1379,7 @@ Already have correct symlink %s -> %s\n\n"),
 #else  /* not HAVE_SYMLINK */
              logprintf (LOG_NOTQUIET,
                         _("Symlinks not supported, skipping symlink `%s'.\n"),
-                        u->local);
+                        con->target);
 #endif /* not HAVE_SYMLINK */
            }
          else                /* opt.retr_symlinks */
@@ -1372,7 +1410,7 @@ Already have correct symlink %s -> %s\n\n"),
       if (!(f->type == FT_SYMLINK && !opt.retr_symlinks)
          && f->tstamp != -1
           && dlthis
-         && file_exists_p (u->local))
+         && file_exists_p (con->target))
        {
          /* #### This code repeats in http.c and ftp.c.  Move it to a
              function!  */
@@ -1383,27 +1421,31 @@ Already have correct symlink %s -> %s\n\n"),
                fl = opt.output_document;
            }
          else
-           fl = u->local;
+           fl = con->target;
          if (fl)
            touch (fl, f->tstamp);
        }
       else if (f->tstamp == -1)
-       logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"), u->local);
+       logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"), con->target);
 
       if (f->perms && f->type == FT_PLAINFILE && dlthis)
-       chmod (u->local, f->perms);
+       chmod (con->target, f->perms);
       else
-       DEBUGP (("Unrecognized permissions for %s.\n", u->local));
+       DEBUGP (("Unrecognized permissions for %s.\n", con->target));
+
+      xfree (con->target);
+      con->target = old_target;
+
+      url_set_file (u, ofile);
+      xfree (ofile);
 
-      xfree (u->local);
-      u->local = olocal;
-      u->file = ofile;
       /* Break on fatals.  */
       if (err == QUOTEXC || err == HOSTERR || err == FWRITEERR)
        break;
       con->cmd &= ~ (DO_CWD | DO_LOGIN);
       f = f->next;
-    } /* while */
+    }
+
   /* We do not want to call ftp_retrieve_dirs here */
   if (opt.recursive &&
       !(opt.reclevel != INFINITE_RECURSION && depth >= opt.reclevel))
@@ -1420,41 +1462,62 @@ Already have correct symlink %s -> %s\n\n"),
    ftp_retrieve_glob on each directory entry.  The function knows
    about excluded directories.  */
 static uerr_t
-ftp_retrieve_dirs (struct urlinfo *u, struct fileinfo *f, ccon *con)
+ftp_retrieve_dirs (struct url *u, struct fileinfo *f, ccon *con)
 {
-  char *odir;
-  char *current_container = NULL;
-  int current_length = 0;
+  char *container = NULL;
+  int container_size = 0;
 
   for (; f; f = f->next)
     {
-      int len;
+      int size;
+      char *odir, *newdir;
 
       if (downloaded_exceeds_quota ())
        break;
       if (f->type != FT_DIRECTORY)
        continue;
-      odir = u->dir;
-      len = 1 + strlen (u->dir) + 1 + strlen (f->name) + 1;
+
       /* Allocate u->dir off stack, but reallocate only if a larger
-         string is needed.  */
-      if (len > current_length)
-       current_container = (char *)alloca (len);
-      u->dir = current_container;
-      sprintf (u->dir, "/%s%s%s", odir + (*odir == '/'),
-             (!*odir || (*odir == '/' && !* (odir + 1))) ? "" : "/", f->name);
-      if (!accdir (u->dir, ALLABS))
+         string is needed.  It's a pity there's no "realloca" for an
+         item on the bottom of the stack.  */
+      size = strlen (u->dir) + 1 + strlen (f->name) + 1;
+      if (size > container_size)
+       container = (char *)alloca (size);
+      newdir = container;
+
+      odir = u->dir;
+      if (*odir == '\0'
+         || (*odir == '/' && *(odir + 1) == '\0'))
+       /* If ODIR is empty or just "/", simply append f->name to
+          ODIR.  (In the former case, to preserve u->dir being
+          relative; in the latter case, to avoid double slash.)  */
+       sprintf (newdir, "%s%s", odir, f->name);
+      else
+       /* Else, use a separator. */
+       sprintf (newdir, "%s/%s", odir, f->name);
+
+      DEBUGP (("Composing new CWD relative to the initial directory.\n"));
+      DEBUGP (("  odir = '%s'\n  f->name = '%s'\n  newdir = '%s'\n\n",
+              odir, f->name, newdir));
+      if (!accdir (newdir, ALLABS))
        {
          logprintf (LOG_VERBOSE, _("\
-Not descending to `%s' as it is excluded/not-included.\n"), u->dir);
-         u->dir = odir;
+Not descending to `%s' as it is excluded/not-included.\n"), newdir);
          continue;
        }
+
       con->st &= ~DONE_CWD;
+
+      odir = xstrdup (u->dir); /* because url_set_dir will free
+                                  u->dir. */
+      url_set_dir (u, newdir);
       ftp_retrieve_glob (u, con, GETALL);
+      url_set_dir (u, odir);
+      xfree (odir);
+
       /* Set the time-stamp?  */
-      u->dir = odir;
     }
+
   if (opt.quota && opt.downloaded > opt.quota)
     return QUOTEXC;
   else
@@ -1471,14 +1534,16 @@ Not descending to `%s' as it is excluded/not-included.\n"), u->dir);
    get the listing, so that the time-stamp is heeded); if it's GLOBALL,
    use globbing; if it's GETALL, download the whole directory.  */
 static uerr_t
-ftp_retrieve_glob (struct urlinfo *u, ccon *con, int action)
+ftp_retrieve_glob (struct url *u, ccon *con, int action)
 {
   struct fileinfo *orig, *start;
   uerr_t res;
 
   con->cmd |= LEAVE_PENDING;
 
-  orig = ftp_get_listing (u, con);
+  res = ftp_get_listing (u, con, &orig);
+  if (res != RETROK)
+    return res;
   start = orig;
   /* First: weed out that do not conform the global rules given in
      opt.accepts and opt.rejects.  */
@@ -1509,7 +1574,7 @@ ftp_retrieve_glob (struct urlinfo *u, ccon *con, int action)
          matchres = fnmatch (u->file, f->name, 0);
          if (matchres == -1)
            {
-             logprintf (LOG_NOTQUIET, "%s: %s\n", u->local,
+             logprintf (LOG_NOTQUIET, "%s: %s\n", con->target,
                         strerror (errno));
              break;
            }
@@ -1559,7 +1624,7 @@ ftp_retrieve_glob (struct urlinfo *u, ccon *con, int action)
    of URL.  Inherently, its capabilities are limited on what can be
    encoded into a URL.  */
 uerr_t
-ftp_loop (struct urlinfo *u, int *dt)
+ftp_loop (struct url *u, int *dt)
 {
   ccon con;                    /* FTP connection */
   uerr_t res;
@@ -1579,15 +1644,16 @@ ftp_loop (struct urlinfo *u, int *dt)
      opt.htmlify is 0, of course.  :-) */
   if (!*u->file && !opt.recursive)
     {
-      struct fileinfo *f = ftp_get_listing (u, &con);
+      struct fileinfo *f;
+      res = ftp_get_listing (u, &con, &f);
 
-      if (f)
+      if (res == RETROK)
        {
          if (opt.htmlify)
            {
              char *filename = (opt.output_document
                                ? xstrdup (opt.output_document)
-                               : (u->local ? xstrdup (u->local)
+                               : (con.target ? xstrdup (con.target)
                                   : url_filename (u)));
              res = ftp_index (filename, u, f);
              if (res == FTPOK && opt.verbose)
@@ -1637,6 +1703,8 @@ ftp_loop (struct urlinfo *u, int *dt)
     CLOSE (RBUF_FD (&con.rbuf));
   FREE_MAYBE (con.id);
   con.id = NULL;
+  FREE_MAYBE (con.target);
+  con.target = NULL;
   return res;
 }