]> sjero.net Git - wget/blob - src/url.c
[svn] Don't needlessly decode % escapes in URLs.
[wget] / src / url.c
1 /* URL handling.
2    Copyright (C) 2005 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 #include <config.h>
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #ifdef HAVE_STRING_H
35 # include <string.h>
36 #else
37 # include <strings.h>
38 #endif
39 #include <sys/types.h>
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif
43 #include <errno.h>
44 #include <assert.h>
45
46 #include "wget.h"
47 #include "utils.h"
48 #include "url.h"
49 #include "host.h"  /* for is_valid_ipv6_address */
50
51 #ifndef errno
52 extern int errno;
53 #endif
54
55 struct scheme_data
56 {
57   const char *name;
58   const char *leading_string;
59   int default_port;
60   int enabled;
61 };
62
63 /* Supported schemes: */
64 static struct scheme_data supported_schemes[] =
65 {
66   { "http",     "http://",  DEFAULT_HTTP_PORT,  1 },
67 #ifdef HAVE_SSL
68   { "https",    "https://", DEFAULT_HTTPS_PORT, 1 },
69 #endif
70   { "ftp",      "ftp://",   DEFAULT_FTP_PORT,   1 },
71
72   /* SCHEME_INVALID */
73   { NULL,       NULL,       -1,                 0 }
74 };
75
76 /* Forward declarations: */
77
78 static int path_simplify PARAMS ((char *));
79 \f
80 /* Support for escaping and unescaping of URL strings.  */
81
82 /* Table of "reserved" and "unsafe" characters.  Those terms are
83    rfc1738-speak, as such largely obsoleted by rfc2396 and later
84    specs, but the general idea remains.
85
86    A reserved character is the one that you can't decode without
87    changing the meaning of the URL.  For example, you can't decode
88    "/foo/%2f/bar" into "/foo///bar" because the number and contents of
89    path components is different.  Non-reserved characters can be
90    changed, so "/foo/%78/bar" is safe to change to "/foo/x/bar".  The
91    unsafe characters are loosely based on rfc1738, plus "$" and ",",
92    as recommended by rfc2396, and minus "~", which is very frequently
93    used (and sometimes unrecognized as %7E by broken servers).
94
95    An unsafe character is the one that should be encoded when URLs are
96    placed in foreign environments.  E.g. space and newline are unsafe
97    in HTTP contexts because HTTP uses them as separator and line
98    terminator, so they must be encoded to %20 and %0A respectively.
99    "*" is unsafe in shell context, etc.
100
101    We determine whether a character is unsafe through static table
102    lookup.  This code assumes ASCII character set and 8-bit chars.  */
103
104 enum {
105   /* rfc1738 reserved chars + "$" and ",".  */
106   urlchr_reserved = 1,
107
108   /* rfc1738 unsafe chars, plus non-printables.  */
109   urlchr_unsafe   = 2
110 };
111
112 #define urlchr_test(c, mask) (urlchr_table[(unsigned char)(c)] & (mask))
113 #define URL_RESERVED_CHAR(c) urlchr_test(c, urlchr_reserved)
114 #define URL_UNSAFE_CHAR(c) urlchr_test(c, urlchr_unsafe)
115
116 /* Shorthands for the table: */
117 #define R  urlchr_reserved
118 #define U  urlchr_unsafe
119 #define RU R|U
120
121 static const unsigned char urlchr_table[256] =
122 {
123   U,  U,  U,  U,   U,  U,  U,  U,   /* NUL SOH STX ETX  EOT ENQ ACK BEL */
124   U,  U,  U,  U,   U,  U,  U,  U,   /* BS  HT  LF  VT   FF  CR  SO  SI  */
125   U,  U,  U,  U,   U,  U,  U,  U,   /* DLE DC1 DC2 DC3  DC4 NAK SYN ETB */
126   U,  U,  U,  U,   U,  U,  U,  U,   /* CAN EM  SUB ESC  FS  GS  RS  US  */
127   U,  0,  U, RU,   R,  U,  R,  0,   /* SP  !   "   #    $   %   &   '   */
128   0,  0,  0,  R,   R,  0,  0,  R,   /* (   )   *   +    ,   -   .   /   */
129   0,  0,  0,  0,   0,  0,  0,  0,   /* 0   1   2   3    4   5   6   7   */
130   0,  0, RU,  R,   U,  R,  U,  R,   /* 8   9   :   ;    <   =   >   ?   */
131  RU,  0,  0,  0,   0,  0,  0,  0,   /* @   A   B   C    D   E   F   G   */
132   0,  0,  0,  0,   0,  0,  0,  0,   /* H   I   J   K    L   M   N   O   */
133   0,  0,  0,  0,   0,  0,  0,  0,   /* P   Q   R   S    T   U   V   W   */
134   0,  0,  0, RU,   U, RU,  U,  0,   /* X   Y   Z   [    \   ]   ^   _   */
135   U,  0,  0,  0,   0,  0,  0,  0,   /* `   a   b   c    d   e   f   g   */
136   0,  0,  0,  0,   0,  0,  0,  0,   /* h   i   j   k    l   m   n   o   */
137   0,  0,  0,  0,   0,  0,  0,  0,   /* p   q   r   s    t   u   v   w   */
138   0,  0,  0,  U,   U,  U,  0,  U,   /* x   y   z   {    |   }   ~   DEL */
139
140   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
141   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
142   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
143   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
144
145   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
146   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
147   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
148   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
149 };
150 #undef R
151 #undef U
152 #undef RU
153
154 /* URL-unescape the string S.
155
156    This is done by transforming the sequences "%HH" to the character
157    represented by the hexadecimal digits HH.  If % is not followed by
158    two hexadecimal digits, it is inserted literally.
159
160    The transformation is done in place.  If you need the original
161    string intact, make a copy before calling this function.  */
162
163 static void
164 url_unescape (char *s)
165 {
166   char *t = s;                  /* t - tortoise */
167   char *h = s;                  /* h - hare     */
168
169   for (; *h; h++, t++)
170     {
171       if (*h != '%')
172         {
173         copychar:
174           *t = *h;
175         }
176       else
177         {
178           char c;
179           /* Do nothing if '%' is not followed by two hex digits. */
180           if (!h[1] || !h[2] || !(ISXDIGIT (h[1]) && ISXDIGIT (h[2])))
181             goto copychar;
182           c = X2DIGITS_TO_NUM (h[1], h[2]);
183           /* Don't unescape %00 because there is no way to insert it
184              into a C string without effectively truncating it. */
185           if (c == '\0')
186             goto copychar;
187           *t = c;
188           h += 2;
189         }
190     }
191   *t = '\0';
192 }
193
194 /* The core of url_escape_* functions.  Escapes the characters that
195    match the provided mask in urlchr_table.
196
197    If ALLOW_PASSTHROUGH is non-zero, a string with no unsafe chars
198    will be returned unchanged.  If ALLOW_PASSTHROUGH is zero, a
199    freshly allocated string will be returned in all cases.  */
200
201 static char *
202 url_escape_1 (const char *s, unsigned char mask, int allow_passthrough)
203 {
204   const char *p1;
205   char *p2, *newstr;
206   int newlen;
207   int addition = 0;
208
209   for (p1 = s; *p1; p1++)
210     if (urlchr_test (*p1, mask))
211       addition += 2;            /* Two more characters (hex digits) */
212
213   if (!addition)
214     return allow_passthrough ? (char *)s : xstrdup (s);
215
216   newlen = (p1 - s) + addition;
217   newstr = (char *)xmalloc (newlen + 1);
218
219   p1 = s;
220   p2 = newstr;
221   while (*p1)
222     {
223       /* Quote the characters that match the test mask. */
224       if (urlchr_test (*p1, mask))
225         {
226           unsigned char c = *p1++;
227           *p2++ = '%';
228           *p2++ = XNUM_TO_DIGIT (c >> 4);
229           *p2++ = XNUM_TO_DIGIT (c & 0xf);
230         }
231       else
232         *p2++ = *p1++;
233     }
234   assert (p2 - newstr == newlen);
235   *p2 = '\0';
236
237   return newstr;
238 }
239
240 /* URL-escape the unsafe characters (see urlchr_table) in a given
241    string, returning a freshly allocated string.  */
242
243 char *
244 url_escape (const char *s)
245 {
246   return url_escape_1 (s, urlchr_unsafe, 0);
247 }
248
249 /* URL-escape the unsafe characters (see urlchr_table) in a given
250    string.  If no characters are unsafe, S is returned.  */
251
252 static char *
253 url_escape_allow_passthrough (const char *s)
254 {
255   return url_escape_1 (s, urlchr_unsafe, 1);
256 }
257 \f
258 enum copy_method { cm_encode, cm_passthrough };
259
260 /* Decide whether to encode or pass through the char at P.  This used
261    to be a macro, but it got a little too convoluted.  */
262
263 static inline enum copy_method
264 decide_copy_method (const char *p)
265 {
266   if (*p == '%')
267     {
268       if (ISXDIGIT (*(p + 1)) && ISXDIGIT (*(p + 2)))
269         /* Prior to 1.10 this decoded %HH escapes corresponding to
270            "safe" chars, but that proved too obtrusive -- it's better
271            to always preserve the escapes found in the URL.  */
272         return cm_passthrough;
273       else
274         /* Garbled %.. sequence: encode `%'. */
275         return cm_encode;
276     }
277   else if (URL_UNSAFE_CHAR (*p) && !URL_RESERVED_CHAR (*p))
278     return cm_encode;
279   else
280     return cm_passthrough;
281 }
282
283 /* Translate a %-escaped (but possibly non-conformant) input string S
284    into a %-escaped (and conformant) output string.  If no characters
285    are encoded or decoded, return the same string S; otherwise, return
286    a freshly allocated string with the new contents.
287
288    After a URL has been run through this function, the protocols that
289    use `%' as the quote character can use the resulting string as-is,
290    while those that don't can use url_unescape to get to the intended
291    data.  This function is stable: once the input is transformed,
292    further transformations of the result yield the same output.
293
294    Let's discuss why this function is needed.
295
296    Imagine Wget is asked to retrieve `http://abc.xyz/abc def'.  Since
297    a raw space character would mess up the HTTP request, it needs to
298    be quoted, like this:
299
300        GET /abc%20def HTTP/1.0
301
302    It appears that the unsafe chars need to be quoted, for example
303    with url_escape.  But what if we're requested to download
304    `abc%20def'?  url_escape transforms "%" to "%25", which would leave
305    us with `abc%2520def'.  This is incorrect -- since %-escapes are
306    part of URL syntax, "%20" is the correct way to denote a literal
307    space on the Wget command line.  This leaves us in the conclusion
308    that in that case Wget should not call url_escape, but leave the
309    `%20' as is.  This is clearly contradictory, but it only gets
310    worse.
311
312    What if the requested URI is `abc%20 def'?  If we call url_escape,
313    we end up with `/abc%2520%20def', which is almost certainly not
314    intended.  If we don't call url_escape, we are left with the
315    embedded space and cannot complete the request.  What the user
316    meant was for Wget to request `/abc%20%20def', and this is where
317    reencode_escapes kicks in.
318
319    Wget used to solve this by first decoding %-quotes, and then
320    encoding all the "unsafe" characters found in the resulting string.
321    This was wrong because it didn't preserve certain URL special
322    (reserved) characters.  For instance, URI containing "a%2B+b" (0x2b
323    == '+') would get translated to "a%2B%2Bb" or "a++b" depending on
324    whether we considered `+' reserved (it is).  One of these results
325    is inevitable because by the second step we would lose information
326    on whether the `+' was originally encoded or not.  Both results
327    were wrong because in CGI parameters + means space, while %2B means
328    literal plus.  reencode_escapes correctly translates the above to
329    "a%2B+b", i.e. returns the original string.
330
331    This function uses a modified version of the algorithm originally
332    proposed by Anon Sricharoenchai:
333
334    * Encode all "unsafe" characters, except those that are also
335      "reserved", to %XX.  See urlchr_table for which characters are
336      unsafe and reserved.
337
338    * Encode the "%" characters not followed by two hex digits to
339      "%25".
340
341    * Pass through all other characters and %XX escapes as-is.  (Up to
342      Wget 1.10 this decoded %XX escapes corresponding to "safe"
343      characters, but that was obtrusive and broke some servers.)
344
345    Anon's test case:
346
347    "http://abc.xyz/%20%3F%%36%31%25aa% a?a=%61+a%2Ba&b=b%26c%3Dc"
348    ->
349    "http://abc.xyz/%20%3F%25%36%31%25aa%25%20a?a=%61+a%2Ba&b=b%26c%3Dc"
350
351    Simpler test cases:
352
353    "foo bar"         -> "foo%20bar"
354    "foo%20bar"       -> "foo%20bar"
355    "foo %20bar"      -> "foo%20%20bar"
356    "foo%%20bar"      -> "foo%25%20bar"       (0x25 == '%')
357    "foo%25%20bar"    -> "foo%25%20bar"
358    "foo%2%20bar"     -> "foo%252%20bar"
359    "foo+bar"         -> "foo+bar"            (plus is reserved!)
360    "foo%2b+bar"      -> "foo%2b+bar"  */
361
362 static char *
363 reencode_escapes (const char *s)
364 {
365   const char *p1;
366   char *newstr, *p2;
367   int oldlen, newlen;
368
369   int encode_count = 0;
370
371   /* First, pass through the string to see if there's anything to do,
372      and to calculate the new length.  */
373   for (p1 = s; *p1; p1++)
374     {
375       switch (decide_copy_method (p1))
376         {
377         case cm_encode:
378           ++encode_count;
379           break;
380         case cm_passthrough:
381           break;
382         }
383     }
384
385   if (!encode_count)
386     /* The string is good as it is. */
387     return (char *) s;          /* C const model sucks. */
388
389   oldlen = p1 - s;
390   /* Each encoding adds two characters (hex digits).  */
391   newlen = oldlen + 2 * encode_count;
392   newstr = xmalloc (newlen + 1);
393
394   p1 = s;
395   p2 = newstr;
396
397   while (*p1)
398     {
399       switch (decide_copy_method (p1))
400         {
401         case cm_encode:
402           {
403             unsigned char c = *p1++;
404             *p2++ = '%';
405             *p2++ = XNUM_TO_DIGIT (c >> 4);
406             *p2++ = XNUM_TO_DIGIT (c & 0xf);
407           }
408           break;
409         case cm_passthrough:
410           *p2++ = *p1++;
411         }
412     }
413   *p2 = '\0';
414   assert (p2 - newstr == newlen);
415   return newstr;
416 }
417 \f
418 /* Returns the scheme type if the scheme is supported, or
419    SCHEME_INVALID if not.  */
420
421 enum url_scheme
422 url_scheme (const char *url)
423 {
424   int i;
425
426   for (i = 0; supported_schemes[i].leading_string; i++)
427     if (0 == strncasecmp (url, supported_schemes[i].leading_string,
428                           strlen (supported_schemes[i].leading_string)))
429       {
430         if (supported_schemes[i].enabled)
431           return (enum url_scheme) i;
432         else
433           return SCHEME_INVALID;
434       }
435
436   return SCHEME_INVALID;
437 }
438
439 #define SCHEME_CHAR(ch) (ISALNUM (ch) || (ch) == '-' || (ch) == '+')
440
441 /* Return 1 if the URL begins with any "scheme", 0 otherwise.  As
442    currently implemented, it returns true if URL begins with
443    [-+a-zA-Z0-9]+: .  */
444
445 int
446 url_has_scheme (const char *url)
447 {
448   const char *p = url;
449
450   /* The first char must be a scheme char. */
451   if (!*p || !SCHEME_CHAR (*p))
452     return 0;
453   ++p;
454   /* Followed by 0 or more scheme chars. */
455   while (*p && SCHEME_CHAR (*p))
456     ++p;
457   /* Terminated by ':'. */
458   return *p == ':';
459 }
460
461 int
462 scheme_default_port (enum url_scheme scheme)
463 {
464   return supported_schemes[scheme].default_port;
465 }
466
467 void
468 scheme_disable (enum url_scheme scheme)
469 {
470   supported_schemes[scheme].enabled = 0;
471 }
472
473 /* Skip the username and password, if present in the URL.  The
474    function should *not* be called with the complete URL, but with the
475    portion after the scheme.
476
477    If no username and password are found, return URL.  */
478
479 static const char *
480 url_skip_credentials (const char *url)
481 {
482   /* Look for '@' that comes before terminators, such as '/', '?',
483      '#', or ';'.  */
484   const char *p = (const char *)strpbrk (url, "@/?#;");
485   if (!p || *p != '@')
486     return url;
487   return p + 1;
488 }
489
490 /* Parse credentials contained in [BEG, END).  The region is expected
491    to have come from a URL and is unescaped.  */
492
493 static int
494 parse_credentials (const char *beg, const char *end, char **user, char **passwd)
495 {
496   char *colon;
497   const char *userend;
498
499   if (beg == end)
500     return 0;                   /* empty user name */
501
502   colon = memchr (beg, ':', end - beg);
503   if (colon == beg)
504     return 0;                   /* again empty user name */
505
506   if (colon)
507     {
508       *passwd = strdupdelim (colon + 1, end);
509       userend = colon;
510       url_unescape (*passwd);
511     }
512   else
513     {
514       *passwd = NULL;
515       userend = end;
516     }
517   *user = strdupdelim (beg, userend);
518   url_unescape (*user);
519   return 1;
520 }
521
522 /* Used by main.c: detect URLs written using the "shorthand" URL forms
523    popularized by Netscape and NcFTP.  HTTP shorthands look like this:
524
525    www.foo.com[:port]/dir/file   -> http://www.foo.com[:port]/dir/file
526    www.foo.com[:port]            -> http://www.foo.com[:port]
527
528    FTP shorthands look like this:
529
530    foo.bar.com:dir/file          -> ftp://foo.bar.com/dir/file
531    foo.bar.com:/absdir/file      -> ftp://foo.bar.com//absdir/file
532
533    If the URL needs not or cannot be rewritten, return NULL.  */
534
535 char *
536 rewrite_shorthand_url (const char *url)
537 {
538   const char *p;
539
540   if (url_scheme (url) != SCHEME_INVALID)
541     return NULL;
542
543   /* Look for a ':' or '/'.  The former signifies NcFTP syntax, the
544      latter Netscape.  */
545   for (p = url; *p && *p != ':' && *p != '/'; p++)
546     ;
547
548   if (p == url)
549     return NULL;
550
551   if (*p == ':')
552     {
553       const char *pp;
554       char *res;
555       /* If the characters after the colon and before the next slash
556          or end of string are all digits, it's HTTP.  */
557       int digits = 0;
558       for (pp = p + 1; ISDIGIT (*pp); pp++)
559         ++digits;
560       if (digits > 0 && (*pp == '/' || *pp == '\0'))
561         goto http;
562
563       /* Prepend "ftp://" to the entire URL... */
564       res = xmalloc (6 + strlen (url) + 1);
565       sprintf (res, "ftp://%s", url);
566       /* ...and replace ':' with '/'. */
567       res[6 + (p - url)] = '/';
568       return res;
569     }
570   else
571     {
572       char *res;
573     http:
574       /* Just prepend "http://" to what we have. */
575       res = xmalloc (7 + strlen (url) + 1);
576       sprintf (res, "http://%s", url);
577       return res;
578     }
579 }
580 \f
581 static void split_path PARAMS ((const char *, char **, char **));
582
583 /* Like strpbrk, with the exception that it returns the pointer to the
584    terminating zero (end-of-string aka "eos") if no matching character
585    is found.
586
587    Although I normally balk at Gcc-specific optimizations, it probably
588    makes sense here: glibc has optimizations that detect strpbrk being
589    called with literal string as ACCEPT and inline the search.  That
590    optimization is defeated if strpbrk is hidden within the call to
591    another function.  (And no, making strpbrk_or_eos inline doesn't
592    help because the check for literal accept is in the
593    preprocessor.)  */
594
595 #ifdef __GNUC__
596
597 #define strpbrk_or_eos(s, accept) ({            \
598   char *SOE_p = strpbrk (s, accept);            \
599   if (!SOE_p)                                   \
600     SOE_p = (char *)s + strlen (s);             \
601   SOE_p;                                        \
602 })
603
604 #else  /* not __GNUC__ */
605
606 static char *
607 strpbrk_or_eos (const char *s, const char *accept)
608 {
609   char *p = strpbrk (s, accept);
610   if (!p)
611     p = (char *)s + strlen (s);
612   return p;
613 }
614 #endif
615
616 /* Turn STR into lowercase; return non-zero if a character was
617    actually changed. */
618
619 static int
620 lowercase_str (char *str)
621 {
622   int change = 0;
623   for (; *str; str++)
624     if (ISUPPER (*str))
625       {
626         change = 1;
627         *str = TOLOWER (*str);
628       }
629   return change;
630 }
631
632 static const char *parse_errors[] = {
633 #define PE_NO_ERROR                     0
634   N_("No error"),
635 #define PE_UNSUPPORTED_SCHEME           1
636   N_("Unsupported scheme"),
637 #define PE_EMPTY_HOST                   2
638   N_("Empty host"),
639 #define PE_BAD_PORT_NUMBER              3
640   N_("Bad port number"),
641 #define PE_INVALID_USER_NAME            4
642   N_("Invalid user name"),
643 #define PE_UNTERMINATED_IPV6_ADDRESS    5
644   N_("Unterminated IPv6 numeric address"),
645 #define PE_IPV6_NOT_SUPPORTED           6
646   N_("IPv6 addresses not supported"),
647 #define PE_INVALID_IPV6_ADDRESS         7
648   N_("Invalid IPv6 numeric address")
649 };
650
651 /* Parse a URL.
652
653    Return a new struct url if successful, NULL on error.  In case of
654    error, and if ERROR is not NULL, also set *ERROR to the appropriate
655    error code. */
656 struct url *
657 url_parse (const char *url, int *error)
658 {
659   struct url *u;
660   const char *p;
661   int path_modified, host_modified;
662
663   enum url_scheme scheme;
664
665   const char *uname_b,     *uname_e;
666   const char *host_b,      *host_e;
667   const char *path_b,      *path_e;
668   const char *params_b,    *params_e;
669   const char *query_b,     *query_e;
670   const char *fragment_b,  *fragment_e;
671
672   int port;
673   char *user = NULL, *passwd = NULL;
674
675   char *url_encoded = NULL;
676
677   int error_code;
678
679   scheme = url_scheme (url);
680   if (scheme == SCHEME_INVALID)
681     {
682       error_code = PE_UNSUPPORTED_SCHEME;
683       goto err;
684     }
685
686   url_encoded = reencode_escapes (url);
687   p = url_encoded;
688
689   p += strlen (supported_schemes[scheme].leading_string);
690   uname_b = p;
691   p = url_skip_credentials (p);
692   uname_e = p;
693
694   /* scheme://user:pass@host[:port]... */
695   /*                    ^              */
696
697   /* We attempt to break down the URL into the components path,
698      params, query, and fragment.  They are ordered like this:
699
700        scheme://host[:port][/path][;params][?query][#fragment]  */
701
702   params_b   = params_e   = NULL;
703   query_b    = query_e    = NULL;
704   fragment_b = fragment_e = NULL;
705
706   host_b = p;
707
708   if (*p == '[')
709     {
710       /* Handle IPv6 address inside square brackets.  Ideally we'd
711          just look for the terminating ']', but rfc2732 mandates
712          rejecting invalid IPv6 addresses.  */
713
714       /* The address begins after '['. */
715       host_b = p + 1;
716       host_e = strchr (host_b, ']');
717
718       if (!host_e)
719         {
720           error_code = PE_UNTERMINATED_IPV6_ADDRESS;
721           goto err;
722         }
723
724 #ifdef ENABLE_IPV6
725       /* Check if the IPv6 address is valid. */
726       if (!is_valid_ipv6_address(host_b, host_e))
727         {
728           error_code = PE_INVALID_IPV6_ADDRESS;
729           goto err;
730         }
731
732       /* Continue parsing after the closing ']'. */
733       p = host_e + 1;
734 #else
735       error_code = PE_IPV6_NOT_SUPPORTED;
736       goto err;
737 #endif
738     }
739   else
740     {
741       p = strpbrk_or_eos (p, ":/;?#");
742       host_e = p;
743     }
744
745   if (host_b == host_e)
746     {
747       error_code = PE_EMPTY_HOST;
748       goto err;
749     }
750
751   port = scheme_default_port (scheme);
752   if (*p == ':')
753     {
754       const char *port_b, *port_e, *pp;
755
756       /* scheme://host:port/tralala */
757       /*              ^             */
758       ++p;
759       port_b = p;
760       p = strpbrk_or_eos (p, "/;?#");
761       port_e = p;
762
763       /* Allow empty port, as per rfc2396. */
764       if (port_b != port_e)
765         {
766           for (port = 0, pp = port_b; pp < port_e; pp++)
767             {
768               if (!ISDIGIT (*pp))
769                 {
770                   /* http://host:12randomgarbage/blah */
771                   /*               ^                  */
772                   error_code = PE_BAD_PORT_NUMBER;
773                   goto err;
774                 }
775               port = 10 * port + (*pp - '0');
776               /* Check for too large port numbers here, before we have
777                  a chance to overflow on bogus port values.  */
778               if (port > 65535)
779                 {
780                   error_code = PE_BAD_PORT_NUMBER;
781                   goto err;
782                 }
783             }
784         }
785     }
786
787   if (*p == '/')
788     {
789       ++p;
790       path_b = p;
791       p = strpbrk_or_eos (p, ";?#");
792       path_e = p;
793     }
794   else
795     {
796       /* Path is not allowed not to exist. */
797       path_b = path_e = p;
798     }
799
800   if (*p == ';')
801     {
802       ++p;
803       params_b = p;
804       p = strpbrk_or_eos (p, "?#");
805       params_e = p;
806     }
807   if (*p == '?')
808     {
809       ++p;
810       query_b = p;
811       p = strpbrk_or_eos (p, "#");
812       query_e = p;
813
814       /* Hack that allows users to use '?' (a wildcard character) in
815          FTP URLs without it being interpreted as a query string
816          delimiter.  */
817       if (scheme == SCHEME_FTP)
818         {
819           query_b = query_e = NULL;
820           path_e = p;
821         }
822     }
823   if (*p == '#')
824     {
825       ++p;
826       fragment_b = p;
827       p += strlen (p);
828       fragment_e = p;
829     }
830   assert (*p == 0);
831
832   if (uname_b != uname_e)
833     {
834       /* http://user:pass@host */
835       /*        ^         ^    */
836       /*     uname_b   uname_e */
837       if (!parse_credentials (uname_b, uname_e - 1, &user, &passwd))
838         {
839           error_code = PE_INVALID_USER_NAME;
840           goto err;
841         }
842     }
843
844   u = xnew0 (struct url);
845   u->scheme = scheme;
846   u->host   = strdupdelim (host_b, host_e);
847   u->port   = port;
848   u->user   = user;
849   u->passwd = passwd;
850
851   u->path = strdupdelim (path_b, path_e);
852   path_modified = path_simplify (u->path);
853   split_path (u->path, &u->dir, &u->file);
854
855   host_modified = lowercase_str (u->host);
856
857   /* Decode %HH sequences in host name.  This is important not so much
858      to support %HH sequences in host names (which other browser
859      don't), but to support binary characters (which will have been
860      converted to %HH by reencode_escapes).  */
861   if (strchr (u->host, '%'))
862     {
863       url_unescape (u->host);
864       host_modified = 1;
865     }
866
867   if (params_b)
868     u->params = strdupdelim (params_b, params_e);
869   if (query_b)
870     u->query = strdupdelim (query_b, query_e);
871   if (fragment_b)
872     u->fragment = strdupdelim (fragment_b, fragment_e);
873
874   if (path_modified || u->fragment || host_modified || path_b == path_e)
875     {
876       /* If we suspect that a transformation has rendered what
877          url_string might return different from URL_ENCODED, rebuild
878          u->url using url_string.  */
879       u->url = url_string (u, 0);
880
881       if (url_encoded != url)
882         xfree ((char *) url_encoded);
883     }
884   else
885     {
886       if (url_encoded == url)
887         u->url = xstrdup (url);
888       else
889         u->url = url_encoded;
890     }
891   url_encoded = NULL;
892
893   return u;
894
895  err:
896   /* Cleanup in case of error: */
897   if (url_encoded && url_encoded != url)
898     xfree (url_encoded);
899
900   /* Transmit the error code to the caller, if the caller wants to
901      know.  */
902   if (error)
903     *error = error_code;
904   return NULL;
905 }
906
907 /* Return the error message string from ERROR_CODE, which should have
908    been retrieved from url_parse.  The error message is translated.  */
909
910 const char *
911 url_error (int error_code)
912 {
913   assert (error_code >= 0 && error_code < countof (parse_errors));
914   return _(parse_errors[error_code]);
915 }
916
917 /* Split PATH into DIR and FILE.  PATH comes from the URL and is
918    expected to be URL-escaped.
919
920    The path is split into directory (the part up to the last slash)
921    and file (the part after the last slash), which are subsequently
922    unescaped.  Examples:
923
924    PATH                 DIR           FILE
925    "foo/bar/baz"        "foo/bar"     "baz"
926    "foo/bar/"           "foo/bar"     ""
927    "foo"                ""            "foo"
928    "foo/bar/baz%2fqux"  "foo/bar"     "baz/qux" (!)
929
930    DIR and FILE are freshly allocated.  */
931
932 static void
933 split_path (const char *path, char **dir, char **file)
934 {
935   char *last_slash = strrchr (path, '/');
936   if (!last_slash)
937     {
938       *dir = xstrdup ("");
939       *file = xstrdup (path);
940     }
941   else
942     {
943       *dir = strdupdelim (path, last_slash);
944       *file = xstrdup (last_slash + 1);
945     }
946   url_unescape (*dir);
947   url_unescape (*file);
948 }
949
950 /* Note: URL's "full path" is the path with the query string and
951    params appended.  The "fragment" (#foo) is intentionally ignored,
952    but that might be changed.  For example, if the original URL was
953    "http://host:port/foo/bar/baz;bullshit?querystring#uselessfragment",
954    the full path will be "/foo/bar/baz;bullshit?querystring".  */
955
956 /* Return the length of the full path, without the terminating
957    zero.  */
958
959 static int
960 full_path_length (const struct url *url)
961 {
962   int len = 0;
963
964 #define FROB(el) if (url->el) len += 1 + strlen (url->el)
965
966   FROB (path);
967   FROB (params);
968   FROB (query);
969
970 #undef FROB
971
972   return len;
973 }
974
975 /* Write out the full path. */
976
977 static void
978 full_path_write (const struct url *url, char *where)
979 {
980 #define FROB(el, chr) do {                      \
981   char *f_el = url->el;                         \
982   if (f_el) {                                   \
983     int l = strlen (f_el);                      \
984     *where++ = chr;                             \
985     memcpy (where, f_el, l);                    \
986     where += l;                                 \
987   }                                             \
988 } while (0)
989
990   FROB (path, '/');
991   FROB (params, ';');
992   FROB (query, '?');
993
994 #undef FROB
995 }
996
997 /* Public function for getting the "full path".  E.g. if u->path is
998    "foo/bar" and u->query is "param=value", full_path will be
999    "/foo/bar?param=value". */
1000
1001 char *
1002 url_full_path (const struct url *url)
1003 {
1004   int length = full_path_length (url);
1005   char *full_path = (char *) xmalloc (length + 1);
1006
1007   full_path_write (url, full_path);
1008   full_path[length] = '\0';
1009
1010   return full_path;
1011 }
1012
1013 /* Unescape CHR in an otherwise escaped STR.  Used to selectively
1014    escaping of certain characters, such as "/" and ":".  Returns a
1015    count of unescaped chars.  */
1016
1017 static void
1018 unescape_single_char (char *str, char chr)
1019 {
1020   const char c1 = XNUM_TO_DIGIT (chr >> 4);
1021   const char c2 = XNUM_TO_DIGIT (chr & 0xf);
1022   char *h = str;                /* hare */
1023   char *t = str;                /* tortoise */
1024   for (; *h; h++, t++)
1025     {
1026       if (h[0] == '%' && h[1] == c1 && h[2] == c2)
1027         {
1028           *t = chr;
1029           h += 2;
1030         }
1031       else
1032         *t = *h;
1033     }
1034   *t = '\0';
1035 }
1036
1037 /* Escape unsafe and reserved characters, except for the slash
1038    characters.  */
1039
1040 static char *
1041 url_escape_dir (const char *dir)
1042 {
1043   char *newdir = url_escape_1 (dir, urlchr_unsafe | urlchr_reserved, 1);
1044   if (newdir == dir)
1045     return (char *)dir;
1046
1047   unescape_single_char (newdir, '/');
1048   return newdir;
1049 }
1050
1051 /* Sync u->path and u->url with u->dir and u->file.  Called after
1052    u->file or u->dir have been changed, typically by the FTP code.  */
1053
1054 static void
1055 sync_path (struct url *u)
1056 {
1057   char *newpath, *efile, *edir;
1058
1059   xfree (u->path);
1060
1061   /* u->dir and u->file are not escaped.  URL-escape them before
1062      reassembling them into u->path.  That way, if they contain
1063      separators like '?' or even if u->file contains slashes, the
1064      path will be correctly assembled.  (u->file can contain slashes
1065      if the URL specifies it with %2f, or if an FTP server returns
1066      it.)  */
1067   edir = url_escape_dir (u->dir);
1068   efile = url_escape_1 (u->file, urlchr_unsafe | urlchr_reserved, 1);
1069
1070   if (!*edir)
1071     newpath = xstrdup (efile);
1072   else
1073     {
1074       int dirlen = strlen (edir);
1075       int filelen = strlen (efile);
1076
1077       /* Copy "DIR/FILE" to newpath. */
1078       char *p = newpath = xmalloc (dirlen + 1 + filelen + 1);
1079       memcpy (p, edir, dirlen);
1080       p += dirlen;
1081       *p++ = '/';
1082       memcpy (p, efile, filelen);
1083       p += filelen;
1084       *p++ = '\0';
1085     }
1086
1087   u->path = newpath;
1088
1089   if (edir != u->dir)
1090     xfree (edir);
1091   if (efile != u->file)
1092     xfree (efile);
1093
1094   /* Regenerate u->url as well.  */
1095   xfree (u->url);
1096   u->url = url_string (u, 0);
1097 }
1098
1099 /* Mutators.  Code in ftp.c insists on changing u->dir and u->file.
1100    This way we can sync u->path and u->url when they get changed.  */
1101
1102 void
1103 url_set_dir (struct url *url, const char *newdir)
1104 {
1105   xfree (url->dir);
1106   url->dir = xstrdup (newdir);
1107   sync_path (url);
1108 }
1109
1110 void
1111 url_set_file (struct url *url, const char *newfile)
1112 {
1113   xfree (url->file);
1114   url->file = xstrdup (newfile);
1115   sync_path (url);
1116 }
1117
1118 void
1119 url_free (struct url *url)
1120 {
1121   xfree (url->host);
1122   xfree (url->path);
1123   xfree (url->url);
1124
1125   xfree_null (url->params);
1126   xfree_null (url->query);
1127   xfree_null (url->fragment);
1128   xfree_null (url->user);
1129   xfree_null (url->passwd);
1130
1131   xfree (url->dir);
1132   xfree (url->file);
1133
1134   xfree (url);
1135 }
1136 \f
1137 /* Create all the necessary directories for PATH (a file).  Calls
1138    mkdirhier() internally.  */
1139 int
1140 mkalldirs (const char *path)
1141 {
1142   const char *p;
1143   char *t;
1144   struct_stat st;
1145   int res;
1146
1147   p = path + strlen (path);
1148   for (; *p != '/' && p != path; p--)
1149     ;
1150
1151   /* Don't create if it's just a file.  */
1152   if ((p == path) && (*p != '/'))
1153     return 0;
1154   t = strdupdelim (path, p);
1155
1156   /* Check whether the directory exists.  */
1157   if ((stat (t, &st) == 0))
1158     {
1159       if (S_ISDIR (st.st_mode))
1160         {
1161           xfree (t);
1162           return 0;
1163         }
1164       else
1165         {
1166           /* If the dir exists as a file name, remove it first.  This
1167              is *only* for Wget to work with buggy old CERN http
1168              servers.  Here is the scenario: When Wget tries to
1169              retrieve a directory without a slash, e.g.
1170              http://foo/bar (bar being a directory), CERN server will
1171              not redirect it too http://foo/bar/ -- it will generate a
1172              directory listing containing links to bar/file1,
1173              bar/file2, etc.  Wget will lose because it saves this
1174              HTML listing to a file `bar', so it cannot create the
1175              directory.  To work around this, if the file of the same
1176              name exists, we just remove it and create the directory
1177              anyway.  */
1178           DEBUGP (("Removing %s because of directory danger!\n", t));
1179           unlink (t);
1180         }
1181     }
1182   res = make_directory (t);
1183   if (res != 0)
1184     logprintf (LOG_NOTQUIET, "%s: %s", t, strerror (errno));
1185   xfree (t);
1186   return res;
1187 }
1188 \f
1189 /* Functions for constructing the file name out of URL components.  */
1190
1191 /* A growable string structure, used by url_file_name and friends.
1192    This should perhaps be moved to utils.c.
1193
1194    The idea is to have a convenient and efficient way to construct a
1195    string by having various functions append data to it.  Instead of
1196    passing the obligatory BASEVAR, SIZEVAR and TAILPOS to all the
1197    functions in questions, we pass the pointer to this struct.  */
1198
1199 struct growable {
1200   char *base;
1201   int size;
1202   int tail;
1203 };
1204
1205 /* Ensure that the string can accept APPEND_COUNT more characters past
1206    the current TAIL position.  If necessary, this will grow the string
1207    and update its allocated size.  If the string is already large
1208    enough to take TAIL+APPEND_COUNT characters, this does nothing.  */
1209 #define GROW(g, append_size) do {                                       \
1210   struct growable *G_ = g;                                              \
1211   DO_REALLOC (G_->base, G_->size, G_->tail + append_size, char);        \
1212 } while (0)
1213
1214 /* Return the tail position of the string. */
1215 #define TAIL(r) ((r)->base + (r)->tail)
1216
1217 /* Move the tail position by APPEND_COUNT characters. */
1218 #define TAIL_INCR(r, append_count) ((r)->tail += append_count)
1219
1220 /* Append the string STR to DEST.  NOTICE: the string in DEST is not
1221    terminated.  */
1222
1223 static void
1224 append_string (const char *str, struct growable *dest)
1225 {
1226   int l = strlen (str);
1227   GROW (dest, l);
1228   memcpy (TAIL (dest), str, l);
1229   TAIL_INCR (dest, l);
1230 }
1231
1232 /* Append CH to DEST.  For example, append_char (0, DEST)
1233    zero-terminates DEST.  */
1234
1235 static void
1236 append_char (char ch, struct growable *dest)
1237 {
1238   GROW (dest, 1);
1239   *TAIL (dest) = ch;
1240   TAIL_INCR (dest, 1);
1241 }
1242
1243 enum {
1244   filechr_not_unix    = 1,      /* unusable on Unix, / and \0 */
1245   filechr_not_windows = 2,      /* unusable on Windows, one of \|/<>?:*" */
1246   filechr_control     = 4       /* a control character, e.g. 0-31 */
1247 };
1248
1249 #define FILE_CHAR_TEST(c, mask) (filechr_table[(unsigned char)(c)] & (mask))
1250
1251 /* Shorthands for the table: */
1252 #define U filechr_not_unix
1253 #define W filechr_not_windows
1254 #define C filechr_control
1255
1256 #define UW U|W
1257 #define UWC U|W|C
1258
1259 /* Table of characters unsafe under various conditions (see above).
1260
1261    Arguably we could also claim `%' to be unsafe, since we use it as
1262    the escape character.  If we ever want to be able to reliably
1263    translate file name back to URL, this would become important
1264    crucial.  Right now, it's better to be minimal in escaping.  */
1265
1266 static const unsigned char filechr_table[256] =
1267 {
1268 UWC,  C,  C,  C,   C,  C,  C,  C,   /* NUL SOH STX ETX  EOT ENQ ACK BEL */
1269   C,  C,  C,  C,   C,  C,  C,  C,   /* BS  HT  LF  VT   FF  CR  SO  SI  */
1270   C,  C,  C,  C,   C,  C,  C,  C,   /* DLE DC1 DC2 DC3  DC4 NAK SYN ETB */
1271   C,  C,  C,  C,   C,  C,  C,  C,   /* CAN EM  SUB ESC  FS  GS  RS  US  */
1272   0,  0,  W,  0,   0,  0,  0,  0,   /* SP  !   "   #    $   %   &   '   */
1273   0,  0,  W,  0,   0,  0,  0, UW,   /* (   )   *   +    ,   -   .   /   */
1274   0,  0,  0,  0,   0,  0,  0,  0,   /* 0   1   2   3    4   5   6   7   */
1275   0,  0,  W,  0,   W,  0,  W,  W,   /* 8   9   :   ;    <   =   >   ?   */
1276   0,  0,  0,  0,   0,  0,  0,  0,   /* @   A   B   C    D   E   F   G   */
1277   0,  0,  0,  0,   0,  0,  0,  0,   /* H   I   J   K    L   M   N   O   */
1278   0,  0,  0,  0,   0,  0,  0,  0,   /* P   Q   R   S    T   U   V   W   */
1279   0,  0,  0,  0,   W,  0,  0,  0,   /* X   Y   Z   [    \   ]   ^   _   */
1280   0,  0,  0,  0,   0,  0,  0,  0,   /* `   a   b   c    d   e   f   g   */
1281   0,  0,  0,  0,   0,  0,  0,  0,   /* h   i   j   k    l   m   n   o   */
1282   0,  0,  0,  0,   0,  0,  0,  0,   /* p   q   r   s    t   u   v   w   */
1283   0,  0,  0,  0,   0,  0,  0,  0,   /* x   y   z   {    |   }   ~   DEL */
1284
1285   C, C, C, C,  C, C, C, C,  C, C, C, C,  C, C, C, C, /* 128-143 */
1286   C, C, C, C,  C, C, C, C,  C, C, C, C,  C, C, C, C, /* 144-159 */
1287   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1288   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1289
1290   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1291   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1292   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1293   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1294 };
1295 #undef U
1296 #undef W
1297 #undef C
1298 #undef UW
1299 #undef UWC
1300
1301 /* FN_PORT_SEP is the separator between host and port in file names
1302    for non-standard port numbers.  On Unix this is normally ':', as in
1303    "www.xemacs.org:4001/index.html".  Under Windows, we set it to +
1304    because Windows can't handle ':' in file names.  */
1305 #define FN_PORT_SEP  (opt.restrict_files_os != restrict_windows ? ':' : '+')
1306
1307 /* FN_QUERY_SEP is the separator between the file name and the URL
1308    query, normally '?'.  Since Windows cannot handle '?' as part of
1309    file name, we use '@' instead there.  */
1310 #define FN_QUERY_SEP (opt.restrict_files_os != restrict_windows ? '?' : '@')
1311
1312 /* Quote path element, characters in [b, e), as file name, and append
1313    the quoted string to DEST.  Each character is quoted as per
1314    file_unsafe_char and the corresponding table.
1315
1316    If ESCAPED_P is non-zero, the path element is considered to be
1317    URL-escaped and will be unescaped prior to inspection.  */
1318
1319 static void
1320 append_uri_pathel (const char *b, const char *e, int escaped_p,
1321                    struct growable *dest)
1322 {
1323   const char *p;
1324   int quoted, outlen;
1325
1326   int mask;
1327   if (opt.restrict_files_os == restrict_unix)
1328     mask = filechr_not_unix;
1329   else
1330     mask = filechr_not_windows;
1331   if (opt.restrict_files_ctrl)
1332     mask |= filechr_control;
1333
1334   /* Copy [b, e) to PATHEL and URL-unescape it. */
1335   if (escaped_p)
1336     {
1337       char *unescaped;
1338       BOUNDED_TO_ALLOCA (b, e, unescaped);
1339       url_unescape (unescaped);
1340       b = unescaped;
1341       e = unescaped + strlen (unescaped);
1342     }
1343
1344   /* Defang ".." when found as component of path.  Remember that path
1345      comes from the URL and might contain malicious input.  */
1346   if (e - b == 2 && b[0] == '.' && b[1] == '.')
1347     {
1348       b = "%2E%2E";
1349       e = b + 6;
1350     }
1351
1352   /* Walk the PATHEL string and check how many characters we'll need
1353      to quote.  */
1354   quoted = 0;
1355   for (p = b; p < e; p++)
1356     if (FILE_CHAR_TEST (*p, mask))
1357       ++quoted;
1358
1359   /* Calculate the length of the output string.  e-b is the input
1360      string length.  Each quoted char introduces two additional
1361      characters in the string, hence 2*quoted.  */
1362   outlen = (e - b) + (2 * quoted);
1363   GROW (dest, outlen);
1364
1365   if (!quoted)
1366     {
1367       /* If there's nothing to quote, we can simply append the string
1368          without processing it again.  */
1369       memcpy (TAIL (dest), b, outlen);
1370     }
1371   else
1372     {
1373       char *q = TAIL (dest);
1374       for (p = b; p < e; p++)
1375         {
1376           if (!FILE_CHAR_TEST (*p, mask))
1377             *q++ = *p;
1378           else
1379             {
1380               unsigned char ch = *p;
1381               *q++ = '%';
1382               *q++ = XNUM_TO_DIGIT (ch >> 4);
1383               *q++ = XNUM_TO_DIGIT (ch & 0xf);
1384             }
1385         }
1386       assert (q - TAIL (dest) == outlen);
1387     }
1388   TAIL_INCR (dest, outlen);
1389 }
1390
1391 /* Append to DEST the directory structure that corresponds the
1392    directory part of URL's path.  For example, if the URL is
1393    http://server/dir1/dir2/file, this appends "/dir1/dir2".
1394
1395    Each path element ("dir1" and "dir2" in the above example) is
1396    examined, url-unescaped, and re-escaped as file name element.
1397
1398    Additionally, it cuts as many directories from the path as
1399    specified by opt.cut_dirs.  For example, if opt.cut_dirs is 1, it
1400    will produce "bar" for the above example.  For 2 or more, it will
1401    produce "".
1402
1403    Each component of the path is quoted for use as file name.  */
1404
1405 static void
1406 append_dir_structure (const struct url *u, struct growable *dest)
1407 {
1408   char *pathel, *next;
1409   int cut = opt.cut_dirs;
1410
1411   /* Go through the path components, de-URL-quote them, and quote them
1412      (if necessary) as file names.  */
1413
1414   pathel = u->path;
1415   for (; (next = strchr (pathel, '/')) != NULL; pathel = next + 1)
1416     {
1417       if (cut-- > 0)
1418         continue;
1419       if (pathel == next)
1420         /* Ignore empty pathels.  */
1421         continue;
1422
1423       if (dest->tail)
1424         append_char ('/', dest);
1425       append_uri_pathel (pathel, next, 1, dest);
1426     }
1427 }
1428
1429 /* Return a unique file name that matches the given URL as good as
1430    possible.  Does not create directories on the file system.  */
1431
1432 char *
1433 url_file_name (const struct url *u)
1434 {
1435   struct growable fnres;        /* stands for "file name result" */
1436
1437   const char *u_file, *u_query;
1438   char *fname, *unique;
1439
1440   fnres.base = NULL;
1441   fnres.size = 0;
1442   fnres.tail = 0;
1443
1444   /* Start with the directory prefix, if specified. */
1445   if (opt.dir_prefix)
1446     append_string (opt.dir_prefix, &fnres);
1447
1448   /* If "dirstruct" is turned on (typically the case with -r), add
1449      the host and port (unless those have been turned off) and
1450      directory structure.  */
1451   if (opt.dirstruct)
1452     {
1453       if (opt.protocol_directories)
1454         {
1455           if (fnres.tail)
1456             append_char ('/', &fnres);
1457           append_string (supported_schemes[u->scheme].name, &fnres);
1458         }
1459       if (opt.add_hostdir)
1460         {
1461           if (fnres.tail)
1462             append_char ('/', &fnres);
1463           if (0 != strcmp (u->host, ".."))
1464             append_string (u->host, &fnres);
1465           else
1466             /* Host name can come from the network; malicious DNS may
1467                allow ".." to be resolved, causing us to write to
1468                "../<file>".  Defang such host names.  */
1469             append_string ("%2E%2E", &fnres);
1470           if (u->port != scheme_default_port (u->scheme))
1471             {
1472               char portstr[24];
1473               number_to_string (portstr, u->port);
1474               append_char (FN_PORT_SEP, &fnres);
1475               append_string (portstr, &fnres);
1476             }
1477         }
1478
1479       append_dir_structure (u, &fnres);
1480     }
1481
1482   /* Add the file name. */
1483   if (fnres.tail)
1484     append_char ('/', &fnres);
1485   u_file = *u->file ? u->file : "index.html";
1486   append_uri_pathel (u_file, u_file + strlen (u_file), 0, &fnres);
1487
1488   /* Append "?query" to the file name. */
1489   u_query = u->query && *u->query ? u->query : NULL;
1490   if (u_query)
1491     {
1492       append_char (FN_QUERY_SEP, &fnres);
1493       append_uri_pathel (u_query, u_query + strlen (u_query), 1, &fnres);
1494     }
1495
1496   /* Zero-terminate the file name. */
1497   append_char ('\0', &fnres);
1498
1499   fname = fnres.base;
1500
1501   /* Check the cases in which the unique extensions are not used:
1502      1) Clobbering is turned off (-nc).
1503      2) Retrieval with regetting.
1504      3) Timestamping is used.
1505      4) Hierarchy is built.
1506
1507      The exception is the case when file does exist and is a
1508      directory (see `mkalldirs' for explanation).  */
1509
1510   if ((opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct)
1511       && !(file_exists_p (fname) && !file_non_directory_p (fname)))
1512     return fname;
1513
1514   unique = unique_name (fname, 1);
1515   if (unique != fname)
1516     xfree (fname);
1517   return unique;
1518 }
1519 \f
1520 /* Resolve "." and ".." elements of PATH by destructively modifying
1521    PATH and return non-zero if PATH has been modified, zero otherwise.
1522
1523    The algorithm is in spirit similar to the one described in rfc1808,
1524    although implemented differently, in one pass.  To recap, path
1525    elements containing only "." are removed, and ".." is taken to mean
1526    "back up one element".  Single leading and trailing slashes are
1527    preserved.
1528
1529    For example, "a/b/c/./../d/.." will yield "a/b/".  More exhaustive
1530    test examples are provided below.  If you change anything in this
1531    function, run test_path_simplify to make sure you haven't broken a
1532    test case.  */
1533
1534 static int
1535 path_simplify (char *path)
1536 {
1537   char *h = path;               /* hare */
1538   char *t = path;               /* tortoise */
1539   char *beg = path;             /* boundary for backing the tortoise */
1540   char *end = path + strlen (path);
1541
1542   while (h < end)
1543     {
1544       /* Hare should be at the beginning of a path element. */
1545
1546       if (h[0] == '.' && (h[1] == '/' || h[1] == '\0'))
1547         {
1548           /* Ignore "./". */
1549           h += 2;
1550         }
1551       else if (h[0] == '.' && h[1] == '.' && (h[2] == '/' || h[2] == '\0'))
1552         {
1553           /* Handle "../" by retreating the tortoise by one path
1554              element -- but not past beggining.  */
1555           if (t > beg)
1556             {
1557               /* Move backwards until T hits the beginning of the
1558                  previous path element or the beginning of path. */
1559               for (--t; t > beg && t[-1] != '/'; t--)
1560                 ;
1561             }
1562           else
1563             {
1564               /* If we're at the beginning, copy the "../" literally
1565                  move the beginning so a later ".." doesn't remove
1566                  it.  */
1567               beg = t + 3;
1568               goto regular;
1569             }
1570           h += 3;
1571         }
1572       else
1573         {
1574         regular:
1575           /* A regular path element.  If H hasn't advanced past T,
1576              simply skip to the next path element.  Otherwise, copy
1577              the path element until the next slash.  */
1578           if (t == h)
1579             {
1580               /* Skip the path element, including the slash.  */
1581               while (h < end && *h != '/')
1582                 t++, h++;
1583               if (h < end)
1584                 t++, h++;
1585             }
1586           else
1587             {
1588               /* Copy the path element, including the final slash.  */
1589               while (h < end && *h != '/')
1590                 *t++ = *h++;
1591               if (h < end)
1592                 *t++ = *h++;
1593             }
1594         }
1595     }
1596
1597   if (t != h)
1598     *t = '\0';
1599
1600   return t != h;
1601 }
1602 \f
1603 /* Return the length of URL's path.  Path is considered to be
1604    terminated by one of '?', ';', '#', or by the end of the
1605    string.  */
1606
1607 static int
1608 path_length (const char *url)
1609 {
1610   const char *q = strpbrk_or_eos (url, "?;#");
1611   return q - url;
1612 }
1613
1614 /* Find the last occurrence of character C in the range [b, e), or
1615    NULL, if none are present.  We might want to use memrchr (a GNU
1616    extension) under GNU libc.  */
1617
1618 static const char *
1619 find_last_char (const char *b, const char *e, char c)
1620 {
1621   for (; e > b; e--)
1622     if (*e == c)
1623       return e;
1624   return NULL;
1625 }
1626
1627 /* Merge BASE with LINK and return the resulting URI.
1628
1629    Either of the URIs may be absolute or relative, complete with the
1630    host name, or path only.  This tries to reasonably handle all
1631    foreseeable cases.  It only employs minimal URL parsing, without
1632    knowledge of the specifics of schemes.
1633
1634    I briefly considered making this function call path_simplify after
1635    the merging process, as rfc1738 seems to suggest.  This is a bad
1636    idea for several reasons: 1) it complexifies the code, and 2)
1637    url_parse has to simplify path anyway, so it's wasteful to boot.  */
1638
1639 char *
1640 uri_merge (const char *base, const char *link)
1641 {
1642   int linklength;
1643   const char *end;
1644   char *merge;
1645
1646   if (url_has_scheme (link))
1647     return xstrdup (link);
1648
1649   /* We may not examine BASE past END. */
1650   end = base + path_length (base);
1651   linklength = strlen (link);
1652
1653   if (!*link)
1654     {
1655       /* Empty LINK points back to BASE, query string and all. */
1656       return xstrdup (base);
1657     }
1658   else if (*link == '?')
1659     {
1660       /* LINK points to the same location, but changes the query
1661          string.  Examples: */
1662       /* uri_merge("path",         "?new") -> "path?new"     */
1663       /* uri_merge("path?foo",     "?new") -> "path?new"     */
1664       /* uri_merge("path?foo#bar", "?new") -> "path?new"     */
1665       /* uri_merge("path#foo",     "?new") -> "path?new"     */
1666       int baselength = end - base;
1667       merge = xmalloc (baselength + linklength + 1);
1668       memcpy (merge, base, baselength);
1669       memcpy (merge + baselength, link, linklength);
1670       merge[baselength + linklength] = '\0';
1671     }
1672   else if (*link == '#')
1673     {
1674       /* uri_merge("path",         "#new") -> "path#new"     */
1675       /* uri_merge("path#foo",     "#new") -> "path#new"     */
1676       /* uri_merge("path?foo",     "#new") -> "path?foo#new" */
1677       /* uri_merge("path?foo#bar", "#new") -> "path?foo#new" */
1678       int baselength;
1679       const char *end1 = strchr (base, '#');
1680       if (!end1)
1681         end1 = base + strlen (base);
1682       baselength = end1 - base;
1683       merge = xmalloc (baselength + linklength + 1);
1684       memcpy (merge, base, baselength);
1685       memcpy (merge + baselength, link, linklength);
1686       merge[baselength + linklength] = '\0';
1687     }
1688   else if (*link == '/' && *(link + 1) == '/')
1689     {
1690       /* LINK begins with "//" and so is a net path: we need to
1691          replace everything after (and including) the double slash
1692          with LINK. */
1693
1694       /* uri_merge("foo", "//new/bar")            -> "//new/bar"      */
1695       /* uri_merge("//old/foo", "//new/bar")      -> "//new/bar"      */
1696       /* uri_merge("http://old/foo", "//new/bar") -> "http://new/bar" */
1697
1698       int span;
1699       const char *slash;
1700       const char *start_insert;
1701
1702       /* Look for first slash. */
1703       slash = memchr (base, '/', end - base);
1704       /* If found slash and it is a double slash, then replace
1705          from this point, else default to replacing from the
1706          beginning.  */
1707       if (slash && *(slash + 1) == '/')
1708         start_insert = slash;
1709       else
1710         start_insert = base;
1711
1712       span = start_insert - base;
1713       merge = (char *)xmalloc (span + linklength + 1);
1714       if (span)
1715         memcpy (merge, base, span);
1716       memcpy (merge + span, link, linklength);
1717       merge[span + linklength] = '\0';
1718     }
1719   else if (*link == '/')
1720     {
1721       /* LINK is an absolute path: we need to replace everything
1722          after (and including) the FIRST slash with LINK.
1723
1724          So, if BASE is "http://host/whatever/foo/bar", and LINK is
1725          "/qux/xyzzy", our result should be
1726          "http://host/qux/xyzzy".  */
1727       int span;
1728       const char *slash;
1729       const char *start_insert = NULL; /* for gcc to shut up. */
1730       const char *pos = base;
1731       int seen_slash_slash = 0;
1732       /* We're looking for the first slash, but want to ignore
1733          double slash. */
1734     again:
1735       slash = memchr (pos, '/', end - pos);
1736       if (slash && !seen_slash_slash)
1737         if (*(slash + 1) == '/')
1738           {
1739             pos = slash + 2;
1740             seen_slash_slash = 1;
1741             goto again;
1742           }
1743
1744       /* At this point, SLASH is the location of the first / after
1745          "//", or the first slash altogether.  START_INSERT is the
1746          pointer to the location where LINK will be inserted.  When
1747          examining the last two examples, keep in mind that LINK
1748          begins with '/'. */
1749
1750       if (!slash && !seen_slash_slash)
1751         /* example: "foo" */
1752         /*           ^    */
1753         start_insert = base;
1754       else if (!slash && seen_slash_slash)
1755         /* example: "http://foo" */
1756         /*                     ^ */
1757         start_insert = end;
1758       else if (slash && !seen_slash_slash)
1759         /* example: "foo/bar" */
1760         /*           ^        */
1761         start_insert = base;
1762       else if (slash && seen_slash_slash)
1763         /* example: "http://something/" */
1764         /*                           ^  */
1765         start_insert = slash;
1766
1767       span = start_insert - base;
1768       merge = (char *)xmalloc (span + linklength + 1);
1769       if (span)
1770         memcpy (merge, base, span);
1771       memcpy (merge + span, link, linklength);
1772       merge[span + linklength] = '\0';
1773     }
1774   else
1775     {
1776       /* LINK is a relative URL: we need to replace everything
1777          after last slash (possibly empty) with LINK.
1778
1779          So, if BASE is "whatever/foo/bar", and LINK is "qux/xyzzy",
1780          our result should be "whatever/foo/qux/xyzzy".  */
1781       int need_explicit_slash = 0;
1782       int span;
1783       const char *start_insert;
1784       const char *last_slash = find_last_char (base, end, '/');
1785       if (!last_slash)
1786         {
1787           /* No slash found at all.  Replace what we have with LINK. */
1788           start_insert = base;
1789         }
1790       else if (last_slash && last_slash >= base + 2
1791                && last_slash[-2] == ':' && last_slash[-1] == '/')
1792         {
1793           /* example: http://host"  */
1794           /*                      ^ */
1795           start_insert = end + 1;
1796           need_explicit_slash = 1;
1797         }
1798       else
1799         {
1800           /* example: "whatever/foo/bar" */
1801           /*                        ^    */
1802           start_insert = last_slash + 1;
1803         }
1804
1805       span = start_insert - base;
1806       merge = (char *)xmalloc (span + linklength + 1);
1807       if (span)
1808         memcpy (merge, base, span);
1809       if (need_explicit_slash)
1810         merge[span - 1] = '/';
1811       memcpy (merge + span, link, linklength);
1812       merge[span + linklength] = '\0';
1813     }
1814
1815   return merge;
1816 }
1817 \f
1818 #define APPEND(p, s) do {                       \
1819   int len = strlen (s);                         \
1820   memcpy (p, s, len);                           \
1821   p += len;                                     \
1822 } while (0)
1823
1824 /* Use this instead of password when the actual password is supposed
1825    to be hidden.  We intentionally use a generic string without giving
1826    away the number of characters in the password, like previous
1827    versions did.  */
1828 #define HIDDEN_PASSWORD "*password*"
1829
1830 /* Recreate the URL string from the data in URL.
1831
1832    If HIDE is non-zero (as it is when we're calling this on a URL we
1833    plan to print, but not when calling it to canonicalize a URL for
1834    use within the program), password will be hidden.  Unsafe
1835    characters in the URL will be quoted.  */
1836
1837 char *
1838 url_string (const struct url *url, int hide_password)
1839 {
1840   int size;
1841   char *result, *p;
1842   char *quoted_host, *quoted_user = NULL, *quoted_passwd = NULL;
1843
1844   int scheme_port  = supported_schemes[url->scheme].default_port;
1845   const char *scheme_str = supported_schemes[url->scheme].leading_string;
1846   int fplen = full_path_length (url);
1847
1848   int brackets_around_host;
1849
1850   assert (scheme_str != NULL);
1851
1852   /* Make sure the user name and password are quoted. */
1853   if (url->user)
1854     {
1855       quoted_user = url_escape_allow_passthrough (url->user);
1856       if (url->passwd)
1857         {
1858           if (hide_password)
1859             quoted_passwd = HIDDEN_PASSWORD;
1860           else
1861             quoted_passwd = url_escape_allow_passthrough (url->passwd);
1862         }
1863     }
1864
1865   /* In the unlikely event that the host name contains non-printable
1866      characters, quote it for displaying to the user.  */
1867   quoted_host = url_escape_allow_passthrough (url->host);
1868
1869   /* Undo the quoting of colons that URL escaping performs.  IPv6
1870      addresses may legally contain colons, and in that case must be
1871      placed in square brackets.  */
1872   if (quoted_host != url->host)
1873     unescape_single_char (quoted_host, ':');
1874   brackets_around_host = strchr (quoted_host, ':') != NULL;
1875
1876   size = (strlen (scheme_str)
1877           + strlen (quoted_host)
1878           + (brackets_around_host ? 2 : 0)
1879           + fplen
1880           + 1);
1881   if (url->port != scheme_port)
1882     size += 1 + numdigit (url->port);
1883   if (quoted_user)
1884     {
1885       size += 1 + strlen (quoted_user);
1886       if (quoted_passwd)
1887         size += 1 + strlen (quoted_passwd);
1888     }
1889
1890   p = result = xmalloc (size);
1891
1892   APPEND (p, scheme_str);
1893   if (quoted_user)
1894     {
1895       APPEND (p, quoted_user);
1896       if (quoted_passwd)
1897         {
1898           *p++ = ':';
1899           APPEND (p, quoted_passwd);
1900         }
1901       *p++ = '@';
1902     }
1903
1904   if (brackets_around_host)
1905     *p++ = '[';
1906   APPEND (p, quoted_host);
1907   if (brackets_around_host)
1908     *p++ = ']';
1909   if (url->port != scheme_port)
1910     {
1911       *p++ = ':';
1912       p = number_to_string (p, url->port);
1913     }
1914
1915   full_path_write (url, p);
1916   p += fplen;
1917   *p++ = '\0';
1918
1919   assert (p - result == size);
1920
1921   if (quoted_user && quoted_user != url->user)
1922     xfree (quoted_user);
1923   if (quoted_passwd && !hide_password && quoted_passwd != url->passwd)
1924     xfree (quoted_passwd);
1925   if (quoted_host != url->host)
1926     xfree (quoted_host);
1927
1928   return result;
1929 }
1930 \f
1931 /* Return non-zero if scheme a is similar to scheme b.
1932  
1933    Schemes are similar if they are equal.  If SSL is supported, schemes
1934    are also similar if one is http (SCHEME_HTTP) and the other is https
1935    (SCHEME_HTTPS).  */
1936 int
1937 schemes_are_similar_p (enum url_scheme a, enum url_scheme b)
1938 {
1939   if (a == b)
1940     return 1;
1941 #ifdef HAVE_SSL
1942   if ((a == SCHEME_HTTP && b == SCHEME_HTTPS)
1943       || (a == SCHEME_HTTPS && b == SCHEME_HTTP))
1944     return 1;
1945 #endif
1946   return 0;
1947 }
1948 \f
1949 #if 0
1950 /* Debugging and testing support for path_simplify. */
1951
1952 /* Debug: run path_simplify on PATH and return the result in a new
1953    string.  Useful for calling from the debugger.  */
1954 static char *
1955 ps (char *path)
1956 {
1957   char *copy = xstrdup (path);
1958   path_simplify (copy);
1959   return copy;
1960 }
1961
1962 static void
1963 run_test (char *test, char *expected_result, int expected_change)
1964 {
1965   char *test_copy = xstrdup (test);
1966   int modified = path_simplify (test_copy);
1967
1968   if (0 != strcmp (test_copy, expected_result))
1969     {
1970       printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
1971               test, expected_result, test_copy);
1972     }
1973   if (modified != expected_change)
1974     {
1975       if (expected_change == 1)
1976         printf ("Expected modification with path_simplify(\"%s\").\n",
1977                 test);
1978       else
1979         printf ("Expected no modification with path_simplify(\"%s\").\n",
1980                 test);
1981     }
1982   xfree (test_copy);
1983 }
1984
1985 static void
1986 test_path_simplify (void)
1987 {
1988   static struct {
1989     char *test, *result;
1990     int should_modify;
1991   } tests[] = {
1992     { "",                       "",             0 },
1993     { ".",                      "",             1 },
1994     { "./",                     "",             1 },
1995     { "..",                     "..",           0 },
1996     { "../",                    "../",          0 },
1997     { "foo",                    "foo",          0 },
1998     { "foo/bar",                "foo/bar",      0 },
1999     { "foo///bar",              "foo///bar",    0 },
2000     { "foo/.",                  "foo/",         1 },
2001     { "foo/./",                 "foo/",         1 },
2002     { "foo./",                  "foo./",        0 },
2003     { "foo/../bar",             "bar",          1 },
2004     { "foo/../bar/",            "bar/",         1 },
2005     { "foo/bar/..",             "foo/",         1 },
2006     { "foo/bar/../x",           "foo/x",        1 },
2007     { "foo/bar/../x/",          "foo/x/",       1 },
2008     { "foo/..",                 "",             1 },
2009     { "foo/../..",              "..",           1 },
2010     { "foo/../../..",           "../..",        1 },
2011     { "foo/../../bar/../../baz", "../../baz",   1 },
2012     { "a/b/../../c",            "c",            1 },
2013     { "./a/../b",               "b",            1 }
2014   };
2015   int i;
2016
2017   for (i = 0; i < countof (tests); i++)
2018     {
2019       char *test = tests[i].test;
2020       char *expected_result = tests[i].result;
2021       int   expected_change = tests[i].should_modify;
2022       run_test (test, expected_result, expected_change);
2023     }
2024 }
2025 #endif