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