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