]> sjero.net Git - wget/blobdiff - src/url.c
[svn] Declare the pointers to literal strings as `const'.
[wget] / src / url.c
index 132479428ec2e27497a1a1abfb95bc79a704e974..8baa9869483ee344ecfc4fe9936f0644fb7157d3 100644 (file)
--- a/src/url.c
+++ b/src/url.c
@@ -54,7 +54,8 @@ extern int errno;
 
 struct scheme_data
 {
-  char *leading_string;
+  const char *name;
+  const char *leading_string;
   int default_port;
   int enabled;
 };
@@ -62,29 +63,48 @@ struct scheme_data
 /* Supported schemes: */
 static struct scheme_data supported_schemes[] =
 {
-  { "http://",  DEFAULT_HTTP_PORT,  1 },
+  { "http",    "http://",  DEFAULT_HTTP_PORT,  1 },
 #ifdef HAVE_SSL
-  { "https://", DEFAULT_HTTPS_PORT, 1 },
+  { "https",   "https://", DEFAULT_HTTPS_PORT, 1 },
 #endif
-  { "ftp://",   DEFAULT_FTP_PORT,   1 },
+  { "ftp",     "ftp://",   DEFAULT_FTP_PORT,   1 },
 
   /* SCHEME_INVALID */
-  { NULL,       -1,                 0 }
+  { NULL,      NULL,       -1,                 0 }
 };
 
 /* Forward declarations: */
 
 static int path_simplify PARAMS ((char *));
 \f
-/* Support for encoding and decoding of URL strings.  We determine
-   whether a character is unsafe through static table lookup.  This
-   code assumes ASCII character set and 8-bit chars.  */
+/* Support for escaping and unescaping of URL strings.  */
+
+/* Table of "reserved" and "unsafe" characters.  Those terms are
+   rfc1738-speak, as such largely obsoleted by rfc2396 and later
+   specs, but the general idea remains.
+
+   A reserved character is the one that you can't decode without
+   changing the meaning of the URL.  For example, you can't decode
+   "/foo/%2f/bar" into "/foo///bar" because the number and contents of
+   path components is different.  Non-reserved characters can be
+   changed, so "/foo/%78/bar" is safe to change to "/foo/x/bar".  Wget
+   uses the rfc1738 set of reserved characters, plus "$" and ",", as
+   recommended by rfc2396.
+
+   An unsafe characters is the one that should be encoded when URLs
+   are placed in foreign environments.  E.g. space and newline are
+   unsafe in HTTP contexts because HTTP uses them as separator and
+   terminator, so they must be encoded to %20 and %0A respectively.
+   "*" is unsafe in shell context, etc.
+
+   We determine whether a character is unsafe through static table
+   lookup.  This code assumes ASCII character set and 8-bit chars.  */
 
 enum {
-  /* rfc1738 reserved chars, preserved from encoding.  */
+  /* rfc1738 reserved chars + "$" and ",".  */
   urlchr_reserved = 1,
 
-  /* rfc1738 unsafe chars, plus some more.  */
+  /* rfc1738 unsafe chars, plus non-printables.  */
   urlchr_unsafe   = 2
 };
 
@@ -103,8 +123,8 @@ const static unsigned char urlchr_table[256] =
   U,  U,  U,  U,   U,  U,  U,  U,   /* BS  HT  LF  VT   FF  CR  SO  SI  */
   U,  U,  U,  U,   U,  U,  U,  U,   /* DLE DC1 DC2 DC3  DC4 NAK SYN ETB */
   U,  U,  U,  U,   U,  U,  U,  U,   /* CAN EM  SUB ESC  FS  GS  RS  US  */
-  U,  0,  U, RU,   0,  U,  R,  0,   /* SP  !   "   #    $   %   &   '   */
-  0,  0,  0,  R,   0,  0,  0,  R,   /* (   )   *   +    ,   -   .   /   */
+  U,  0,  U, RU,   R,  U,  R,  0,   /* SP  !   "   #    $   %   &   '   */
+  0,  0,  0,  R,   R,  0,  0,  R,   /* (   )   *   +    ,   -   .   /   */
   0,  0,  0,  0,   0,  0,  0,  0,   /* 0   1   2   3    4   5   6   7   */
   0,  0, RU,  R,   U,  R,  U,  R,   /* 8   9   :   ;    <   =   >   ?   */
  RU,  0,  0,  0,   0,  0,  0,  0,   /* @   A   B   C    D   E   F   G   */
@@ -616,7 +636,7 @@ lowercase_str (char *str)
   return change;
 }
 
-static char *parse_errors[] = {
+static const char *parse_errors[] = {
 #define PE_NO_ERROR                    0
   N_("No error"),
 #define PE_UNSUPPORTED_SCHEME          1
@@ -892,25 +912,20 @@ url_parse (const char *url, int *error)
       p = strpbrk_or_eos (p, "/;?#");
       port_e = p;
 
-      if (port_b == port_e)
-       {
-         /* http://host:/whatever */
-         /*             ^         */
-          error_code = PE_BAD_PORT_NUMBER;
-         goto error;
-       }
-
-      for (port = 0, pp = port_b; pp < port_e; pp++)
+      /* Allow empty port, as per rfc2396. */
+      if (port_b != port_e)
        {
-         if (!ISDIGIT (*pp))
+         for (port = 0, pp = port_b; pp < port_e; pp++)
            {
-             /* http://host:12randomgarbage/blah */
-             /*               ^                  */
-              error_code = PE_BAD_PORT_NUMBER;
-              goto error;
+             if (!ISDIGIT (*pp))
+               {
+                 /* http://host:12randomgarbage/blah */
+                 /*               ^                  */
+                 error_code = PE_BAD_PORT_NUMBER;
+                 goto error;
+               }
+             port = 10 * port + (*pp - '0');
            }
-         
-         port = 10 * port + (*pp - '0');
        }
     }
 
@@ -971,9 +986,7 @@ url_parse (const char *url, int *error)
        }
     }
 
-  u = (struct url *)xmalloc (sizeof (struct url));
-  memset (u, 0, sizeof (*u));
-
+  u = xnew0 (struct url);
   u->scheme = scheme;
   u->host   = strdupdelim (host_b, host_e);
   u->port   = port;
@@ -1238,11 +1251,11 @@ url_free (struct url *url)
   xfree (url->path);
   xfree (url->url);
 
-  FREE_MAYBE (url->params);
-  FREE_MAYBE (url->query);
-  FREE_MAYBE (url->fragment);
-  FREE_MAYBE (url->user);
-  FREE_MAYBE (url->passwd);
+  xfree_null (url->params);
+  xfree_null (url->query);
+  xfree_null (url->fragment);
+  xfree_null (url->user);
+  xfree_null (url->passwd);
 
   xfree (url->dir);
   xfree (url->file);
@@ -1457,22 +1470,31 @@ append_uri_pathel (const char *b, const char *e, int escaped_p,
       e = unescaped + strlen (unescaped);
     }
 
+  /* Defang ".." when found as component of path.  Remember that path
+     comes from the URL and might contain malicious input.  */
+  if (e - b == 2 && b[0] == '.' && b[1] == '.')
+    {
+      b = "%2E%2E";
+      e = b + 6;
+    }
+
   /* Walk the PATHEL string and check how many characters we'll need
-     to add for file quoting.  */
+     to quote.  */
   quoted = 0;
   for (p = b; p < e; p++)
     if (FILE_CHAR_TEST (*p, mask))
       ++quoted;
 
-  /* e-b is the string length.  Each quoted char means two additional
+  /* Calculate the length of the output string.  e-b is the input
+     string length.  Each quoted char introduces two additional
      characters in the string, hence 2*quoted.  */
   outlen = (e - b) + (2 * quoted);
   GROW (dest, outlen);
 
   if (!quoted)
     {
-      /* If there's nothing to quote, we don't need to go through the
-        string the second time.  */
+      /* If there's nothing to quote, we can simply append the string
+        without processing it again.  */
       memcpy (TAIL (dest), b, outlen);
     }
   else
@@ -1541,7 +1563,7 @@ url_file_name (const struct url *u)
 {
   struct growable fnres;
 
-  char *u_file, *u_query;
+  const char *u_file, *u_query;
   char *fname, *unique;
 
   fnres.base = NULL;
@@ -1557,6 +1579,12 @@ url_file_name (const struct url *u)
      directory structure.  */
   if (opt.dirstruct)
     {
+      if (opt.protocol_directories)
+       {
+         if (fnres.tail)
+           append_char ('/', &fnres);
+         append_string (supported_schemes[u->scheme].name, &fnres);
+       }
       if (opt.add_hostdir)
        {
          if (fnres.tail)
@@ -1611,29 +1639,6 @@ url_file_name (const struct url *u)
     xfree (fname);
   return unique;
 }
-
-/* Return the length of URL's path.  Path is considered to be
-   terminated by one of '?', ';', '#', or by the end of the
-   string.  */
-static int
-path_length (const char *url)
-{
-  const char *q = strpbrk_or_eos (url, "?;#");
-  return q - url;
-}
-
-/* Find the last occurrence of character C in the range [b, e), or
-   NULL, if none are present.  This is equivalent to strrchr(b, c),
-   except that it accepts an END argument instead of requiring the
-   string to be zero-terminated.  Why is there no memrchr()?  */
-static const char *
-find_last_char (const char *b, const char *e, char c)
-{
-  for (; e > b; e--)
-    if (*e == c)
-      return e;
-  return NULL;
-}
 \f
 /* Resolve "." and ".." elements of PATH by destructively modifying
    PATH and return non-zero if PATH has been modified, zero otherwise.
@@ -1657,15 +1662,10 @@ find_last_char (const char *b, const char *e, char c)
 static int
 path_simplify (char *path)
 {
-  char *h, *t, *end;
-
-  /* Preserve the leading '/'. */
-  if (path[0] == '/')
-    ++path;
-
-  h = path;                    /* hare */
-  t = path;                    /* tortoise */
-  end = path + strlen (path);
+  char *h = path;              /* hare */
+  char *t = path;              /* tortoise */
+  char *beg = path;            /* boundary for backing the tortoise */
+  char *end = path + strlen (path);
 
   while (h < end)
     {
@@ -1679,28 +1679,27 @@ path_simplify (char *path)
       else if (h[0] == '.' && h[1] == '.' && (h[2] == '/' || h[2] == '\0'))
        {
          /* Handle "../" by retreating the tortoise by one path
-            element -- but not past beggining of PATH.  */
-         if (t > path)
+            element -- but not past beggining.  */
+         if (t > beg)
            {
              /* Move backwards until T hits the beginning of the
                 previous path element or the beginning of path. */
-             for (--t; t > path && t[-1] != '/'; t--)
+             for (--t; t > beg && t[-1] != '/'; t--)
                ;
            }
+         else
+           {
+             /* If we're at the beginning, copy the "../" literally
+                move the beginning so a later ".." doesn't remove
+                it.  */
+             beg = t + 3;
+             goto regular;
+           }
          h += 3;
        }
-      else if (*h == '/')
-       {
-         /* Ignore empty path elements.  Supporting them well is hard
-            (where do you save "http://x.com///y.html"?), and they
-            don't bring any practical gain.  Plus, they break our
-            filesystem-influenced assumptions: allowing them would
-            make "x/y//../z" simplify to "x/y/z", whereas most people
-            would expect "x/z".  */
-         ++h;
-       }
       else
        {
+       regular:
          /* A regular path element.  If H hasn't advanced past T,
             simply skip to the next path element.  Otherwise, copy
             the path element until the next slash.  */
@@ -1729,6 +1728,30 @@ path_simplify (char *path)
   return t != h;
 }
 \f
+/* Return the length of URL's path.  Path is considered to be
+   terminated by one of '?', ';', '#', or by the end of the
+   string.  */
+
+static int
+path_length (const char *url)
+{
+  const char *q = strpbrk_or_eos (url, "?;#");
+  return q - url;
+}
+
+/* Find the last occurrence of character C in the range [b, e), or
+   NULL, if none are present.  We might want to use memrchr (a GNU
+   extension) under GNU libc.  */
+
+static const char *
+find_last_char (const char *b, const char *e, char c)
+{
+  for (; e > b; e--)
+    if (*e == c)
+      return e;
+  return NULL;
+}
+
 /* Merge BASE with LINK and return the resulting URI.
 
    Either of the URIs may be absolute or relative, complete with the
@@ -1736,8 +1759,10 @@ path_simplify (char *path)
    foreseeable cases.  It only employs minimal URL parsing, without
    knowledge of the specifics of schemes.
 
-   Perhaps this function should call path_simplify so that the callers
-   don't have to call url_parse unconditionally.  */
+   I briefly considered making this function call path_simplify after
+   the merging process, as rfc1738 seems to suggest.  This is a bad
+   idea for several reasons: 1) it complexifies the code, and 2)
+   url_parse has to simplify path anyway, so it's wasteful to boot.  */
 
 char *
 uri_merge (const char *base, const char *link)
@@ -1887,24 +1912,8 @@ uri_merge (const char *base, const char *link)
       const char *last_slash = find_last_char (base, end, '/');
       if (!last_slash)
        {
-         /* No slash found at all.  Append LINK to what we have,
-            but we'll need a slash as a separator.
-
-            Example: if base == "foo" and link == "qux/xyzzy", then
-            we cannot just append link to base, because we'd get
-            "fooqux/xyzzy", whereas what we want is
-            "foo/qux/xyzzy".
-
-            To make sure the / gets inserted, we set
-            need_explicit_slash to 1.  We also set start_insert
-            to end + 1, so that the length calculations work out
-            correctly for one more (slash) character.  Accessing
-            that character is fine, since it will be the
-            delimiter, '\0' or '?'.  */
-         /* example: "foo?..." */
-         /*               ^    ('?' gets changed to '/') */
-         start_insert = end + 1;
-         need_explicit_slash = 1;
+         /* No slash found at all.  Replace what we have with LINK. */
+         start_insert = base;
        }
       else if (last_slash && last_slash >= base + 2
               && last_slash[-2] == ':' && last_slash[-1] == '/')
@@ -1961,10 +1970,10 @@ url_string (const struct url *url, int hide_password)
   char *quoted_user = NULL, *quoted_passwd = NULL;
 
   int scheme_port  = supported_schemes[url->scheme].default_port;
-  char *scheme_str = supported_schemes[url->scheme].leading_string;
+  const char *scheme_str = supported_schemes[url->scheme].leading_string;
   int fplen = full_path_length (url);
 
-  int brackets_around_host = 0;
+  int brackets_around_host;
 
   assert (scheme_str != NULL);
 
@@ -1981,8 +1990,9 @@ url_string (const struct url *url, int hide_password)
        }
     }
 
-  if (strchr (url->host, ':'))
-    brackets_around_host = 1;
+  /* Numeric IPv6 addresses can contain ':' and need to be quoted with
+     brackets.  */
+  brackets_around_host = strchr (url->host, ':') != NULL;
 
   size = (strlen (scheme_str)
          + strlen (url->host)
@@ -2083,10 +2093,10 @@ run_test (char *test, char *expected_result, int expected_change)
   if (modified != expected_change)
     {
       if (expected_change == 1)
-       printf ("Expected no modification with path_simplify(\"%s\").\n",
+       printf ("Expected modification with path_simplify(\"%s\").\n",
                test);
       else
-       printf ("Expected modification with path_simplify(\"%s\").\n",
+       printf ("Expected no modification with path_simplify(\"%s\").\n",
                test);
     }
   xfree (test_copy);
@@ -2099,24 +2109,28 @@ test_path_simplify (void)
     char *test, *result;
     int should_modify;
   } tests[] = {
-    { "",              "",             0 },
-    { ".",             "",             1 },
-    { "..",            "",             1 },
-    { "foo",           "foo",          0 },
-    { "foo/bar",       "foo/bar",      0 },
-    { "foo///bar",     "foo/bar",      1 },
-    { "foo/.",         "foo/",         1 },
-    { "foo/./",                "foo/",         1 },
-    { "foo./",         "foo./",        0 },
-    { "foo/../bar",    "bar",          1 },
-    { "foo/../bar/",   "bar/",         1 },
-    { "foo/bar/..",    "foo/",         1 },
-    { "foo/bar/../x",  "foo/x",        1 },
-    { "foo/bar/../x/", "foo/x/",       1 },
-    { "foo/..",                "",             1 },
-    { "foo/../..",     "",             1 },
-    { "a/b/../../c",   "c",            1 },
-    { "./a/../b",      "b",            1 }
+    { "",                      "",             0 },
+    { ".",                     "",             1 },
+    { "./",                    "",             1 },
+    { "..",                    "..",           0 },
+    { "../",                   "../",          0 },
+    { "foo",                   "foo",          0 },
+    { "foo/bar",               "foo/bar",      0 },
+    { "foo///bar",             "foo///bar",    0 },
+    { "foo/.",                 "foo/",         1 },
+    { "foo/./",                        "foo/",         1 },
+    { "foo./",                 "foo./",        0 },
+    { "foo/../bar",            "bar",          1 },
+    { "foo/../bar/",           "bar/",         1 },
+    { "foo/bar/..",            "foo/",         1 },
+    { "foo/bar/../x",          "foo/x",        1 },
+    { "foo/bar/../x/",         "foo/x/",       1 },
+    { "foo/..",                        "",             1 },
+    { "foo/../..",             "..",           1 },
+    { "foo/../../..",          "../..",        1 },
+    { "foo/../../bar/../../baz", "../../baz",  1 },
+    { "a/b/../../c",           "c",            1 },
+    { "./a/../b",              "b",            1 }
   };
   int i;
 
@@ -2127,24 +2141,5 @@ test_path_simplify (void)
       int   expected_change = tests[i].should_modify;
       run_test (test, expected_result, expected_change);
     }
-
-  /* Now run all the tests with a leading slash before the test case,
-     to prove that the slash is being preserved.  */
-  for (i = 0; i < countof (tests); i++)
-    {
-      char *test, *expected_result;
-      int expected_change = tests[i].should_modify;
-
-      test = xmalloc (1 + strlen (tests[i].test) + 1);
-      sprintf (test, "/%s", tests[i].test);
-
-      expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
-      sprintf (expected_result, "/%s", tests[i].result);
-
-      run_test (test, expected_result, expected_change);
-
-      xfree (test);
-      xfree (expected_result);
-    }
 }
 #endif