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