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