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