]> sjero.net Git - wget/blob - src/url.c
aprintf, not asprintf.
[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         error = aprintf (_("HTTPS support not compiled in"));
904       else
905         error = aprintf (_(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   char *index_filename = "index.html"; /* The default index file is index.html */
1452
1453   fnres.base = NULL;
1454   fnres.size = 0;
1455   fnres.tail = 0;
1456
1457   /* If an alternative index file was defined, change index_filename */
1458   if (opt.default_page)
1459     index_filename = opt.default_page;
1460      
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_filename;
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