From 151a60419bea65d6a2d0b5022f04384ce81c63b9 Mon Sep 17 00:00:00 2001
From: Tomas Hozza <thozza@redhat.com>
Date: Mon, 15 Oct 2012 14:49:30 +0200
Subject: [PATCH] Fixed error when downloading SYMLINK from FTP.

Fixed problem with downloading single file from a FTP
server, when timestamping (-N) is used and the target
file is a SYMLINK. Now wget downloads the file to which
the SYMLINK points and sets timestamp correctly (to timestamp
of the target file to which the SYMLINK points).
---
 src/ftp.c | 302 +++++++++++++++++++++++++++++++++++++++++++-------------------
 src/url.c |   4 +-
 src/url.h |   2 +
 3 files changed, 211 insertions(+), 97 deletions(-)

diff --git a/src/ftp.c b/src/ftp.c
index 669e663..4ac8a69 100644
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -1725,6 +1725,113 @@ 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);
+static bool has_insecure_name_p (const char *);
+
+/* Do a safe check on given fileinfo list and remove entries that
+   1. Do not conform with global rules given in opt.accepts or opt.rejects.
+   2. Files with possible harmful names.
+   3. Entries that do not match given globbing pattern if glob_action is
+      GLOB_GLOBALL or GLOB_GETONE.
+   
+   If argument "file" is null, entries are not matched against any globbing
+   pattern. */
+static uerr_t
+fileinfo_cleanup (struct fileinfo **start, const char *file, int glob_action)
+{
+  struct fileinfo *f;
+
+  /* First: weed out that do not conform the global rules given in
+     opt.accepts and opt.rejects.  */
+  if (opt.accepts || opt.rejects)
+    {
+      f = *start;
+      while (f)
+        {
+          if (f->type != FT_DIRECTORY && !acceptable (f->name))
+            {
+              logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
+                         quote (f->name));
+              f = delelement (f, start);
+            }
+          else
+            f = f->next;
+        }
+    }
+  /* Remove all files with possible harmful names */
+  f = *start;
+  while (f)
+    {
+      if (has_insecure_name_p (f->name))
+        {
+          logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
+                     quote (f->name));
+          f = delelement (f, start);
+        }
+      else
+        f = f->next;
+    }
+  /* Now weed out the files that do not match our globbing pattern.
+     If we are dealing with a globbing pattern, that is.  */
+  if (file)
+    {
+      if (glob_action == GLOB_GLOBALL)
+        {
+          int (*matcher) (const char *, const char *, int)
+            = opt.ignore_case ? fnmatch_nocase : fnmatch;
+          int matchres = 0;
+
+          f = *start;
+          while (f)
+            {
+              matchres = matcher (file, f->name, 0);
+              if (matchres == -1)
+                {
+                  logprintf (LOG_NOTQUIET, _("Error matching %s against %s: %s\n"),
+                             file, quotearg_style (escape_quoting_style, f->name),
+                             strerror (errno));
+                  break;
+                }
+              if (matchres == FNM_NOMATCH)
+                f = delelement (f, start); /* delete the element from the list */
+              else
+                f = f->next;        /* leave the element in the list */
+            }
+          if (matchres == -1)
+            {
+              freefileinfo (*start);
+              return RETRBADPATTERN;
+            }
+        }
+      else if (glob_action == GLOB_GETONE)
+        {
+#ifdef __VMS
+          /* 2009-09-09 SMS.
+           * Odd-ball compiler ("HP C V7.3-009 on OpenVMS Alpha V7.3-2")
+           * bug causes spurious %CC-E-BADCONDIT complaint with this
+           * "?:" statement.  (Different linkage attributes for strcmp()
+           * and strcasecmp().)  Converting to "if" changes the
+           * complaint to %CC-W-PTRMISMATCH on "cmp = strcmp;".  Adding
+           * the senseless type cast clears the complaint, and looks
+           * harmless.
+           */
+          int (*cmp) (const char *, const char *)
+            = opt.ignore_case ? strcasecmp : (int (*)())strcmp;
+#else /* def __VMS */
+          int (*cmp) (const char *, const char *)
+            = opt.ignore_case ? strcasecmp : strcmp;
+#endif /* def __VMS [else] */
+          f = *start;
+          while (f)
+            {
+              if (0 != cmp(file, f->name))
+                f = delelement (f, start);
+              else
+                f = f->next;
+            }
+        }
+    }
+  return RETROK;
+}
 
 /* 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
@@ -1734,7 +1841,7 @@ static void freefileinfo (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 url *u, struct fileinfo *f, ccon *con)
+ftp_retrieve_list (struct url *u, struct fileinfo *f, ccon *con, int action)
 {
   static int depth = 0;
   uerr_t err;
@@ -1841,11 +1948,102 @@ The sizes do not match (local %s) -- retrieving.\n\n"),
       switch (f->type)
         {
         case FT_SYMLINK:
+          /* In case we are downloading just one file using timestamping
+             and this file is a SYMLINK, then we need to traverse this 
+             SYMLINK to the destination file, get its fileinfo and match
+             the timestamp with existing file and finaly download it if
+             everything is OK! */ 
+          if (action == GLOB_GETONE)
+            {
+              struct fileinfo *finfo_list;
+              struct url *tmp_url;
+              char *dir, *file, *tmp;
+
+              tmp_url = xnew0 (struct url);
+
+              split_path (f->linkto, &dir, &file);
+              if (!strcmp (dir, ""))
+                {
+                  xfree (dir);
+                  dir = xstrdup (u->dir);
+                }
+              tmp = concat_strings (u->host, ":", dir, "/", file, NULL);
+              
+              tmp_url->url = rewrite_shorthand_url (tmp);
+              tmp_url->scheme = u->scheme;
+              tmp_url->host = xstrdup (u->host);
+              tmp_url->port = u->port;
+              tmp_url->path = concat_strings (dir, "/", file, NULL);
+              if (u->params)
+                tmp_url->params = xstrdup (u->params);
+              if (u->query)
+                tmp_url->query = xstrdup (u->query);
+              if (u->fragment)
+                tmp_url->fragment = xstrdup (u->fragment);
+              tmp_url->dir = dir;
+              tmp_url->file = file;
+              if (u->user)
+                tmp_url->user = xstrdup (u->user);
+              if (u->passwd)
+                tmp_url->passwd = xstrdup (u->passwd);
+
+              dir = NULL;
+              file = NULL;
+              xfree(tmp);
+
+              err = ftp_get_listing (tmp_url, con, &finfo_list);
+              if (err != RETROK)
+                {
+                  url_free (tmp_url);
+                  return err;
+                }
+
+              err = fileinfo_cleanup (&finfo_list, tmp_url->file, action);
+              if (err != RETROK)
+                {
+                  url_free (tmp_url);
+                  return err;
+                }
+
+              url_free (tmp_url);
+
+              /* Copy the real file fileinfo */
+              f->type = finfo_list->type;
+              f->size = finfo_list->size;
+              f->tstamp = finfo_list->tstamp;
+              f->ptype = finfo_list->ptype;
+              f->perms = finfo_list->perms;
+              xfree (f->linkto);
+              if (finfo_list->linkto)
+                f->linkto = xstrdup (finfo_list->linkto);
+              else
+                f->linkto = NULL;
+              
+              freefileinfo (finfo_list);
+              
+              /* Reset FTP connection status and command
+                 to the state like it was in the beginning */
+              con->st &= ~ON_YOUR_OWN;
+              if (!(con->st & DONE_CWD))
+                con->cmd |= DO_CWD;
+              else
+                con->cmd &= ~DO_CWD;
+              con->cmd |= (DO_RETR | LEAVE_PENDING);
+
+              if (con->csock < 0)
+                con->cmd |= DO_LOGIN;
+              else
+                con->cmd &= ~DO_LOGIN;
+
+              /* Now we have correct fileinfo and can check timestamp
+                 of the real file. */
+              continue;
+            }
           /* If opt.retr_symlinks is defined, we treat symlinks as
              if they were normal files.  There is currently no way
              to distinguish whether they might be directories, and
              follow them.  */
-          if (!opt.retr_symlinks)
+          else if (!opt.retr_symlinks)
             {
 #ifdef HAVE_SYMLINK
               if (!f->linkto)
@@ -2060,8 +2258,9 @@ has_insecure_name_p (const char *s)
 
 /* A near-top-level function to retrieve the files in a directory.
    The function calls ftp_get_listing, to get a linked list of files.
-   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.
+   Then it weeds out the file names that do not match the pattern
+   using fileinfo_cleanup function. ftp_retrieve_list is called with
+   this updated list as an argument.
 
    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
@@ -2078,100 +2277,15 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
   res = ftp_get_listing (u, con, &start);
   if (res != RETROK)
     return res;
-  /* First: weed out that do not conform the global rules given in
-     opt.accepts and opt.rejects.  */
-  if (opt.accepts || opt.rejects)
-    {
-      f = start;
-      while (f)
-        {
-          if (f->type != FT_DIRECTORY && !acceptable (f->name))
-            {
-              logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
-                         quote (f->name));
-              f = delelement (f, &start);
-            }
-          else
-            f = f->next;
-        }
-    }
-  /* Remove all files with possible harmful names */
-  f = start;
-  while (f)
-    {
-      if (has_insecure_name_p (f->name))
-        {
-          logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
-                     quote (f->name));
-          f = delelement (f, &start);
-        }
-      else
-        f = f->next;
-    }
-  /* 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)
-    {
-      if (action == GLOB_GLOBALL)
-        {
-          int (*matcher) (const char *, const char *, int)
-            = opt.ignore_case ? fnmatch_nocase : fnmatch;
-          int matchres = 0;
+  
+  res = fileinfo_cleanup (&start, u->file, action);
+  if (res != RETROK)
+    return res;
 
-          f = start;
-          while (f)
-            {
-              matchres = matcher (u->file, f->name, 0);
-              if (matchres == -1)
-                {
-                  logprintf (LOG_NOTQUIET, _("Error matching %s against %s: %s\n"),
-                             u->file, quotearg_style (escape_quoting_style, f->name),
-                             strerror (errno));
-                  break;
-                }
-              if (matchres == FNM_NOMATCH)
-                f = delelement (f, &start); /* delete the element from the list */
-              else
-                f = f->next;        /* leave the element in the list */
-            }
-          if (matchres == -1)
-            {
-              freefileinfo (start);
-              return RETRBADPATTERN;
-            }
-        }
-      else if (action == GLOB_GETONE)
-        {
-#ifdef __VMS
-          /* 2009-09-09 SMS.
-           * Odd-ball compiler ("HP C V7.3-009 on OpenVMS Alpha V7.3-2")
-           * bug causes spurious %CC-E-BADCONDIT complaint with this
-           * "?:" statement.  (Different linkage attributes for strcmp()
-           * and strcasecmp().)  Converting to "if" changes the
-           * complaint to %CC-W-PTRMISMATCH on "cmp = strcmp;".  Adding
-           * the senseless type cast clears the complaint, and looks
-           * harmless.
-           */
-          int (*cmp) (const char *, const char *)
-            = opt.ignore_case ? strcasecmp : (int (*)())strcmp;
-#else /* def __VMS */
-          int (*cmp) (const char *, const char *)
-            = opt.ignore_case ? strcasecmp : strcmp;
-#endif /* def __VMS [else] */
-          f = start;
-          while (f)
-            {
-              if (0 != cmp(u->file, f->name))
-                f = delelement (f, &start);
-              else
-                f = f->next;
-            }
-        }
-    }
   if (start)
     {
       /* Just get everything.  */
-      res = ftp_retrieve_list (u, start, con);
+      res = ftp_retrieve_list (u, start, con, action);
     }
   else
     {
diff --git a/src/url.c b/src/url.c
index e44dfcd..4ae7d33 100644
--- a/src/url.c
+++ b/src/url.c
@@ -585,8 +585,6 @@ rewrite_shorthand_url (const char *url)
     }
   return ret;
 }
-
-static void split_path (const char *, char **, char **);
 
 /* Like strpbrk, with the exception that it returns the pointer to the
    terminating zero (end-of-string aka "eos") if no matching character
@@ -980,7 +978,7 @@ url_error (const char *url, int error_code)
 
    DIR and FILE are freshly allocated.  */
 
-static void
+void
 split_path (const char *path, char **dir, char **file)
 {
   char *last_slash = strrchr (path, '/');
diff --git a/src/url.h b/src/url.h
index edb6b06..da13e2c 100644
--- a/src/url.h
+++ b/src/url.h
@@ -111,4 +111,6 @@ bool schemes_are_similar_p (enum url_scheme a, enum url_scheme b);
 
 bool are_urls_equal (const char *u1, const char *u2);
 
+void split_path (const char *path, char **dir, char **file);
+
 #endif /* URL_H */
-- 
1.7.11.7

