]> sjero.net Git - wget/blobdiff - src/ftp.c
[svn] Remove the "lockable boolean" setting for passive_ftp.
[wget] / src / ftp.c
index e65f8284dccae3a3d835f41410b37817cb95e2dd..fed0597c319d5575c04dab6cbdd8f0e6fb416e13 100644 (file)
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -32,15 +32,10 @@ so, delete this exception statement from your version.  */
 
 #include <stdio.h>
 #include <stdlib.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
-#include <sys/types.h>
 #include <assert.h>
 #include <errno.h>
 
@@ -55,10 +50,6 @@ so, delete this exception statement from your version.  */
 #include "convert.h"           /* for downloaded_file */
 #include "recur.h"             /* for INFINITE_RECURSION */
 
-#ifndef errno
-extern int errno;
-#endif
-
 extern LARGE_INT total_downloaded_bytes;
 
 /* File where the "ls -al" listing will be saved.  */
@@ -220,6 +211,25 @@ ftp_do_port (int csock, int *local_sock)
 }
 #endif
 
+static void
+print_length (wgint size, wgint start, int authoritative)
+{
+  logprintf (LOG_VERBOSE, _("Length: %s"), with_thousand_seps (size));
+  if (size >= 1024)
+    logprintf (LOG_VERBOSE, " (%s)", human_readable (size));
+  if (start > 0)
+    {
+      if (start >= 1024)
+       logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
+                  with_thousand_seps (size - start),
+                  human_readable (size - start));
+      else
+       logprintf (LOG_VERBOSE, _(", %s remaining"),
+                  with_thousand_seps (size - start));
+    }
+  logputs (LOG_VERBOSE, !authoritative ? _(" (unauthoritative)\n") : "\n");
+}
+
 /* Retrieves a file with denoted parameters through opening an FTP
    connection to the server.  It always closes the data connection,
    and closes the control connection in case of error.  */
@@ -233,7 +243,7 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
   char *tms, *tmrate;
   int cmd = con->cmd;
   int pasv_mode_open = 0;
-  wgint expected_bytes = 0L;
+  wgint expected_bytes = 0;
   int rest_failed = 0;
   int flags;
   wgint rd_size;
@@ -251,9 +261,10 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
   user = u->user;
   passwd = u->passwd;
   search_netrc (u->host, (const char **)&user, (const char **)&passwd, 1);
-  user = user ? user : opt.ftp_acc;
-  passwd = passwd ? passwd : opt.ftp_pass;
-  assert (user && passwd);
+  user = user ? user : (opt.ftp_user ? opt.ftp_user : opt.user);
+  if (!user) user = "anonymous";
+  passwd = passwd ? passwd : (opt.ftp_passwd ? opt.ftp_passwd : opt.passwd);
+  if (!passwd) passwd = "-wget@";
 
   dtsock = -1;
   local_sock = -1;
@@ -271,8 +282,7 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
       if (con->proxy)
        {
          /* If proxy is in use, log in as username@target-site. */
-         logname = xmalloc (strlen (user) + 1 + strlen (u->host) + 1);
-         sprintf (logname, "%s@%s", user, u->host);
+         logname = concat_strings (user, "@", u->host, (char *) 0);
        }
 
       /* Login to the server: */
@@ -292,7 +302,7 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
        con->csock = -1;
 
       /* Second: Login with proper USER/PASS sequence.  */
-      logprintf (LOG_VERBOSE, _("Logging in as %s ... "), user);
+      logprintf (LOG_VERBOSE, _("Logging in as %s ... "), escnonprint (user));
       if (opt.server_response)
        logputs (LOG_ALWAYS, "\n");
       err = ftp_login (csock, logname, passwd);
@@ -310,14 +320,12 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPSRVERR:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET, _("Error in server greeting.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case WRITEFAILED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -325,29 +333,24 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPLOGREFUSED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET, _("The server refuses login.\n"));
          fd_close (csock);
          con->csock = -1;
          return FTPLOGREFUSED;
-         break;
        case FTPLOGINC:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET, _("Login incorrect.\n"));
          fd_close (csock);
          con->csock = -1;
          return FTPLOGINC;
-         break;
        case FTPOK:
          if (!opt.server_response)
            logputs (LOG_VERBOSE, _("Logged in!\n"));
          break;
        default:
          abort ();
-         exit (1);
-         break;
        }
       /* Third: Get the system type */
       if (!opt.server_response)
@@ -363,7 +366,6 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPSRVERR:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -374,7 +376,6 @@ Error in server response, closing control connection.\n"));
          break;
        default:
          abort ();
-         break;
        }
       if (!opt.server_response && err != FTPSRVERR)
        logputs (LOG_VERBOSE, _("done.    "));
@@ -394,7 +395,6 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPSRVERR :
          /* PWD unsupported -- assume "/". */
          xfree_null (con->id);
@@ -405,7 +405,6 @@ Error in server response, closing control connection.\n"));
          break;
        default:
          abort ();
-         break;
        }
       /* VMS will report something like "PUB$DEVICE:[INITIAL.FOLDER]".
          Convert it to "/INITIAL/FOLDER" */ 
@@ -448,7 +447,6 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case WRITEFAILED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -456,7 +454,6 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPUNKNOWNTYPE:
          logputs (LOG_VERBOSE, "\n");
          logprintf (LOG_NOTQUIET,
@@ -470,7 +467,6 @@ Error in server response, closing control connection.\n"));
          break;
        default:
          abort ();
-         break;
        }
       if (!opt.server_response)
        logputs (LOG_VERBOSE, _("done.  "));
@@ -551,7 +547,7 @@ Error in server response, closing control connection.\n"));
            }
 
          if (!opt.server_response)
-           logprintf (LOG_VERBOSE, "==> CWD %s ... ", target);
+           logprintf (LOG_VERBOSE, "==> CWD %s ... ", escnonprint (target));
          err = ftp_cwd (csock, target);
          /* FTPRERR, WRITEFAILED, FTPNSFOD */
          switch (err)
@@ -563,7 +559,6 @@ Error in server response, closing control connection.\n"));
              fd_close (csock);
              con->csock = -1;
              return err;
-             break;
            case WRITEFAILED:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET,
@@ -571,21 +566,17 @@ Error in server response, closing control connection.\n"));
              fd_close (csock);
              con->csock = -1;
              return err;
-             break;
            case FTPNSFOD:
              logputs (LOG_VERBOSE, "\n");
              logprintf (LOG_NOTQUIET, _("No such directory `%s'.\n\n"),
-                        u->dir);
+                        escnonprint (u->dir));
              fd_close (csock);
              con->csock = -1;
              return err;
-             break;
            case FTPOK:
-             /* fine and dandy */
              break;
            default:
              abort ();
-             break;
            }
          if (!opt.server_response)
            logputs (LOG_VERBOSE, _("done.\n"));
@@ -599,7 +590,7 @@ Error in server response, closing control connection.\n"));
       if (opt.verbose)
        {
           if (!opt.server_response)
-           logprintf (LOG_VERBOSE, "==> SIZE %s ... ", u->file);
+           logprintf (LOG_VERBOSE, "==> SIZE %s ... ", escnonprint (u->file));
        }
 
       err = ftp_size (csock, u->file, len);
@@ -614,13 +605,11 @@ Error in server response, closing control connection.\n"));
          fd_close (csock);
          con->csock = -1;
          return err;
-         break;
        case FTPOK:
          /* Everything is OK.  */
          break;
        default:
          abort ();
-         break;
        }
        if (!opt.server_response)
          logputs (LOG_VERBOSE, _("done.\n"));
@@ -629,7 +618,7 @@ Error in server response, closing control connection.\n"));
   /* If anything is to be retrieved, PORT (or PASV) must be sent.  */
   if (cmd & (DO_LIST | DO_RETR))
     {
-      if (opt.ftp_pasv > 0)
+      if (opt.ftp_pasv)
        {
          ip_address passive_addr;
          int        passive_port;
@@ -644,7 +633,6 @@ Error in server response, closing control connection.\n"));
              fd_close (csock);
              con->csock = -1;
              return err;
-             break;
            case WRITEFAILED:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET,
@@ -652,7 +640,6 @@ Error in server response, closing control connection.\n"));
              fd_close (csock);
              con->csock = -1;
              return err;
-             break;
            case FTPNOPASV:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET, _("Cannot initiate PASV transfer.\n"));
@@ -662,11 +649,9 @@ Error in server response, closing control connection.\n"));
              logputs (LOG_NOTQUIET, _("Cannot parse PASV response.\n"));
              break;
            case FTPOK:
-             /* fine and dandy */
              break;
            default:
              abort ();
-             break;
            }   /* switch (err) */
          if (err==FTPOK)
            {
@@ -708,7 +693,6 @@ Error in server response, closing control connection.\n"));
              fd_close (dtsock);
              fd_close (local_sock);
              return err;
-             break;
            case WRITEFAILED:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET,
@@ -718,7 +702,6 @@ Error in server response, closing control connection.\n"));
              fd_close (dtsock);
              fd_close (local_sock);
              return err;
-             break;
            case CONSOCKERR:
              logputs (LOG_VERBOSE, "\n");
              logprintf (LOG_NOTQUIET, "socket: %s\n", strerror (errno));
@@ -727,14 +710,12 @@ Error in server response, closing control connection.\n"));
              fd_close (dtsock);
              fd_close (local_sock);
              return err;
-             break;
            case FTPSYSERR:
              logputs (LOG_VERBOSE, "\n");
              logprintf (LOG_NOTQUIET, _("Bind error (%s).\n"),
                         strerror (errno));
              fd_close (dtsock);
              return err;
-             break;
            case FTPPORTERR:
              logputs (LOG_VERBOSE, "\n");
              logputs (LOG_NOTQUIET, _("Invalid PORT.\n"));
@@ -743,13 +724,10 @@ Error in server response, closing control connection.\n"));
              fd_close (dtsock);
              fd_close (local_sock);
              return err;
-             break;
            case FTPOK:
-             /* fine and dandy */
              break;
            default:
              abort ();
-             break;
            } /* port switch */
          if (!opt.server_response)
            logputs (LOG_VERBOSE, _("done.    "));
@@ -760,7 +738,8 @@ Error in server response, closing control connection.\n"));
   if (restval && (cmd & DO_RETR))
     {
       if (!opt.server_response)
-       logprintf (LOG_VERBOSE, "==> REST %s ... ", number_to_static_string (restval));
+       logprintf (LOG_VERBOSE, "==> REST %s ... ",
+                  number_to_static_string (restval));
       err = ftp_rest (csock, restval);
 
       /* FTPRERR, WRITEFAILED, FTPRESTFAIL */
@@ -775,7 +754,6 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case WRITEFAILED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -785,17 +763,14 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case FTPRESTFAIL:
          logputs (LOG_VERBOSE, _("\nREST failed, starting from scratch.\n"));
          rest_failed = 1;
          break;
        case FTPOK:
-         /* fine and dandy */
          break;
        default:
          abort ();
-         break;
        }
       if (err != FTPRESTFAIL && !opt.server_response)
        logputs (LOG_VERBOSE, _("done.    "));
@@ -822,7 +797,7 @@ Error in server response, closing control connection.\n"));
            {
              if (restval)
                logputs (LOG_VERBOSE, "\n");
-             logprintf (LOG_VERBOSE, "==> RETR %s ... ", u->file);
+             logprintf (LOG_VERBOSE, "==> RETR %s ... ", escnonprint (u->file));
            }
        }
 
@@ -839,7 +814,6 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case WRITEFAILED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -849,20 +823,17 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case FTPNSFOD:
          logputs (LOG_VERBOSE, "\n");
-         logprintf (LOG_NOTQUIET, _("No such file `%s'.\n\n"), u->file);
+         logprintf (LOG_NOTQUIET, _("No such file `%s'.\n\n"),
+                    escnonprint (u->file));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case FTPOK:
-         /* fine and dandy */
          break;
        default:
          abort ();
-         break;
        }
 
       if (!opt.server_response)
@@ -890,7 +861,6 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case WRITEFAILED:
          logputs (LOG_VERBOSE, "\n");
          logputs (LOG_NOTQUIET,
@@ -900,7 +870,6 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case FTPNSFOD:
          logputs (LOG_VERBOSE, "\n");
          logprintf (LOG_NOTQUIET, _("No such file or directory `%s'.\n\n"),
@@ -908,13 +877,10 @@ Error in server response, closing control connection.\n"));
          fd_close (dtsock);
          fd_close (local_sock);
          return err;
-         break;
        case FTPOK:
-         /* fine and dandy */
          break;
        default:
          abort ();
-         break;
        }
       if (!opt.server_response)
        logputs (LOG_VERBOSE, _("done.\n"));
@@ -953,10 +919,30 @@ Error in server response, closing control connection.\n"));
       mkalldirs (con->target);
       if (opt.backups)
        rotate_backups (con->target);
-      /* #### Is this correct? */
-      chmod (con->target, 0600);
 
-      fp = fopen (con->target, restval ? "ab" : "wb");
+      if (restval)
+       fp = fopen (con->target, "ab");
+      else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
+              || opt.output_document)
+       fp = fopen (con->target, "wb");
+      else
+       {
+         fp = fopen_excl (con->target, 1);
+         if (!fp && errno == EEXIST)
+           {
+             /* We cannot just invent a new name and use it (which is
+                what functions like unique_create typically do)
+                because we told the user we'd use this name.
+                Instead, return and retry the download.  */
+             logprintf (LOG_NOTQUIET, _("%s has sprung into existence.\n"),
+                        con->target);
+             fd_close (csock);
+             con->csock = -1;
+             fd_close (dtsock);
+             fd_close (local_sock);
+             return FOPEN_EXCL_ERR;
+           }
+       }
       if (!fp)
        {
          logprintf (LOG_NOTQUIET, "%s: %s\n", con->target, strerror (errno));
@@ -972,20 +958,11 @@ Error in server response, closing control connection.\n"));
 
   if (*len)
     {
-      logprintf (LOG_VERBOSE, _("Length: %s"), legible (*len));
-      if (restval)
-       logprintf (LOG_VERBOSE, _(" [%s to go]"), legible (*len - restval));
-      logputs (LOG_VERBOSE, "\n");
+      print_length (*len, restval, 1);
       expected_bytes = *len;   /* for get_contents/show_progress */
     }
   else if (expected_bytes)
-    {
-      logprintf (LOG_VERBOSE, _("Length: %s"), legible (expected_bytes));
-      if (restval)
-       logprintf (LOG_VERBOSE, _(" [%s to go]"),
-                  legible (expected_bytes - restval));
-      logputs (LOG_VERBOSE, _(" (unauthoritative)\n"));
-    }
+    print_length (expected_bytes, restval, 0);
 
   /* Get the contents of the document.  */
   flags = 0;
@@ -1092,9 +1069,9 @@ Error in server response, closing control connection.\n"));
          char *line;
          /* The lines are being read with read_whole_line because of
             no-buffering on opt.lfile.  */
-         while ((line = read_whole_line (fp)))
+         while ((line = read_whole_line (fp)) != NULL)
            {
-             logprintf (LOG_ALWAYS, "%s\n", line);
+             logprintf (LOG_ALWAYS, "%s\n", escnonprint (line));
              xfree (line);
            }
          fclose (fp);
@@ -1125,7 +1102,7 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
   if (opt.noclobber && file_exists_p (con->target))
     {
       logprintf (LOG_VERBOSE,
-                _("File `%s' already there, not retrieving.\n"), con->target);
+                _("File `%s' already there; not retrieving.\n"), con->target);
       /* If the file is there, we suppose it's retrieved OK.  */
       return RETROK;
     }
@@ -1172,13 +1149,17 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
        }
 
       /* Decide whether or not to restart.  */
-      restval = 0;
-      if (count > 1)
-       restval = len;          /* start where the previous run left off */
-      else if (opt.always_rest
-              && stat (locf, &st) == 0
-              && S_ISREG (st.st_mode))
+      if (opt.always_rest
+         && stat (locf, &st) == 0
+         && S_ISREG (st.st_mode))
+       /* When -c is used, continue from on-disk size.  (Can't use
+          hstat.len even if count>1 because we don't want a failed
+          first attempt to clobber existing data.)  */
        restval = st.st_size;
+      else if (count > 1)
+       restval = len;          /* start where the previous run left off */
+      else
+       restval = 0;
 
       /* Get the current time string.  */
       tms = time_str (NULL);
@@ -1215,14 +1196,20 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
        case FTPNSFOD: case FTPLOGINC: case FTPNOPASV: case CONTNOTSUPPORTED:
          /* Fatal errors, give up.  */
          return err;
-         break;
        case CONSOCKERR: case CONERROR: case FTPSRVERR: case FTPRERR:
        case WRITEFAILED: case FTPUNKNOWNTYPE: case FTPSYSERR:
        case FTPPORTERR: case FTPLOGREFUSED: case FTPINVPASV:
+       case FOPEN_EXCL_ERR:
          printwhat (count, opt.ntry);
          /* non-fatal errors */
+         if (err == FOPEN_EXCL_ERR)
+           {
+             /* Re-determine the file name. */
+             xfree_null (con->target);
+             con->target = url_file_name (u);
+             locf = con->target;
+           }
          continue;
-         break;
        case FTPRETRINT:
          /* If the control connection was closed, the retrieval
             will be considered OK if f->size == len.  */
@@ -1293,8 +1280,8 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
 
          if (opt.delete_after)
            {
-             DEBUGP (("Removing file due to --delete-after in"
-                      " ftp_loop_internal():\n"));
+             DEBUGP (("\
+Removing file due to --delete-after in ftp_loop_internal():\n"));
              logprintf (LOG_VERBOSE, _("Removing %s.\n"), locf);
              if (unlink (locf))
                logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
@@ -1359,12 +1346,10 @@ ftp_get_listing (struct url *u, ccon *con, struct fileinfo **f)
   return err;
 }
 
-static uerr_t ftp_retrieve_dirs PARAMS ((struct url *, struct fileinfo *,
-                                        ccon *));
-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));
+static uerr_t ftp_retrieve_dirs (struct url *, struct fileinfo *, ccon *);
+static uerr_t ftp_retrieve_glob (struct url *, ccon *, int);
+static struct fileinfo *delelement (struct fileinfo *, struct fileinfo **);
+static void freefileinfo (struct fileinfo *f);
 
 /* Retrieve a list of files given in struct fileinfo linked list.  If
    a file is a symbolic link, do not retrieve it, but rather try to
@@ -1508,19 +1493,18 @@ The sizes do not match (local %s) -- retrieving.\n\n"),
                            {
                              logprintf (LOG_VERBOSE, _("\
 Already have correct symlink %s -> %s\n\n"),
-                                        con->target, f->linkto);
+                                        con->target, escnonprint (f->linkto));
                               dlthis = 0;
                              break;
                            }
                        }
                    }
                  logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"),
-                            con->target, f->linkto);
+                            con->target, escnonprint (f->linkto));
                  /* Unlink before creating symlink!  */
                  unlink (con->target);
                  if (symlink (f->linkto, con->target) == -1)
-                   logprintf (LOG_NOTQUIET, "symlink: %s\n",
-                              strerror (errno));
+                   logprintf (LOG_NOTQUIET, "symlink: %s\n", strerror (errno));
                  logputs (LOG_VERBOSE, "\n");
                } /* have f->linkto */
 #else  /* not HAVE_SYMLINK */
@@ -1538,7 +1522,7 @@ Already have correct symlink %s -> %s\n\n"),
        case FT_DIRECTORY:
          if (!opt.recursive)
            logprintf (LOG_NOTQUIET, _("Skipping directory `%s'.\n"),
-                      f->name);
+                      escnonprint (f->name));
          break;
        case FT_PLAINFILE:
          /* Call the retrieve loop.  */
@@ -1547,7 +1531,7 @@ Already have correct symlink %s -> %s\n\n"),
          break;
        case FT_UNKNOWN:
          logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"),
-                    f->name);
+                    escnonprint (f->name));
          break;
        }       /* switch */
 
@@ -1652,7 +1636,8 @@ ftp_retrieve_dirs (struct url *u, struct fileinfo *f, ccon *con)
       if (!accdir (newdir, ALLABS))
        {
          logprintf (LOG_VERBOSE, _("\
-Not descending to `%s' as it is excluded/not-included.\n"), newdir);
+Not descending to `%s' as it is excluded/not-included.\n"),
+                    escnonprint (newdir));
          continue;
        }
 
@@ -1661,7 +1646,7 @@ Not descending to `%s' as it is excluded/not-included.\n"), newdir);
       odir = xstrdup (u->dir); /* because url_set_dir will free
                                   u->dir. */
       url_set_dir (u, newdir);
-      ftp_retrieve_glob (u, con, GETALL);
+      ftp_retrieve_glob (u, con, GLOB_GETALL);
       url_set_dir (u, odir);
       xfree (odir);
 
@@ -1692,9 +1677,10 @@ has_insecure_name_p (const char *s)
    Then it weeds out the file names that do not match the pattern.
    ftp_retrieve_list is called with this updated list as an argument.
 
-   If the argument ACTION is GETONE, just download the file (but first
-   get the listing, so that the time-stamp is heeded); if it's GLOBALL,
-   use globbing; if it's GETALL, download the whole directory.  */
+   If the argument ACTION is GLOB_GETONE, just download the file (but
+   first get the listing, so that the time-stamp is heeded); if it's
+   GLOB_GLOBALL, use globbing; if it's GLOB_GETALL, download the whole
+   directory.  */
 static uerr_t
 ftp_retrieve_glob (struct url *u, ccon *con, int action)
 {
@@ -1715,7 +1701,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
        {
          if (f->type != FT_DIRECTORY && !acceptable (f->name))
            {
-             logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"), f->name);
+             logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
+                        escnonprint (f->name));
              f = delelement (f, &start);
            }
          else
@@ -1728,7 +1715,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
     {
       if (has_insecure_name_p (f->name))
        {
-         logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"), f->name);
+         logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
+                    escnonprint (f->name));
          f = delelement (f, &start);
        }
       else
@@ -1736,7 +1724,7 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
     }
   /* Now weed out the files that do not match our globbing pattern.
      If we are dealing with a globbing pattern, that is.  */
-  if (*u->file && (action == GLOBALL || action == GETONE))
+  if (*u->file && (action == GLOB_GLOBALL || action == GLOB_GETONE))
     {
       int matchres = 0;
 
@@ -1761,7 +1749,6 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
          return RETRBADPATTERN;
        }
     }
-  res = RETROK;
   if (start)
     {
       /* Just get everything.  */
@@ -1769,14 +1756,15 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
     }
   else if (!start)
     {
-      if (action == GLOBALL)
+      if (action == GLOB_GLOBALL)
        {
          /* No luck.  */
          /* #### This message SUCKS.  We should see what was the
             reason that nothing was retrieved.  */
-         logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"), u->file);
+         logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"),
+                    escnonprint (u->file));
        }
-      else /* GETONE or GETALL */
+      else /* GLOB_GETONE or GLOB_GETALL */
        {
          /* Let's try retrieving it anyway.  */
          con->st |= ON_YOUR_OWN;
@@ -1803,14 +1791,13 @@ ftp_loop (struct url *u, int *dt, struct url *proxy)
 
   *dt = 0;
 
-  memset (&con, 0, sizeof (con));
+  xzero (con);
 
   con.csock = -1;
   con.st = ON_YOUR_OWN;
   con.rs = ST_UNIX;
   con.id = NULL;
   con.proxy = proxy;
-  res = RETROK;                        /* in case it's not used */
 
   /* If the file name is empty, the user probably wants a directory
      index.  We'll provide one, properly HTML-ized.  Unless
@@ -1855,14 +1842,25 @@ ftp_loop (struct url *u, int *dt, struct url *proxy)
     }
   else
     {
-      int wild = has_wildcards_p (u->file);
-      if ((opt.ftp_glob && wild) || opt.recursive || opt.timestamping)
+      int ispattern = 0;
+      if (opt.ftp_glob)
+       {
+         /* Treat the URL as a pattern if the file name part of the
+            URL path contains wildcards.  (Don't check for u->file
+            because it is unescaped and therefore doesn't leave users
+            the option to escape literal '*' as %2A.)  */
+         char *file_part = strrchr (u->path, '/');
+         if (!file_part)
+           file_part = u->path;
+         ispattern = has_wildcards_p (file_part);
+       }
+      if (ispattern || opt.recursive || opt.timestamping)
        {
          /* ftp_retrieve_glob is a catch-all function that gets called
             if we need globbing, time-stamping or recursion.  Its
             third argument is just what we really need.  */
          res = ftp_retrieve_glob (u, &con,
-                                  (opt.ftp_glob && wild) ? GLOBALL : GETONE);
+                                  ispattern ? GLOB_GLOBALL : GLOB_GETONE);
        }
       else
        res = ftp_loop_internal (u, NULL, &con);