]> sjero.net Git - wget/blobdiff - src/http.c
[svn] Parse Content-Disposition better. Implement memrchr where missing.
[wget] / src / http.c
index c69739cbd3a399c9190af6ab2fdb3355987de390..9668d5043b1bebddba547815a9581d61f1655aa5 100644 (file)
@@ -60,6 +60,10 @@ so, delete this exception statement from your version.  */
 #endif
 #include "convert.h"
 
+#ifdef TESTING
+#include "test.h"
+#endif
+
 extern char *version_string;
 
 #ifndef MIN
@@ -850,6 +854,143 @@ skip_short_body (int fd, wgint contlen)
   DEBUGP (("] done.\n"));
   return true;
 }
+
+static bool
+extract_param_value_delim (const char *begin, const char *end, 
+                           const char *param_name, char **param_value)
+{
+  const char *p; 
+  int len;  
+
+  assert (begin);
+  assert (end);
+  assert (param_name);
+  assert (param_value);
+
+  len = strlen (param_name);
+
+  /* skip initial whitespaces */
+  p = begin;
+  while (*p && ISSPACE (*p) && p < end) ++p;
+  
+  if (end - p > len
+      && 0 == strncasecmp (p, param_name, len))
+    {
+      const char *e;
+
+      /* skip white spaces, equal sign and inital quote */
+      p += len;
+      while (*p && (ISSPACE (*p) || *p == '\"' || *p == '=') && p < end) ++p;
+
+      /* find last quote */
+      e = p;
+      while (*e && *e != '\"' && e < end) ++e;
+      
+      *param_value = strdupdelim (p, e);
+      
+      return true;
+    }
+
+  return false;
+}
+
+typedef struct {
+  /* A token consists of characters in the [b, e) range. */
+  const char *b, *e;
+} param_token;
+
+/* Extract a parameter from the HTTP header at *SOURCE and advance
+   *SOURCE to the next parameter.  Return false when there are no more
+   parameters to extract.  The name of the parameter is returned in
+   NAME, and the value in VALUE.  If the parameter has no value, the
+   token's value is zeroed out.
+
+   For example, if *SOURCE points to the string "attachment;
+   filename=\"foo bar\"", the first call to this function will return
+   the token named "attachment" and no value, and the second call will
+   return the token named "filename" and value "foo bar".  The third
+   call will return false, indicating no more valid tokens.  */
+
+static bool
+extract_param (const char **source, param_token *name, param_token *value)
+{
+  const char *p = *source;
+
+  while (ISSPACE (*p)) ++p;
+  if (!*p)
+    return false;              /* nothing more to extract */
+
+  /* Extract name. */
+  name->b = p;
+  while (*p && !ISSPACE (*p) && *p != '=' && *p != ';') ++p;
+  name->e = p;
+  while (ISSPACE (*p)) ++p;
+  if (*p == ';' || !*p)                /* no value */
+    {
+      xzero (*value);
+      if (*p == ';') ++p;
+      *source = p;
+      return true;
+    }
+  if (*p != '=')
+    return false;              /* error */
+
+  /* *p is '=', extract value */
+  ++p;
+  while (ISSPACE (*p)) ++p;
+  if (*p == '"')               /* quoted */
+    {
+      value->b = ++p;
+      while (*p && *p != '"') ++p;
+      if (!*p)
+        return false;
+      value->e = p++;
+      /* Currently at closing quote; find the end of param. */
+      while (ISSPACE (*p)) ++p;
+      while (*p && *p != ';') ++p;
+      if (*p == ';')
+       ++p;
+      else if (*p)
+       /* garbage after closed quote, e.g. foo="bar"baz */
+       return false;
+    }
+  else                         /* unquoted */
+    {
+      value->b = p;
+      while (*p && *p != ';') ++p;
+      value->e = p;
+      while (value->e != value->b && ISSPACE (value->e[-1]))
+        --value->e;
+      if (*p == ';') ++p;
+    }
+  *source = p;
+  return true;
+}
+
+#undef MAX
+#define MAX(p, q) ((p) > (q) ? (p) : (q))
+
+static bool
+parse_content_disposition (const char *hdr, char **filename)
+{
+  param_token name, value;
+  while (extract_param (&hdr, &name, &value))
+    if (BOUNDED_EQUAL_NO_CASE (name.b, name.e, "filename") && value.b != NULL)
+      {
+       /* Make the file name begin at the last slash or backslash. */
+        const char *last_slash = memrchr (value.b, '/', value.e - value.b);
+        const char *last_bs = memrchr (value.b, '\\', value.e - value.b);
+        if (last_slash && last_bs)
+          value.b = 1 + MAX (last_slash, last_bs);
+        else if (last_slash || last_bs)
+          value.b = 1 + (last_slash ? last_slash : last_bs);
+       if (value.b == value.e)
+         continue;
+        *filename = strdupdelim (value.b, value.e);
+        return true;
+      }
+  return false;
+}
 \f
 /* Persistent connections.  Currently, we cache the most recently used
    connection as persistent, provided that the HTTP server agrees to
@@ -1200,8 +1341,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
   bool host_lookup_failed = false;
 
-  DEBUGP(("in gethttp 1\n"));
-  
 #ifdef HAVE_SSL
   if (u->scheme == SCHEME_HTTPS)
     {
@@ -1217,9 +1356,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     }
 #endif /* HAVE_SSL */
 
-  DEBUGP(("in gethttp 2\n"));
-  DEBUGP(("in gethttp 3\n"));
-  
   /* Initialize certain elements of struct http_stat.  */
   hs->len = 0;
   hs->contlen = -1;
@@ -1609,26 +1745,19 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       print_server_response (resp, "  ");
     }
 
-  DEBUGP(("in gethttp 4\n"));
-  
   /* Determine the local filename if needed. Notice that if -O is used 
    * hstat.local_file is set by http_loop to the argument of -O. */
   if (!hs->local_file)     
     {
-      if (resp_header_copy (resp, "Content-Disposition", hdrval, sizeof (hdrval)))
-        /* Honor Content-Disposition. */
-        {
-          hs->local_file = xstrdup (hdrval);
-        }
-      else
-        /* Choose filename according to URL name. */
+      /* Honor Content-Disposition whether possible. */
+      if (!resp_header_copy (resp, "Content-Disposition", hdrval, sizeof (hdrval))
+          || !parse_content_disposition (hdrval, &hs->local_file))
         {
+          /* Choose filename according to URL name. */
           hs->local_file = url_file_name (u);
         }
     }
   
-  DEBUGP(("in gethttp 5\n"));
-
   /* TODO: perform this check only once. */
   if (opt.noclobber && file_exists_p (hs->local_file))
     {
@@ -1914,7 +2043,7 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
           strcpy(hs->local_file + local_filename_len, ".html");
           /* If clobbering is not allowed and the file, as named,
              exists, tack on ".NUMBER.html" instead. */
-          if (!ALLOW_CLOBBER)
+          if (!ALLOW_CLOBBER && file_exists_p (hs->local_file))
             {
               int ext_num = 1;
               do
@@ -2013,6 +2142,13 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
       return RETRFINISHED;
     }
 
+  /* Print fetch message, if opt.verbose.  */
+  if (opt.verbose)
+    {
+      logprintf (LOG_NOTQUIET, _("Saving to: `%s'\n"), 
+                 HYPHENP (hs->local_file) ? "STDOUT" : hs->local_file);
+    }
+    
   /* Open the local file.  */
   if (!output_stream)
     {
@@ -2106,8 +2242,6 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   struct http_stat hstat;        /* HTTP status */
   struct_stat st;
 
-  DEBUGP(("in http_loop\n"));
-
   /* Assert that no value for *LOCAL_FILE was passed. */
   assert (local_file == NULL || *local_file == NULL);
   
@@ -2144,8 +2278,6 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   /* THE loop */
   do
     {
-      DEBUGP(("in http_loop LOOP\n"));
-
       /* Increment the pass counter.  */
       ++count;
       sleep_between_retrievals (count);
@@ -2157,12 +2289,20 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
       if (opt.verbose)
         {
           char *hurl = url_string (u, true);
-          char tmp[256];
-          strcpy (tmp, "        ");
-          if (count > 1)
-            sprintf (tmp, _("(try:%2d)"), count);
-          logprintf (LOG_VERBOSE, "--%s--  %s\n  %s\n",
-                     tms, hurl, tmp);
+          
+          if (count > 1) 
+            {
+              char tmp[256];
+              sprintf (tmp, _("(try:%2d)"), count);
+              logprintf (LOG_NOTQUIET, "--%s--  %s  %s\n",
+                         tms, tmp, hurl);
+            }
+          else 
+            {
+              logprintf (LOG_NOTQUIET, "--%s--  %s\n",
+                         tms, hurl);
+            }
+          
 #ifdef WINDOWS
           ws_changetitle (hurl);
 #endif
@@ -2222,7 +2362,7 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
           /* Non-fatal errors continue executing the loop, which will
              bring them to "while" statement at the end, to judge
              whether the number of tries was exceeded.  */
-          //free_hstat (&hstat);
+          /* free_hstat (&hstat); */
           printwhat (count, opt.ntry);
           continue;
         case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED: 
@@ -2334,9 +2474,11 @@ The sizes do not match (local %s) -- retrieving.\n"),
               else
                 logputs (LOG_VERBOSE,
                          _("Remote file is newer, retrieving.\n"));
+
+              logputs (LOG_VERBOSE, "\n");
             }
           
-          //free_hstat (&hstat);
+          /* free_hstat (&hstat); */
           hstat.timestamp_checked = true;
           continue;
         }
@@ -2434,7 +2576,7 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          _("%s (%s) - Connection closed at byte %s. "),
                          tms, tmrate, number_to_static_string (hstat.len));
               printwhat (count, opt.ntry);
-              //free_hstat (&hstat);
+              /* free_hstat (&hstat); */
               continue;
             }
           else
@@ -2451,7 +2593,7 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          tms, tmrate, number_to_static_string (hstat.len),
                          hstat.rderrmsg);
               printwhat (count, opt.ntry);
-              //free_hstat (&hstat);
+              /* free_hstat (&hstat); */
               continue;
             }
           else /* hstat.res == -1 and contlen is given */
@@ -2463,7 +2605,7 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          number_to_static_string (hstat.contlen),
                          hstat.rderrmsg);
               printwhat (count, opt.ntry);
-              //free_hstat (&hstat);
+              /* free_hstat (&hstat); */
               continue;
             }
         }
@@ -2892,6 +3034,42 @@ http_cleanup (void)
     cookie_jar_delete (wget_cookie_jar);
 }
 
+
+#ifdef TESTING
+
+char *
+test_parse_content_disposition()
+{
+  int i;
+  struct {
+    char *hdrval;    
+    char *filename;
+    bool result;
+  } test_array[] = {
+    { "filename=\"file.ext\"", "file.ext", true },
+    { "attachment; filename=\"file.ext\"", "file.ext", true },
+    { "attachment; filename=\"file.ext\"; dummy", "file.ext", true },
+    { "attachment", NULL, false },    
+  };
+  
+  for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i) 
+    {
+      char *filename;
+      bool res = parse_content_disposition (test_array[i].hdrval, &filename);
+
+      mu_assert ("test_parse_content_disposition: wrong result", 
+                 res == test_array[i].result
+                 && (res == false 
+                     || 0 == strcmp (test_array[i].filename, filename)));
+
+      /* printf ("test %d: %s\n", i, res == false ? "false" : filename); */
+    }
+
+  return NULL;
+}
+
+#endif /* TESTING */
+
 /*
  * vim: et ts=2 sw=2
  */