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