]> sjero.net Git - wget/blob - src/url.c
Automated merge.
[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, struct iri *iri)
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, *new_url = 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   if (iri && iri->utf8_encode)
674     {
675       url_unescape ((char *) url);
676       iri->utf8_encode = remote_to_utf8 (iri, url, (const char **) &new_url);
677       if (!iri->utf8_encode)
678         new_url = NULL;
679     }
680
681   url_encoded = reencode_escapes (new_url ? new_url : url);
682   p = url_encoded;
683
684   if (new_url && url_encoded != new_url)
685     xfree (new_url);
686
687   p += strlen (supported_schemes[scheme].leading_string);
688   uname_b = p;
689   p = url_skip_credentials (p);
690   uname_e = p;
691
692   /* scheme://user:pass@host[:port]... */
693   /*                    ^              */
694
695   /* We attempt to break down the URL into the components path,
696      params, query, and fragment.  They are ordered like this:
697
698        scheme://host[:port][/path][;params][?query][#fragment]  */
699
700   path_b     = path_e     = NULL;
701   params_b   = params_e   = NULL;
702   query_b    = query_e    = NULL;
703   fragment_b = fragment_e = NULL;
704
705   /* Initialize separators for optional parts of URL, depending on the
706      scheme.  For example, FTP has params, and HTTP and HTTPS have
707      query string and fragment. */
708   seps = init_seps (scheme);
709
710   host_b = p;
711
712   if (*p == '[')
713     {
714       /* Handle IPv6 address inside square brackets.  Ideally we'd
715          just look for the terminating ']', but rfc2732 mandates
716          rejecting invalid IPv6 addresses.  */
717
718       /* The address begins after '['. */
719       host_b = p + 1;
720       host_e = strchr (host_b, ']');
721
722       if (!host_e)
723         {
724           error_code = PE_UNTERMINATED_IPV6_ADDRESS;
725           goto error;
726         }
727
728 #ifdef ENABLE_IPV6
729       /* Check if the IPv6 address is valid. */
730       if (!is_valid_ipv6_address(host_b, host_e))
731         {
732           error_code = PE_INVALID_IPV6_ADDRESS;
733           goto error;
734         }
735
736       /* Continue parsing after the closing ']'. */
737       p = host_e + 1;
738 #else
739       error_code = PE_IPV6_NOT_SUPPORTED;
740       goto error;
741 #endif
742
743       /* The closing bracket must be followed by a separator or by the
744          null char.  */
745       /* http://[::1]... */
746       /*             ^   */
747       if (!strchr (seps, *p))
748         {
749           /* Trailing garbage after []-delimited IPv6 address. */
750           error_code = PE_INVALID_HOST_NAME;
751           goto error;
752         }
753     }
754   else
755     {
756       p = strpbrk_or_eos (p, seps);
757       host_e = p;
758     }
759   ++seps;                       /* advance to '/' */
760
761   if (host_b == host_e)
762     {
763       error_code = PE_INVALID_HOST_NAME;
764       goto error;
765     }
766
767   port = scheme_default_port (scheme);
768   if (*p == ':')
769     {
770       const char *port_b, *port_e, *pp;
771
772       /* scheme://host:port/tralala */
773       /*              ^             */
774       ++p;
775       port_b = p;
776       p = strpbrk_or_eos (p, seps);
777       port_e = p;
778
779       /* Allow empty port, as per rfc2396. */
780       if (port_b != port_e)
781         for (port = 0, pp = port_b; pp < port_e; pp++)
782           {
783             if (!c_isdigit (*pp))
784               {
785                 /* http://host:12randomgarbage/blah */
786                 /*               ^                  */
787                 error_code = PE_BAD_PORT_NUMBER;
788                 goto error;
789               }
790             port = 10 * port + (*pp - '0');
791             /* Check for too large port numbers here, before we have
792                a chance to overflow on bogus port values.  */
793             if (port > 0xffff)
794               {
795                 error_code = PE_BAD_PORT_NUMBER;
796                 goto error;
797               }
798           }
799     }
800   /* Advance to the first separator *after* '/' (either ';' or '?',
801      depending on the scheme).  */
802   ++seps;
803
804   /* Get the optional parts of URL, each part being delimited by
805      current location and the position of the next separator.  */
806 #define GET_URL_PART(sepchar, var) do {                         \
807   if (*p == sepchar)                                            \
808     var##_b = ++p, var##_e = p = strpbrk_or_eos (p, seps);      \
809   ++seps;                                                       \
810 } while (0)
811
812   GET_URL_PART ('/', path);
813   if (supported_schemes[scheme].flags & scm_has_params)
814     GET_URL_PART (';', params);
815   if (supported_schemes[scheme].flags & scm_has_query)
816     GET_URL_PART ('?', query);
817   if (supported_schemes[scheme].flags & scm_has_fragment)
818     GET_URL_PART ('#', fragment);
819
820 #undef GET_URL_PART
821   assert (*p == 0);
822
823   if (uname_b != uname_e)
824     {
825       /* http://user:pass@host */
826       /*        ^         ^    */
827       /*     uname_b   uname_e */
828       if (!parse_credentials (uname_b, uname_e - 1, &user, &passwd))
829         {
830           error_code = PE_INVALID_USER_NAME;
831           goto error;
832         }
833     }
834
835   u = xnew0 (struct url);
836   u->scheme = scheme;
837   u->host   = strdupdelim (host_b, host_e);
838   u->port   = port;
839   u->user   = user;
840   u->passwd = passwd;
841
842   u->path = strdupdelim (path_b, path_e);
843   path_modified = path_simplify (scheme, u->path);
844   split_path (u->path, &u->dir, &u->file);
845
846   host_modified = lowercase_str (u->host);
847
848   /* Decode %HH sequences in host name.  This is important not so much
849      to support %HH sequences in host names (which other browser
850      don't), but to support binary characters (which will have been
851      converted to %HH by reencode_escapes).  */
852   if (strchr (u->host, '%'))
853     {
854       url_unescape (u->host);
855       host_modified = true;
856
857       /* Apply IDNA regardless of iri->utf8_encode status */
858       if (opt.enable_iri && iri)
859         {
860           char *new = idn_encode (iri, u->host);
861           if (new)
862             {
863               xfree (u->host);
864               u->host = new;
865               host_modified = true;
866             }
867         }
868     }
869
870   if (params_b)
871     u->params = strdupdelim (params_b, params_e);
872   if (query_b)
873     u->query = strdupdelim (query_b, query_e);
874   if (fragment_b)
875     u->fragment = strdupdelim (fragment_b, fragment_e);
876
877   if (opt.enable_iri || path_modified || u->fragment || host_modified || path_b == path_e)
878     {
879       /* If we suspect that a transformation has rendered what
880          url_string might return different from URL_ENCODED, rebuild
881          u->url using url_string.  */
882       u->url = url_string (u, URL_AUTH_SHOW);
883
884       if (url_encoded != url)
885         xfree ((char *) url_encoded);
886     }
887   else
888     {
889       if (url_encoded == url)
890         u->url = xstrdup (url);
891       else
892         u->url = url_encoded;
893     }
894
895   return u;
896
897  error:
898   /* Cleanup in case of error: */
899   if (url_encoded && url_encoded != url)
900     xfree (url_encoded);
901
902   /* Transmit the error code to the caller, if the caller wants to
903      know.  */
904   if (error)
905     *error = error_code;
906   return NULL;
907 }
908
909 /* Return the error message string from ERROR_CODE, which should have
910    been retrieved from url_parse.  The error message is translated.  */
911
912 char *
913 url_error (const char *url, int error_code)
914 {
915   assert (error_code >= 0 && ((size_t) error_code) < countof (parse_errors));
916
917   if (error_code == PE_UNSUPPORTED_SCHEME)
918     {
919       char *error, *p;
920       char *scheme = xstrdup (url);
921       assert (url_has_scheme (url));
922
923       if ((p = strchr (scheme, ':')))
924         *p = '\0';
925       if (!strcasecmp (scheme, "https"))
926         asprintf (&error, _("HTTPS support not compiled in"));
927       else
928         asprintf (&error, _(parse_errors[error_code]), quote (scheme));
929       xfree (scheme);
930
931       return error;
932     }
933   else
934     return xstrdup (_(parse_errors[error_code]));
935 }
936
937 /* Split PATH into DIR and FILE.  PATH comes from the URL and is
938    expected to be URL-escaped.
939
940    The path is split into directory (the part up to the last slash)
941    and file (the part after the last slash), which are subsequently
942    unescaped.  Examples:
943
944    PATH                 DIR           FILE
945    "foo/bar/baz"        "foo/bar"     "baz"
946    "foo/bar/"           "foo/bar"     ""
947    "foo"                ""            "foo"
948    "foo/bar/baz%2fqux"  "foo/bar"     "baz/qux" (!)
949
950    DIR and FILE are freshly allocated.  */
951
952 static void
953 split_path (const char *path, char **dir, char **file)
954 {
955   char *last_slash = strrchr (path, '/');
956   if (!last_slash)
957     {
958       *dir = xstrdup ("");
959       *file = xstrdup (path);
960     }
961   else
962     {
963       *dir = strdupdelim (path, last_slash);
964       *file = xstrdup (last_slash + 1);
965     }
966   url_unescape (*dir);
967   url_unescape (*file);
968 }
969
970 /* Note: URL's "full path" is the path with the query string and
971    params appended.  The "fragment" (#foo) is intentionally ignored,
972    but that might be changed.  For example, if the original URL was
973    "http://host:port/foo/bar/baz;bullshit?querystring#uselessfragment",
974    the full path will be "/foo/bar/baz;bullshit?querystring".  */
975
976 /* Return the length of the full path, without the terminating
977    zero.  */
978
979 static int
980 full_path_length (const struct url *url)
981 {
982   int len = 0;
983
984 #define FROB(el) if (url->el) len += 1 + strlen (url->el)
985
986   FROB (path);
987   FROB (params);
988   FROB (query);
989
990 #undef FROB
991
992   return len;
993 }
994
995 /* Write out the full path. */
996
997 static void
998 full_path_write (const struct url *url, char *where)
999 {
1000 #define FROB(el, chr) do {                      \
1001   char *f_el = url->el;                         \
1002   if (f_el) {                                   \
1003     int l = strlen (f_el);                      \
1004     *where++ = chr;                             \
1005     memcpy (where, f_el, l);                    \
1006     where += l;                                 \
1007   }                                             \
1008 } while (0)
1009
1010   FROB (path, '/');
1011   FROB (params, ';');
1012   FROB (query, '?');
1013
1014 #undef FROB
1015 }
1016
1017 /* Public function for getting the "full path".  E.g. if u->path is
1018    "foo/bar" and u->query is "param=value", full_path will be
1019    "/foo/bar?param=value". */
1020
1021 char *
1022 url_full_path (const struct url *url)
1023 {
1024   int length = full_path_length (url);
1025   char *full_path = xmalloc (length + 1);
1026
1027   full_path_write (url, full_path);
1028   full_path[length] = '\0';
1029
1030   return full_path;
1031 }
1032
1033 /* Unescape CHR in an otherwise escaped STR.  Used to selectively
1034    escaping of certain characters, such as "/" and ":".  Returns a
1035    count of unescaped chars.  */
1036
1037 static void
1038 unescape_single_char (char *str, char chr)
1039 {
1040   const char c1 = XNUM_TO_DIGIT (chr >> 4);
1041   const char c2 = XNUM_TO_DIGIT (chr & 0xf);
1042   char *h = str;                /* hare */
1043   char *t = str;                /* tortoise */
1044   for (; *h; h++, t++)
1045     {
1046       if (h[0] == '%' && h[1] == c1 && h[2] == c2)
1047         {
1048           *t = chr;
1049           h += 2;
1050         }
1051       else
1052         *t = *h;
1053     }
1054   *t = '\0';
1055 }
1056
1057 /* Escape unsafe and reserved characters, except for the slash
1058    characters.  */
1059
1060 static char *
1061 url_escape_dir (const char *dir)
1062 {
1063   char *newdir = url_escape_1 (dir, urlchr_unsafe | urlchr_reserved, 1);
1064   if (newdir == dir)
1065     return (char *)dir;
1066
1067   unescape_single_char (newdir, '/');
1068   return newdir;
1069 }
1070
1071 /* Sync u->path and u->url with u->dir and u->file.  Called after
1072    u->file or u->dir have been changed, typically by the FTP code.  */
1073
1074 static void
1075 sync_path (struct url *u)
1076 {
1077   char *newpath, *efile, *edir;
1078
1079   xfree (u->path);
1080
1081   /* u->dir and u->file are not escaped.  URL-escape them before
1082      reassembling them into u->path.  That way, if they contain
1083      separators like '?' or even if u->file contains slashes, the
1084      path will be correctly assembled.  (u->file can contain slashes
1085      if the URL specifies it with %2f, or if an FTP server returns
1086      it.)  */
1087   edir = url_escape_dir (u->dir);
1088   efile = url_escape_1 (u->file, urlchr_unsafe | urlchr_reserved, 1);
1089
1090   if (!*edir)
1091     newpath = xstrdup (efile);
1092   else
1093     {
1094       int dirlen = strlen (edir);
1095       int filelen = strlen (efile);
1096
1097       /* Copy "DIR/FILE" to newpath. */
1098       char *p = newpath = xmalloc (dirlen + 1 + filelen + 1);
1099       memcpy (p, edir, dirlen);
1100       p += dirlen;
1101       *p++ = '/';
1102       memcpy (p, efile, filelen);
1103       p += filelen;
1104       *p = '\0';
1105     }
1106
1107   u->path = newpath;
1108
1109   if (edir != u->dir)
1110     xfree (edir);
1111   if (efile != u->file)
1112     xfree (efile);
1113
1114   /* Regenerate u->url as well.  */
1115   xfree (u->url);
1116   u->url = url_string (u, URL_AUTH_SHOW);
1117 }
1118
1119 /* Mutators.  Code in ftp.c insists on changing u->dir and u->file.
1120    This way we can sync u->path and u->url when they get changed.  */
1121
1122 void
1123 url_set_dir (struct url *url, const char *newdir)
1124 {
1125   xfree (url->dir);
1126   url->dir = xstrdup (newdir);
1127   sync_path (url);
1128 }
1129
1130 void
1131 url_set_file (struct url *url, const char *newfile)
1132 {
1133   xfree (url->file);
1134   url->file = xstrdup (newfile);
1135   sync_path (url);
1136 }
1137
1138 void
1139 url_free (struct url *url)
1140 {
1141   xfree (url->host);
1142   xfree (url->path);
1143   xfree (url->url);
1144
1145   xfree_null (url->params);
1146   xfree_null (url->query);
1147   xfree_null (url->fragment);
1148   xfree_null (url->user);
1149   xfree_null (url->passwd);
1150
1151   xfree (url->dir);
1152   xfree (url->file);
1153
1154   xfree (url);
1155 }
1156 \f
1157 /* Create all the necessary directories for PATH (a file).  Calls
1158    make_directory internally.  */
1159 int
1160 mkalldirs (const char *path)
1161 {
1162   const char *p;
1163   char *t;
1164   struct_stat st;
1165   int res;
1166
1167   p = path + strlen (path);
1168   for (; *p != '/' && p != path; p--)
1169     ;
1170
1171   /* Don't create if it's just a file.  */
1172   if ((p == path) && (*p != '/'))
1173     return 0;
1174   t = strdupdelim (path, p);
1175
1176   /* Check whether the directory exists.  */
1177   if ((stat (t, &st) == 0))
1178     {
1179       if (S_ISDIR (st.st_mode))
1180         {
1181           xfree (t);
1182           return 0;
1183         }
1184       else
1185         {
1186           /* If the dir exists as a file name, remove it first.  This
1187              is *only* for Wget to work with buggy old CERN http
1188              servers.  Here is the scenario: When Wget tries to
1189              retrieve a directory without a slash, e.g.
1190              http://foo/bar (bar being a directory), CERN server will
1191              not redirect it too http://foo/bar/ -- it will generate a
1192              directory listing containing links to bar/file1,
1193              bar/file2, etc.  Wget will lose because it saves this
1194              HTML listing to a file `bar', so it cannot create the
1195              directory.  To work around this, if the file of the same
1196              name exists, we just remove it and create the directory
1197              anyway.  */
1198           DEBUGP (("Removing %s because of directory danger!\n", t));
1199           unlink (t);
1200         }
1201     }
1202   res = make_directory (t);
1203   if (res != 0)
1204     logprintf (LOG_NOTQUIET, "%s: %s", t, strerror (errno));
1205   xfree (t);
1206   return res;
1207 }
1208 \f
1209 /* Functions for constructing the file name out of URL components.  */
1210
1211 /* A growable string structure, used by url_file_name and friends.
1212    This should perhaps be moved to utils.c.
1213
1214    The idea is to have a convenient and efficient way to construct a
1215    string by having various functions append data to it.  Instead of
1216    passing the obligatory BASEVAR, SIZEVAR and TAILPOS to all the
1217    functions in questions, we pass the pointer to this struct.  */
1218
1219 struct growable {
1220   char *base;
1221   int size;
1222   int tail;
1223 };
1224
1225 /* Ensure that the string can accept APPEND_COUNT more characters past
1226    the current TAIL position.  If necessary, this will grow the string
1227    and update its allocated size.  If the string is already large
1228    enough to take TAIL+APPEND_COUNT characters, this does nothing.  */
1229 #define GROW(g, append_size) do {                                       \
1230   struct growable *G_ = g;                                              \
1231   DO_REALLOC (G_->base, G_->size, G_->tail + append_size, char);        \
1232 } while (0)
1233
1234 /* Return the tail position of the string. */
1235 #define TAIL(r) ((r)->base + (r)->tail)
1236
1237 /* Move the tail position by APPEND_COUNT characters. */
1238 #define TAIL_INCR(r, append_count) ((r)->tail += append_count)
1239
1240 /* Append the string STR to DEST.  NOTICE: the string in DEST is not
1241    terminated.  */
1242
1243 static void
1244 append_string (const char *str, struct growable *dest)
1245 {
1246   int l = strlen (str);
1247   GROW (dest, l);
1248   memcpy (TAIL (dest), str, l);
1249   TAIL_INCR (dest, l);
1250 }
1251
1252 /* Append CH to DEST.  For example, append_char (0, DEST)
1253    zero-terminates DEST.  */
1254
1255 static void
1256 append_char (char ch, struct growable *dest)
1257 {
1258   GROW (dest, 1);
1259   *TAIL (dest) = ch;
1260   TAIL_INCR (dest, 1);
1261 }
1262
1263 enum {
1264   filechr_not_unix    = 1,      /* unusable on Unix, / and \0 */
1265   filechr_not_windows = 2,      /* unusable on Windows, one of \|/<>?:*" */
1266   filechr_control     = 4       /* a control character, e.g. 0-31 */
1267 };
1268
1269 #define FILE_CHAR_TEST(c, mask) (filechr_table[(unsigned char)(c)] & (mask))
1270
1271 /* Shorthands for the table: */
1272 #define U filechr_not_unix
1273 #define W filechr_not_windows
1274 #define C filechr_control
1275
1276 #define UW U|W
1277 #define UWC U|W|C
1278
1279 /* Table of characters unsafe under various conditions (see above).
1280
1281    Arguably we could also claim `%' to be unsafe, since we use it as
1282    the escape character.  If we ever want to be able to reliably
1283    translate file name back to URL, this would become important
1284    crucial.  Right now, it's better to be minimal in escaping.  */
1285
1286 static const unsigned char filechr_table[256] =
1287 {
1288 UWC,  C,  C,  C,   C,  C,  C,  C,   /* NUL SOH STX ETX  EOT ENQ ACK BEL */
1289   C,  C,  C,  C,   C,  C,  C,  C,   /* BS  HT  LF  VT   FF  CR  SO  SI  */
1290   C,  C,  C,  C,   C,  C,  C,  C,   /* DLE DC1 DC2 DC3  DC4 NAK SYN ETB */
1291   C,  C,  C,  C,   C,  C,  C,  C,   /* CAN EM  SUB ESC  FS  GS  RS  US  */
1292   0,  0,  W,  0,   0,  0,  0,  0,   /* SP  !   "   #    $   %   &   '   */
1293   0,  0,  W,  0,   0,  0,  0, UW,   /* (   )   *   +    ,   -   .   /   */
1294   0,  0,  0,  0,   0,  0,  0,  0,   /* 0   1   2   3    4   5   6   7   */
1295   0,  0,  W,  0,   W,  0,  W,  W,   /* 8   9   :   ;    <   =   >   ?   */
1296   0,  0,  0,  0,   0,  0,  0,  0,   /* @   A   B   C    D   E   F   G   */
1297   0,  0,  0,  0,   0,  0,  0,  0,   /* H   I   J   K    L   M   N   O   */
1298   0,  0,  0,  0,   0,  0,  0,  0,   /* P   Q   R   S    T   U   V   W   */
1299   0,  0,  0,  0,   W,  0,  0,  0,   /* X   Y   Z   [    \   ]   ^   _   */
1300   0,  0,  0,  0,   0,  0,  0,  0,   /* `   a   b   c    d   e   f   g   */
1301   0,  0,  0,  0,   0,  0,  0,  0,   /* h   i   j   k    l   m   n   o   */
1302   0,  0,  0,  0,   0,  0,  0,  0,   /* p   q   r   s    t   u   v   w   */
1303   0,  0,  0,  0,   W,  0,  0,  C,   /* x   y   z   {    |   }   ~   DEL */
1304
1305   C, C, C, C,  C, C, C, C,  C, C, C, C,  C, C, C, C, /* 128-143 */
1306   C, C, C, C,  C, C, C, C,  C, C, C, C,  C, C, C, C, /* 144-159 */
1307   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1308   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1309
1310   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1311   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1312   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1313   0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
1314 };
1315 #undef U
1316 #undef W
1317 #undef C
1318 #undef UW
1319 #undef UWC
1320
1321 /* FN_PORT_SEP is the separator between host and port in file names
1322    for non-standard port numbers.  On Unix this is normally ':', as in
1323    "www.xemacs.org:4001/index.html".  Under Windows, we set it to +
1324    because Windows can't handle ':' in file names.  */
1325 #define FN_PORT_SEP  (opt.restrict_files_os != restrict_windows ? ':' : '+')
1326
1327 /* FN_QUERY_SEP is the separator between the file name and the URL
1328    query, normally '?'.  Since Windows cannot handle '?' as part of
1329    file name, we use '@' instead there.  */
1330 #define FN_QUERY_SEP (opt.restrict_files_os != restrict_windows ? '?' : '@')
1331
1332 /* Quote path element, characters in [b, e), as file name, and append
1333    the quoted string to DEST.  Each character is quoted as per
1334    file_unsafe_char and the corresponding table.
1335
1336    If ESCAPED is true, the path element is considered to be
1337    URL-escaped and will be unescaped prior to inspection.  */
1338
1339 static void
1340 append_uri_pathel (const char *b, const char *e, bool escaped,
1341                    struct growable *dest)
1342 {
1343   const char *p;
1344   int quoted, outlen;
1345
1346   int mask;
1347   if (opt.restrict_files_os == restrict_unix)
1348     mask = filechr_not_unix;
1349   else
1350     mask = filechr_not_windows;
1351   if (opt.restrict_files_ctrl)
1352     mask |= filechr_control;
1353
1354   /* Copy [b, e) to PATHEL and URL-unescape it. */
1355   if (escaped)
1356     {
1357       char *unescaped;
1358       BOUNDED_TO_ALLOCA (b, e, unescaped);
1359       url_unescape (unescaped);
1360       b = unescaped;
1361       e = unescaped + strlen (unescaped);
1362     }
1363
1364   /* Defang ".." when found as component of path.  Remember that path
1365      comes from the URL and might contain malicious input.  */
1366   if (e - b == 2 && b[0] == '.' && b[1] == '.')
1367     {
1368       b = "%2E%2E";
1369       e = b + 6;
1370     }
1371
1372   /* Walk the PATHEL string and check how many characters we'll need
1373      to quote.  */
1374   quoted = 0;
1375   for (p = b; p < e; p++)
1376     if (FILE_CHAR_TEST (*p, mask))
1377       ++quoted;
1378
1379   /* Calculate the length of the output string.  e-b is the input
1380      string length.  Each quoted char introduces two additional
1381      characters in the string, hence 2*quoted.  */
1382   outlen = (e - b) + (2 * quoted);
1383   GROW (dest, outlen);
1384
1385   if (!quoted)
1386     {
1387       /* If there's nothing to quote, we can simply append the string
1388          without processing it again.  */
1389       memcpy (TAIL (dest), b, outlen);
1390     }
1391   else
1392     {
1393       char *q = TAIL (dest);
1394       for (p = b; p < e; p++)
1395         {
1396           if (!FILE_CHAR_TEST (*p, mask))
1397             *q++ = *p;
1398           else
1399             {
1400               unsigned char ch = *p;
1401               *q++ = '%';
1402               *q++ = XNUM_TO_DIGIT (ch >> 4);
1403               *q++ = XNUM_TO_DIGIT (ch & 0xf);
1404             }
1405         }
1406       assert (q - TAIL (dest) == outlen);
1407     }
1408   
1409   /* Perform inline case transformation if required.  */
1410   if (opt.restrict_files_case == restrict_lowercase
1411       || opt.restrict_files_case == restrict_uppercase)
1412     {
1413       char *q;
1414       for (q = TAIL (dest); q < TAIL (dest) + outlen; ++q)
1415         {
1416           if (opt.restrict_files_case == restrict_lowercase)
1417             *q = c_tolower (*q);
1418           else
1419             *q = c_toupper (*q);
1420         }
1421     }
1422           
1423   TAIL_INCR (dest, outlen);
1424 }
1425
1426 /* Append to DEST the directory structure that corresponds the
1427    directory part of URL's path.  For example, if the URL is
1428    http://server/dir1/dir2/file, this appends "/dir1/dir2".
1429
1430    Each path element ("dir1" and "dir2" in the above example) is
1431    examined, url-unescaped, and re-escaped as file name element.
1432
1433    Additionally, it cuts as many directories from the path as
1434    specified by opt.cut_dirs.  For example, if opt.cut_dirs is 1, it
1435    will produce "bar" for the above example.  For 2 or more, it will
1436    produce "".
1437
1438    Each component of the path is quoted for use as file name.  */
1439
1440 static void
1441 append_dir_structure (const struct url *u, struct growable *dest)
1442 {
1443   char *pathel, *next;
1444   int cut = opt.cut_dirs;
1445
1446   /* Go through the path components, de-URL-quote them, and quote them
1447      (if necessary) as file names.  */
1448
1449   pathel = u->path;
1450   for (; (next = strchr (pathel, '/')) != NULL; pathel = next + 1)
1451     {
1452       if (cut-- > 0)
1453         continue;
1454       if (pathel == next)
1455         /* Ignore empty pathels.  */
1456         continue;
1457
1458       if (dest->tail)
1459         append_char ('/', dest);
1460       append_uri_pathel (pathel, next, true, dest);
1461     }
1462 }
1463
1464 /* Return a unique file name that matches the given URL as good as
1465    possible.  Does not create directories on the file system.  */
1466
1467 char *
1468 url_file_name (const struct url *u)
1469 {
1470   struct growable fnres;        /* stands for "file name result" */
1471
1472   const char *u_file, *u_query;
1473   char *fname, *unique;
1474   char *index_filename = "index.html"; /* The default index file is index.html */
1475
1476   fnres.base = NULL;
1477   fnres.size = 0;
1478   fnres.tail = 0;
1479
1480   /* If an alternative index file was defined, change index_filename */
1481   if (opt.default_page)
1482     index_filename = opt.default_page;
1483      
1484
1485   /* Start with the directory prefix, if specified. */
1486   if (opt.dir_prefix)
1487     append_string (opt.dir_prefix, &fnres);
1488
1489   /* If "dirstruct" is turned on (typically the case with -r), add
1490      the host and port (unless those have been turned off) and
1491      directory structure.  */
1492   if (opt.dirstruct)
1493     {
1494       if (opt.protocol_directories)
1495         {
1496           if (fnres.tail)
1497             append_char ('/', &fnres);
1498           append_string (supported_schemes[u->scheme].name, &fnres);
1499         }
1500       if (opt.add_hostdir)
1501         {
1502           if (fnres.tail)
1503             append_char ('/', &fnres);
1504           if (0 != strcmp (u->host, ".."))
1505             append_string (u->host, &fnres);
1506           else
1507             /* Host name can come from the network; malicious DNS may
1508                allow ".." to be resolved, causing us to write to
1509                "../<file>".  Defang such host names.  */
1510             append_string ("%2E%2E", &fnres);
1511           if (u->port != scheme_default_port (u->scheme))
1512             {
1513               char portstr[24];
1514               number_to_string (portstr, u->port);
1515               append_char (FN_PORT_SEP, &fnres);
1516               append_string (portstr, &fnres);
1517             }
1518         }
1519
1520       append_dir_structure (u, &fnres);
1521     }
1522
1523   /* Add the file name. */
1524   if (fnres.tail)
1525     append_char ('/', &fnres);
1526   u_file = *u->file ? u->file : index_filename;
1527   append_uri_pathel (u_file, u_file + strlen (u_file), false, &fnres);
1528
1529   /* Append "?query" to the file name. */
1530   u_query = u->query && *u->query ? u->query : NULL;
1531   if (u_query)
1532     {
1533       append_char (FN_QUERY_SEP, &fnres);
1534       append_uri_pathel (u_query, u_query + strlen (u_query), true, &fnres);
1535     }
1536
1537   /* Zero-terminate the file name. */
1538   append_char ('\0', &fnres);
1539
1540   fname = fnres.base;
1541
1542   /* Check the cases in which the unique extensions are not used:
1543      1) Clobbering is turned off (-nc).
1544      2) Retrieval with regetting.
1545      3) Timestamping is used.
1546      4) Hierarchy is built.
1547
1548      The exception is the case when file does exist and is a
1549      directory (see `mkalldirs' for explanation).  */
1550
1551   if ((opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct)
1552       && !(file_exists_p (fname) && !file_non_directory_p (fname)))
1553     return fname;
1554
1555   unique = unique_name (fname, true);
1556   if (unique != fname)
1557     xfree (fname);
1558   return unique;
1559 }
1560 \f
1561 /* Resolve "." and ".." elements of PATH by destructively modifying
1562    PATH and return true if PATH has been modified, false otherwise.
1563
1564    The algorithm is in spirit similar to the one described in rfc1808,
1565    although implemented differently, in one pass.  To recap, path
1566    elements containing only "." are removed, and ".." is taken to mean
1567    "back up one element".  Single leading and trailing slashes are
1568    preserved.
1569
1570    For example, "a/b/c/./../d/.." will yield "a/b/".  More exhaustive
1571    test examples are provided below.  If you change anything in this
1572    function, run test_path_simplify to make sure you haven't broken a
1573    test case.  */
1574
1575 static bool
1576 path_simplify (enum url_scheme scheme, char *path)
1577 {
1578   char *h = path;               /* hare */
1579   char *t = path;               /* tortoise */
1580   char *beg = path;
1581   char *end = strchr (path, '\0');
1582
1583   while (h < end)
1584     {
1585       /* Hare should be at the beginning of a path element. */
1586
1587       if (h[0] == '.' && (h[1] == '/' || h[1] == '\0'))
1588         {
1589           /* Ignore "./". */
1590           h += 2;
1591         }
1592       else if (h[0] == '.' && h[1] == '.' && (h[2] == '/' || h[2] == '\0'))
1593         {
1594           /* Handle "../" by retreating the tortoise by one path
1595              element -- but not past beggining.  */
1596           if (t > beg)
1597             {
1598               /* Move backwards until T hits the beginning of the
1599                  previous path element or the beginning of path. */
1600               for (--t; t > beg && t[-1] != '/'; t--)
1601                 ;
1602             }
1603           else if (scheme == SCHEME_FTP)
1604             {
1605               /* If we're at the beginning, copy the "../" literally
1606                  and move the beginning so a later ".." doesn't remove
1607                  it.  This violates RFC 3986; but we do it for FTP
1608                  anyway because there is otherwise no way to get at a
1609                  parent directory, when the FTP server drops us in a
1610                  non-root directory (which is not uncommon). */
1611               beg = t + 3;
1612               goto regular;
1613             }
1614           h += 3;
1615         }
1616       else
1617         {
1618         regular:
1619           /* A regular path element.  If H hasn't advanced past T,
1620              simply skip to the next path element.  Otherwise, copy
1621              the path element until the next slash.  */
1622           if (t == h)
1623             {
1624               /* Skip the path element, including the slash.  */
1625               while (h < end && *h != '/')
1626                 t++, h++;
1627               if (h < end)
1628                 t++, h++;
1629             }
1630           else
1631             {
1632               /* Copy the path element, including the final slash.  */
1633               while (h < end && *h != '/')
1634                 *t++ = *h++;
1635               if (h < end)
1636                 *t++ = *h++;
1637             }
1638         }
1639     }
1640
1641   if (t != h)
1642     *t = '\0';
1643
1644   return t != h;
1645 }
1646 \f
1647 /* Return the length of URL's path.  Path is considered to be
1648    terminated by one or more of the ?query or ;params or #fragment,
1649    depending on the scheme.  */
1650
1651 static const char *
1652 path_end (const char *url)
1653 {
1654   enum url_scheme scheme = url_scheme (url);
1655   const char *seps;
1656   if (scheme == SCHEME_INVALID)
1657     scheme = SCHEME_HTTP;       /* use http semantics for rel links */
1658   /* +2 to ignore the first two separators ':' and '/' */
1659   seps = init_seps (scheme) + 2;
1660   return strpbrk_or_eos (url, seps);
1661 }
1662
1663 /* Find the last occurrence of character C in the range [b, e), or
1664    NULL, if none are present.  */
1665 #define find_last_char(b, e, c) memrchr ((b), (c), (e) - (b))
1666
1667 /* Merge BASE with LINK and return the resulting URI.
1668
1669    Either of the URIs may be absolute or relative, complete with the
1670    host name, or path only.  This tries to reasonably handle all
1671    foreseeable cases.  It only employs minimal URL parsing, without
1672    knowledge of the specifics of schemes.
1673
1674    I briefly considered making this function call path_simplify after
1675    the merging process, as rfc1738 seems to suggest.  This is a bad
1676    idea for several reasons: 1) it complexifies the code, and 2)
1677    url_parse has to simplify path anyway, so it's wasteful to boot.  */
1678
1679 char *
1680 uri_merge (const char *base, const char *link)
1681 {
1682   int linklength;
1683   const char *end;
1684   char *merge;
1685
1686   if (url_has_scheme (link))
1687     return xstrdup (link);
1688
1689   /* We may not examine BASE past END. */
1690   end = path_end (base);
1691   linklength = strlen (link);
1692
1693   if (!*link)
1694     {
1695       /* Empty LINK points back to BASE, query string and all. */
1696       return xstrdup (base);
1697     }
1698   else if (*link == '?')
1699     {
1700       /* LINK points to the same location, but changes the query
1701          string.  Examples: */
1702       /* uri_merge("path",         "?new") -> "path?new"     */
1703       /* uri_merge("path?foo",     "?new") -> "path?new"     */
1704       /* uri_merge("path?foo#bar", "?new") -> "path?new"     */
1705       /* uri_merge("path#foo",     "?new") -> "path?new"     */
1706       int baselength = end - base;
1707       merge = xmalloc (baselength + linklength + 1);
1708       memcpy (merge, base, baselength);
1709       memcpy (merge + baselength, link, linklength);
1710       merge[baselength + linklength] = '\0';
1711     }
1712   else if (*link == '#')
1713     {
1714       /* uri_merge("path",         "#new") -> "path#new"     */
1715       /* uri_merge("path#foo",     "#new") -> "path#new"     */
1716       /* uri_merge("path?foo",     "#new") -> "path?foo#new" */
1717       /* uri_merge("path?foo#bar", "#new") -> "path?foo#new" */
1718       int baselength;
1719       const char *end1 = strchr (base, '#');
1720       if (!end1)
1721         end1 = base + strlen (base);
1722       baselength = end1 - base;
1723       merge = xmalloc (baselength + linklength + 1);
1724       memcpy (merge, base, baselength);
1725       memcpy (merge + baselength, link, linklength);
1726       merge[baselength + linklength] = '\0';
1727     }
1728   else if (*link == '/' && *(link + 1) == '/')
1729     {
1730       /* LINK begins with "//" and so is a net path: we need to
1731          replace everything after (and including) the double slash
1732          with LINK. */
1733
1734       /* uri_merge("foo", "//new/bar")            -> "//new/bar"      */
1735       /* uri_merge("//old/foo", "//new/bar")      -> "//new/bar"      */
1736       /* uri_merge("http://old/foo", "//new/bar") -> "http://new/bar" */
1737
1738       int span;
1739       const char *slash;
1740       const char *start_insert;
1741
1742       /* Look for first slash. */
1743       slash = memchr (base, '/', end - base);
1744       /* If found slash and it is a double slash, then replace
1745          from this point, else default to replacing from the
1746          beginning.  */
1747       if (slash && *(slash + 1) == '/')
1748         start_insert = slash;
1749       else
1750         start_insert = base;
1751
1752       span = start_insert - base;
1753       merge = xmalloc (span + linklength + 1);
1754       if (span)
1755         memcpy (merge, base, span);
1756       memcpy (merge + span, link, linklength);
1757       merge[span + linklength] = '\0';
1758     }
1759   else if (*link == '/')
1760     {
1761       /* LINK is an absolute path: we need to replace everything
1762          after (and including) the FIRST slash with LINK.
1763
1764          So, if BASE is "http://host/whatever/foo/bar", and LINK is
1765          "/qux/xyzzy", our result should be
1766          "http://host/qux/xyzzy".  */
1767       int span;
1768       const char *slash;
1769       const char *start_insert = NULL; /* for gcc to shut up. */
1770       const char *pos = base;
1771       bool seen_slash_slash = false;
1772       /* We're looking for the first slash, but want to ignore
1773          double slash. */
1774     again:
1775       slash = memchr (pos, '/', end - pos);
1776       if (slash && !seen_slash_slash)
1777         if (*(slash + 1) == '/')
1778           {
1779             pos = slash + 2;
1780             seen_slash_slash = true;
1781             goto again;
1782           }
1783
1784       /* At this point, SLASH is the location of the first / after
1785          "//", or the first slash altogether.  START_INSERT is the
1786          pointer to the location where LINK will be inserted.  When
1787          examining the last two examples, keep in mind that LINK
1788          begins with '/'. */
1789
1790       if (!slash && !seen_slash_slash)
1791         /* example: "foo" */
1792         /*           ^    */
1793         start_insert = base;
1794       else if (!slash && seen_slash_slash)
1795         /* example: "http://foo" */
1796         /*                     ^ */
1797         start_insert = end;
1798       else if (slash && !seen_slash_slash)
1799         /* example: "foo/bar" */
1800         /*           ^        */
1801         start_insert = base;
1802       else if (slash && seen_slash_slash)
1803         /* example: "http://something/" */
1804         /*                           ^  */
1805         start_insert = slash;
1806
1807       span = start_insert - base;
1808       merge = xmalloc (span + linklength + 1);
1809       if (span)
1810         memcpy (merge, base, span);
1811       memcpy (merge + span, link, linklength);
1812       merge[span + linklength] = '\0';
1813     }
1814   else
1815     {
1816       /* LINK is a relative URL: we need to replace everything
1817          after last slash (possibly empty) with LINK.
1818
1819          So, if BASE is "whatever/foo/bar", and LINK is "qux/xyzzy",
1820          our result should be "whatever/foo/qux/xyzzy".  */
1821       bool need_explicit_slash = false;
1822       int span;
1823       const char *start_insert;
1824       const char *last_slash = find_last_char (base, end, '/');
1825       if (!last_slash)
1826         {
1827           /* No slash found at all.  Replace what we have with LINK. */
1828           start_insert = base;
1829         }
1830       else if (last_slash && last_slash >= base + 2
1831                && last_slash[-2] == ':' && last_slash[-1] == '/')
1832         {
1833           /* example: http://host"  */
1834           /*                      ^ */
1835           start_insert = end + 1;
1836           need_explicit_slash = true;
1837         }
1838       else
1839         {
1840           /* example: "whatever/foo/bar" */
1841           /*                        ^    */
1842           start_insert = last_slash + 1;
1843         }
1844
1845       span = start_insert - base;
1846       merge = xmalloc (span + linklength + 1);
1847       if (span)
1848         memcpy (merge, base, span);
1849       if (need_explicit_slash)
1850         merge[span - 1] = '/';
1851       memcpy (merge + span, link, linklength);
1852       merge[span + linklength] = '\0';
1853     }
1854
1855   return merge;
1856 }
1857 \f
1858 #define APPEND(p, s) do {                       \
1859   int len = strlen (s);                         \
1860   memcpy (p, s, len);                           \
1861   p += len;                                     \
1862 } while (0)
1863
1864 /* Use this instead of password when the actual password is supposed
1865    to be hidden.  We intentionally use a generic string without giving
1866    away the number of characters in the password, like previous
1867    versions did.  */
1868 #define HIDDEN_PASSWORD "*password*"
1869
1870 /* Recreate the URL string from the data in URL.
1871
1872    If HIDE is true (as it is when we're calling this on a URL we plan
1873    to print, but not when calling it to canonicalize a URL for use
1874    within the program), password will be hidden.  Unsafe characters in
1875    the URL will be quoted.  */
1876
1877 char *
1878 url_string (const struct url *url, enum url_auth_mode auth_mode)
1879 {
1880   int size;
1881   char *result, *p;
1882   char *quoted_host, *quoted_user = NULL, *quoted_passwd = NULL;
1883
1884   int scheme_port = supported_schemes[url->scheme].default_port;
1885   const char *scheme_str = supported_schemes[url->scheme].leading_string;
1886   int fplen = full_path_length (url);
1887
1888   bool brackets_around_host;
1889
1890   assert (scheme_str != NULL);
1891
1892   /* Make sure the user name and password are quoted. */
1893   if (url->user)
1894     {
1895       if (auth_mode != URL_AUTH_HIDE)
1896         {
1897           quoted_user = url_escape_allow_passthrough (url->user);
1898           if (url->passwd)
1899             {
1900               if (auth_mode == URL_AUTH_HIDE_PASSWD)
1901                 quoted_passwd = HIDDEN_PASSWORD;
1902               else
1903                 quoted_passwd = url_escape_allow_passthrough (url->passwd);
1904             }
1905         }
1906     }
1907
1908   /* In the unlikely event that the host name contains non-printable
1909      characters, quote it for displaying to the user.  */
1910   quoted_host = url_escape_allow_passthrough (url->host);
1911
1912   /* Undo the quoting of colons that URL escaping performs.  IPv6
1913      addresses may legally contain colons, and in that case must be
1914      placed in square brackets.  */
1915   if (quoted_host != url->host)
1916     unescape_single_char (quoted_host, ':');
1917   brackets_around_host = strchr (quoted_host, ':') != NULL;
1918
1919   size = (strlen (scheme_str)
1920           + strlen (quoted_host)
1921           + (brackets_around_host ? 2 : 0)
1922           + fplen
1923           + 1);
1924   if (url->port != scheme_port)
1925     size += 1 + numdigit (url->port);
1926   if (quoted_user)
1927     {
1928       size += 1 + strlen (quoted_user);
1929       if (quoted_passwd)
1930         size += 1 + strlen (quoted_passwd);
1931     }
1932
1933   p = result = xmalloc (size);
1934
1935   APPEND (p, scheme_str);
1936   if (quoted_user)
1937     {
1938       APPEND (p, quoted_user);
1939       if (quoted_passwd)
1940         {
1941           *p++ = ':';
1942           APPEND (p, quoted_passwd);
1943         }
1944       *p++ = '@';
1945     }
1946
1947   if (brackets_around_host)
1948     *p++ = '[';
1949   APPEND (p, quoted_host);
1950   if (brackets_around_host)
1951     *p++ = ']';
1952   if (url->port != scheme_port)
1953     {
1954       *p++ = ':';
1955       p = number_to_string (p, url->port);
1956     }
1957
1958   full_path_write (url, p);
1959   p += fplen;
1960   *p++ = '\0';
1961
1962   assert (p - result == size);
1963
1964   if (quoted_user && quoted_user != url->user)
1965     xfree (quoted_user);
1966   if (quoted_passwd && auth_mode == URL_AUTH_SHOW
1967       && quoted_passwd != url->passwd)
1968     xfree (quoted_passwd);
1969   if (quoted_host != url->host)
1970     xfree (quoted_host);
1971
1972   return result;
1973 }
1974 \f
1975 /* Return true if scheme a is similar to scheme b.
1976  
1977    Schemes are similar if they are equal.  If SSL is supported, schemes
1978    are also similar if one is http (SCHEME_HTTP) and the other is https
1979    (SCHEME_HTTPS).  */
1980 bool
1981 schemes_are_similar_p (enum url_scheme a, enum url_scheme b)
1982 {
1983   if (a == b)
1984     return true;
1985 #ifdef HAVE_SSL
1986   if ((a == SCHEME_HTTP && b == SCHEME_HTTPS)
1987       || (a == SCHEME_HTTPS && b == SCHEME_HTTP))
1988     return true;
1989 #endif
1990   return false;
1991 }
1992 \f
1993 static int
1994 getchar_from_escaped_string (const char *str, char *c)
1995 {  
1996   const char *p = str;
1997
1998   assert (str && *str);
1999   assert (c);
2000   
2001   if (p[0] == '%')
2002     {
2003       if (!c_isxdigit(p[1]) || !c_isxdigit(p[2]))
2004         {
2005           *c = '%';
2006           return 1;
2007         }
2008       else
2009         {
2010           if (p[2] == 0)
2011             return 0; /* error: invalid string */
2012
2013           *c = X2DIGITS_TO_NUM (p[1], p[2]);
2014           if (URL_RESERVED_CHAR(*c))
2015             {
2016               *c = '%';
2017               return 1;
2018             }
2019           else
2020             return 3;
2021         }
2022     }
2023   else
2024     {
2025       *c = p[0];
2026     }
2027
2028   return 1;
2029 }
2030
2031 bool
2032 are_urls_equal (const char *u1, const char *u2)
2033 {
2034   const char *p, *q;
2035   int pp, qq;
2036   char ch1, ch2;
2037   assert(u1 && u2);
2038
2039   p = u1;
2040   q = u2;
2041
2042   while (*p && *q
2043          && (pp = getchar_from_escaped_string (p, &ch1))
2044          && (qq = getchar_from_escaped_string (q, &ch2))
2045          && (c_tolower(ch1) == c_tolower(ch2)))
2046     {
2047       p += pp;
2048       q += qq;
2049     }
2050   
2051   return (*p == 0 && *q == 0 ? true : false);
2052 }
2053 \f
2054 #ifdef TESTING
2055 /* Debugging and testing support for path_simplify. */
2056
2057 #if 0
2058 /* Debug: run path_simplify on PATH and return the result in a new
2059    string.  Useful for calling from the debugger.  */
2060 static char *
2061 ps (char *path)
2062 {
2063   char *copy = xstrdup (path);
2064   path_simplify (copy);
2065   return copy;
2066 }
2067 #endif
2068
2069 static const char *
2070 run_test (char *test, char *expected_result, enum url_scheme scheme,
2071           bool expected_change)
2072 {
2073   char *test_copy = xstrdup (test);
2074   bool modified = path_simplify (scheme, test_copy);
2075
2076   if (0 != strcmp (test_copy, expected_result))
2077     {
2078       printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
2079               test, expected_result, test_copy);
2080       mu_assert ("", 0);
2081     }
2082   if (modified != expected_change)
2083     {
2084       if (expected_change)
2085         printf ("Expected modification with path_simplify(\"%s\").\n",
2086                 test);
2087       else
2088         printf ("Expected no modification with path_simplify(\"%s\").\n",
2089                 test);
2090     }
2091   xfree (test_copy);
2092   mu_assert ("", modified == expected_change);
2093   return NULL;
2094 }
2095
2096 const char *
2097 test_path_simplify (void)
2098 {
2099   static struct {
2100     char *test, *result;
2101     enum url_scheme scheme;
2102     bool should_modify;
2103   } tests[] = {
2104     { "",                       "",             SCHEME_HTTP, false },
2105     { ".",                      "",             SCHEME_HTTP, true },
2106     { "./",                     "",             SCHEME_HTTP, true },
2107     { "..",                     "",             SCHEME_HTTP, true },
2108     { "../",                    "",             SCHEME_HTTP, true },
2109     { "..",                     "..",           SCHEME_FTP,  false },
2110     { "../",                    "../",          SCHEME_FTP,  false },
2111     { "foo",                    "foo",          SCHEME_HTTP, false },
2112     { "foo/bar",                "foo/bar",      SCHEME_HTTP, false },
2113     { "foo///bar",              "foo///bar",    SCHEME_HTTP, false },
2114     { "foo/.",                  "foo/",         SCHEME_HTTP, true },
2115     { "foo/./",                 "foo/",         SCHEME_HTTP, true },
2116     { "foo./",                  "foo./",        SCHEME_HTTP, false },
2117     { "foo/../bar",             "bar",          SCHEME_HTTP, true },
2118     { "foo/../bar/",            "bar/",         SCHEME_HTTP, true },
2119     { "foo/bar/..",             "foo/",         SCHEME_HTTP, true },
2120     { "foo/bar/../x",           "foo/x",        SCHEME_HTTP, true },
2121     { "foo/bar/../x/",          "foo/x/",       SCHEME_HTTP, true },
2122     { "foo/..",                 "",             SCHEME_HTTP, true },
2123     { "foo/../..",              "",             SCHEME_HTTP, true },
2124     { "foo/../../..",           "",             SCHEME_HTTP, true },
2125     { "foo/../../bar/../../baz", "baz",         SCHEME_HTTP, true },
2126     { "foo/../..",              "..",           SCHEME_FTP,  true },
2127     { "foo/../../..",           "../..",        SCHEME_FTP,  true },
2128     { "foo/../../bar/../../baz", "../../baz",   SCHEME_FTP,  true },
2129     { "a/b/../../c",            "c",            SCHEME_HTTP, true },
2130     { "./a/../b",               "b",            SCHEME_HTTP, true }
2131   };
2132   int i;
2133
2134   for (i = 0; i < countof (tests); i++)
2135     {
2136       const char *message;
2137       char *test = tests[i].test;
2138       char *expected_result = tests[i].result;
2139       enum url_scheme scheme = tests[i].scheme;
2140       bool  expected_change = tests[i].should_modify;
2141       message = run_test (test, expected_result, scheme, expected_change);
2142       if (message) return message;
2143     }
2144   return NULL;
2145 }
2146
2147 const char *
2148 test_append_uri_pathel()
2149 {
2150   int i;
2151   struct {
2152     char *original_url;
2153     char *input;
2154     bool escaped;
2155     char *expected_result;
2156   } test_array[] = {
2157     { "http://www.yoyodyne.com/path/", "somepage.html", false, "http://www.yoyodyne.com/path/somepage.html" },
2158   };
2159   
2160   for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i) 
2161     {
2162       struct growable dest;
2163       const char *p = test_array[i].input;
2164       
2165       memset (&dest, 0, sizeof (dest));
2166       
2167       append_string (test_array[i].original_url, &dest);
2168       append_uri_pathel (p, p + strlen(p), test_array[i].escaped, &dest);
2169       append_char ('\0', &dest);
2170
2171       mu_assert ("test_append_uri_pathel: wrong result", 
2172                  strcmp (dest.base, test_array[i].expected_result) == 0);
2173     }
2174
2175   return NULL;
2176 }
2177
2178 const char*
2179 test_are_urls_equal()
2180 {
2181   int i;
2182   struct {
2183     char *url1;
2184     char *url2;
2185     bool expected_result;
2186   } test_array[] = {
2187     { "http://www.adomain.com/apath/", "http://www.adomain.com/apath/",       true },
2188     { "http://www.adomain.com/apath/", "http://www.adomain.com/anotherpath/", false },
2189     { "http://www.adomain.com/apath/", "http://www.anotherdomain.com/path/",  false },
2190     { "http://www.adomain.com/~path/", "http://www.adomain.com/%7epath/",     true },
2191     { "http://www.adomain.com/longer-path/", "http://www.adomain.com/path/",  false },
2192     { "http://www.adomain.com/path%2f", "http://www.adomain.com/path/",       false },
2193   };
2194   
2195   for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i) 
2196     {
2197       mu_assert ("test_are_urls_equal: wrong result", 
2198                  are_urls_equal (test_array[i].url1, test_array[i].url2) == test_array[i].expected_result);
2199     }
2200
2201   return NULL;
2202 }
2203
2204 #endif /* TESTING */
2205
2206 /*
2207  * vim: et ts=2 sw=2
2208  */
2209