]> sjero.net Git - wget/blob - src/url.c
[svn] Allow all hex digits in IPv6 IP addresses.
[wget] / src / url.c
1 /* URL handling.
2    Copyright (C) 1995, 1996, 1997, 2000, 2001 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #else
27 # include <strings.h>
28 #endif
29 #include <sys/types.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33 #include <errno.h>
34 #include <assert.h>
35
36 #include "wget.h"
37 #include "utils.h"
38 #include "url.h"
39 #include "host.h"
40 #include "hash.h"
41
42 #ifndef errno
43 extern int errno;
44 #endif
45
46 /* Is X "."?  */
47 #define DOTP(x) ((*(x) == '.') && (!*(x + 1)))
48 /* Is X ".."?  */
49 #define DDOTP(x) ((*(x) == '.') && (*(x + 1) == '.') && (!*(x + 2)))
50
51 struct scheme_data
52 {
53   char *leading_string;
54   int default_port;
55   int enabled;
56 };
57
58 /* Supported schemes: */
59 static struct scheme_data supported_schemes[] =
60 {
61   { "http://",  DEFAULT_HTTP_PORT,  1 },
62 #ifdef HAVE_SSL
63   { "https://", DEFAULT_HTTPS_PORT, 1 },
64 #endif
65   { "ftp://",   DEFAULT_FTP_PORT,   1 },
66
67   /* SCHEME_INVALID */
68   { NULL,       -1,                 0 }
69 };
70
71 /* Forward declarations: */
72
73 static char *construct_relative PARAMS ((const char *, const char *));
74 static int path_simplify PARAMS ((char *));
75
76
77 \f
78 /* Support for encoding and decoding of URL strings.  We determine
79    whether a character is unsafe through static table lookup.  This
80    code assumes ASCII character set and 8-bit chars.  */
81
82 enum {
83   urlchr_reserved = 1,
84   urlchr_unsafe   = 2
85 };
86
87 #define R  urlchr_reserved
88 #define U  urlchr_unsafe
89 #define RU R|U
90
91 #define urlchr_test(c, mask) (urlchr_table[(unsigned char)(c)] & (mask))
92
93 /* rfc1738 reserved chars, preserved from encoding.  */
94
95 #define RESERVED_CHAR(c) urlchr_test(c, urlchr_reserved)
96
97 /* rfc1738 unsafe chars, plus some more.  */
98
99 #define UNSAFE_CHAR(c) urlchr_test(c, urlchr_unsafe)
100
101 const static unsigned char urlchr_table[256] =
102 {
103   U,  U,  U,  U,   U,  U,  U,  U,   /* NUL SOH STX ETX  EOT ENQ ACK BEL */
104   U,  U,  U,  U,   U,  U,  U,  U,   /* BS  HT  LF  VT   FF  CR  SO  SI  */
105   U,  U,  U,  U,   U,  U,  U,  U,   /* DLE DC1 DC2 DC3  DC4 NAK SYN ETB */
106   U,  U,  U,  U,   U,  U,  U,  U,   /* CAN EM  SUB ESC  FS  GS  RS  US  */
107   U,  0,  U, RU,   0,  U,  R,  0,   /* SP  !   "   #    $   %   &   '   */
108   0,  0,  0,  R,   0,  0,  0,  R,   /* (   )   *   +    ,   -   .   /   */
109   0,  0,  0,  0,   0,  0,  0,  0,   /* 0   1   2   3    4   5   6   7   */
110   0,  0, RU,  R,   U,  R,  U,  R,   /* 8   9   :   ;    <   =   >   ?   */
111  RU,  0,  0,  0,   0,  0,  0,  0,   /* @   A   B   C    D   E   F   G   */
112   0,  0,  0,  0,   0,  0,  0,  0,   /* H   I   J   K    L   M   N   O   */
113   0,  0,  0,  0,   0,  0,  0,  0,   /* P   Q   R   S    T   U   V   W   */
114   0,  0,  0, RU,   U, RU,  U,  0,   /* X   Y   Z   [    \   ]   ^   _   */
115   U,  0,  0,  0,   0,  0,  0,  0,   /* `   a   b   c    d   e   f   g   */
116   0,  0,  0,  0,   0,  0,  0,  0,   /* h   i   j   k    l   m   n   o   */
117   0,  0,  0,  0,   0,  0,  0,  0,   /* p   q   r   s    t   u   v   w   */
118   0,  0,  0,  U,   U,  U,  U,  U,   /* x   y   z   {    |   }   ~   DEL */
119
120   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
121   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
122   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
123   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
124
125   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
126   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
127   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
128   U, U, U, U,  U, U, U, U,  U, U, U, U,  U, U, U, U,
129 };
130
131 /* Decodes the forms %xy in a URL to the character the hexadecimal
132    code of which is xy.  xy are hexadecimal digits from
133    [0123456789ABCDEF] (case-insensitive).  If x or y are not
134    hex-digits or `%' precedes `\0', the sequence is inserted
135    literally.  */
136
137 static void
138 decode_string (char *s)
139 {
140   char *t = s;                  /* t - tortoise */
141   char *h = s;                  /* h - hare     */
142
143   for (; *h; h++, t++)
144     {
145       if (*h != '%')
146         {
147         copychar:
148           *t = *h;
149         }
150       else
151         {
152           /* Do nothing if '%' is not followed by two hex digits. */
153           if (!*(h + 1) || !*(h + 2)
154               || !(ISXDIGIT (*(h + 1)) && ISXDIGIT (*(h + 2))))
155             goto copychar;
156           *t = (XCHAR_TO_XDIGIT (*(h + 1)) << 4) + XCHAR_TO_XDIGIT (*(h + 2));
157           h += 2;
158         }
159     }
160   *t = '\0';
161 }
162
163 /* Like encode_string, but return S if there are no unsafe chars.  */
164
165 static char *
166 encode_string_maybe (const char *s)
167 {
168   const char *p1;
169   char *p2, *newstr;
170   int newlen;
171   int addition = 0;
172
173   for (p1 = s; *p1; p1++)
174     if (UNSAFE_CHAR (*p1))
175       addition += 2;            /* Two more characters (hex digits) */
176
177   if (!addition)
178     return (char *)s;
179
180   newlen = (p1 - s) + addition;
181   newstr = (char *)xmalloc (newlen + 1);
182
183   p1 = s;
184   p2 = newstr;
185   while (*p1)
186     {
187       if (UNSAFE_CHAR (*p1))
188         {
189           unsigned char c = *p1++;
190           *p2++ = '%';
191           *p2++ = XDIGIT_TO_XCHAR (c >> 4);
192           *p2++ = XDIGIT_TO_XCHAR (c & 0xf);
193         }
194       else
195         *p2++ = *p1++;
196     }
197   *p2 = '\0';
198   assert (p2 - newstr == newlen);
199
200   return newstr;
201 }
202
203 /* Encode the unsafe characters (as determined by UNSAFE_CHAR) in a
204    given string, returning a malloc-ed %XX encoded string.  */
205   
206 char *
207 encode_string (const char *s)
208 {
209   char *encoded = encode_string_maybe (s);
210   if (encoded != s)
211     return encoded;
212   else
213     return xstrdup (s);
214 }
215
216 /* Encode unsafe characters in PTR to %xx.  If such encoding is done,
217    the old value of PTR is freed and PTR is made to point to the newly
218    allocated storage.  */
219
220 #define ENCODE(ptr) do {                        \
221   char *e_new = encode_string_maybe (ptr);      \
222   if (e_new != ptr)                             \
223     {                                           \
224       xfree (ptr);                              \
225       ptr = e_new;                              \
226     }                                           \
227 } while (0)
228 \f
229 enum copy_method { CM_DECODE, CM_ENCODE, CM_PASSTHROUGH };
230
231 /* Decide whether to encode, decode, or pass through the char at P.
232    This used to be a macro, but it got a little too convoluted.  */
233 static inline enum copy_method
234 decide_copy_method (const char *p)
235 {
236   if (*p == '%')
237     {
238       if (ISXDIGIT (*(p + 1)) && ISXDIGIT (*(p + 2)))
239         {
240           /* %xx sequence: decode it, unless it would decode to an
241              unsafe or a reserved char; in that case, leave it as
242              is. */
243           char preempt = (XCHAR_TO_XDIGIT (*(p + 1)) << 4) +
244             XCHAR_TO_XDIGIT (*(p + 2));
245
246           if (UNSAFE_CHAR (preempt) || RESERVED_CHAR (preempt))
247             return CM_PASSTHROUGH;
248           else
249             return CM_DECODE;
250         }
251       else
252         /* Garbled %.. sequence: encode `%'. */
253         return CM_ENCODE;
254     }
255   else if (UNSAFE_CHAR (*p) && !RESERVED_CHAR (*p))
256     return CM_ENCODE;
257   else
258     return CM_PASSTHROUGH;
259 }
260
261 /* Translate a %-quoting (but possibly non-conformant) input string S
262    into a %-quoting (and conformant) output string.  If no characters
263    are encoded or decoded, return the same string S; otherwise, return
264    a freshly allocated string with the new contents.
265
266    After a URL has been run through this function, the protocols that
267    use `%' as the quote character can use the resulting string as-is,
268    while those that don't call decode_string() to get to the intended
269    data.  This function is also stable: after an input string is
270    transformed the first time, all further transformations of the
271    result yield the same result string.
272
273    Let's discuss why this function is needed.
274
275    Imagine Wget is to retrieve `http://abc.xyz/abc def'.  Since a raw
276    space character would mess up the HTTP request, it needs to be
277    quoted, like this:
278
279        GET /abc%20def HTTP/1.0
280
281    So it appears that the unsafe chars need to be quoted, as with
282    encode_string.  But what if we're requested to download
283    `abc%20def'?  Remember that %-encoding is valid URL syntax, so what
284    the user meant was a literal space, and he was kind enough to quote
285    it.  In that case, Wget should obviously leave the `%20' as is, and
286    send the same request as above.  So in this case we may not call
287    encode_string.
288
289    But what if the requested URI is `abc%20 def'?  If we call
290    encode_string, we end up with `/abc%2520%20def', which is almost
291    certainly not intended.  If we don't call encode_string, we are
292    left with the embedded space and cannot send the request.  What the
293    user meant was for Wget to request `/abc%20%20def', and this is
294    where reencode_string kicks in.
295
296    Wget used to solve this by first decoding %-quotes, and then
297    encoding all the "unsafe" characters found in the resulting string.
298    This was wrong because it didn't preserve certain URL special
299    (reserved) characters.  For instance, URI containing "a%2B+b" (0x2b
300    == '+') would get translated to "a%2B%2Bb" or "a++b" depending on
301    whether we considered `+' reserved (it is).  One of these results
302    is inevitable because by the second step we would lose information
303    on whether the `+' was originally encoded or not.  Both results
304    were wrong because in CGI parameters + means space, while %2B means
305    literal plus.  reencode_string correctly translates the above to
306    "a%2B+b", i.e. returns the original string.
307
308    This function uses an algorithm proposed by Anon Sricharoenchai:
309
310    1. Encode all URL_UNSAFE and the "%" that are not followed by 2
311       hexdigits.
312
313    2. Decode all "%XX" except URL_UNSAFE, URL_RESERVED (";/?:@=&") and
314       "+".
315
316    ...except that this code conflates the two steps, and decides
317    whether to encode, decode, or pass through each character in turn.
318    The function still uses two passes, but their logic is the same --
319    the first pass exists merely for the sake of allocation.  Another
320    small difference is that we include `+' to URL_RESERVED.
321
322    Anon's test case:
323
324    "http://abc.xyz/%20%3F%%36%31%25aa% a?a=%61+a%2Ba&b=b%26c%3Dc"
325    ->
326    "http://abc.xyz/%20%3F%2561%25aa%25%20a?a=a+a%2Ba&b=b%26c%3Dc"
327
328    Simpler test cases:
329
330    "foo bar"         -> "foo%20bar"
331    "foo%20bar"       -> "foo%20bar"
332    "foo %20bar"      -> "foo%20%20bar"
333    "foo%%20bar"      -> "foo%25%20bar"       (0x25 == '%')
334    "foo%25%20bar"    -> "foo%25%20bar"
335    "foo%2%20bar"     -> "foo%252%20bar"
336    "foo+bar"         -> "foo+bar"            (plus is reserved!)
337    "foo%2b+bar"      -> "foo%2b+bar"  */
338
339 static char *
340 reencode_string (const char *s)
341 {
342   const char *p1;
343   char *newstr, *p2;
344   int oldlen, newlen;
345
346   int encode_count = 0;
347   int decode_count = 0;
348
349   /* First, pass through the string to see if there's anything to do,
350      and to calculate the new length.  */
351   for (p1 = s; *p1; p1++)
352     {
353       switch (decide_copy_method (p1))
354         {
355         case CM_ENCODE:
356           ++encode_count;
357           break;
358         case CM_DECODE:
359           ++decode_count;
360           break;
361         case CM_PASSTHROUGH:
362           break;
363         }
364     }
365
366   if (!encode_count && !decode_count)
367     /* The string is good as it is. */
368     return (char *)s;           /* C const model sucks. */
369
370   oldlen = p1 - s;
371   /* Each encoding adds two characters (hex digits), while each
372      decoding removes two characters.  */
373   newlen = oldlen + 2 * (encode_count - decode_count);
374   newstr = xmalloc (newlen + 1);
375
376   p1 = s;
377   p2 = newstr;
378
379   while (*p1)
380     {
381       switch (decide_copy_method (p1))
382         {
383         case CM_ENCODE:
384           {
385             unsigned char c = *p1++;
386             *p2++ = '%';
387             *p2++ = XDIGIT_TO_XCHAR (c >> 4);
388             *p2++ = XDIGIT_TO_XCHAR (c & 0xf);
389           }
390           break;
391         case CM_DECODE:
392           *p2++ = ((XCHAR_TO_XDIGIT (*(p1 + 1)) << 4)
393                    + (XCHAR_TO_XDIGIT (*(p1 + 2))));
394           p1 += 3;              /* skip %xx */
395           break;
396         case CM_PASSTHROUGH:
397           *p2++ = *p1++;
398         }
399     }
400   *p2 = '\0';
401   assert (p2 - newstr == newlen);
402   return newstr;
403 }
404
405 /* Run PTR_VAR through reencode_string.  If a new string is consed,
406    free PTR_VAR and make it point to the new storage.  Obviously,
407    PTR_VAR needs to be an lvalue.  */
408
409 #define REENCODE(ptr_var) do {                  \
410   char *rf_new = reencode_string (ptr_var);     \
411   if (rf_new != ptr_var)                        \
412     {                                           \
413       xfree (ptr_var);                          \
414       ptr_var = rf_new;                         \
415     }                                           \
416 } while (0)
417 \f
418 /* Returns the scheme type if the scheme is supported, or
419    SCHEME_INVALID if not.  */
420 enum url_scheme
421 url_scheme (const char *url)
422 {
423   int i;
424
425   for (i = 0; supported_schemes[i].leading_string; i++)
426     if (0 == strncasecmp (url, supported_schemes[i].leading_string,
427                           strlen (supported_schemes[i].leading_string)))
428       {
429         if (supported_schemes[i].enabled)
430           return (enum url_scheme) i;
431         else
432           return SCHEME_INVALID;
433       }
434
435   return SCHEME_INVALID;
436 }
437
438 /* Return the number of characters needed to skip the scheme part of
439    the URL, e.g. `http://'.  If no scheme is found, returns 0.  */
440 int
441 url_skip_scheme (const char *url)
442 {
443   const char *p = url;
444
445   /* Skip the scheme name.  We allow `-' and `+' because of `whois++',
446      etc. */
447   while (ISALNUM (*p) || *p == '-' || *p == '+')
448     ++p;
449   if (*p != ':')
450     return 0;
451   /* Skip ':'. */
452   ++p;
453
454   /* Skip "//" if found. */
455   if (*p == '/' && *(p + 1) == '/')
456     p += 2;
457
458   return p - url;
459 }
460
461 /* Returns 1 if the URL begins with a scheme (supported or
462    unsupported), 0 otherwise.  */
463 int
464 url_has_scheme (const char *url)
465 {
466   const char *p = url;
467   while (ISALNUM (*p) || *p == '-' || *p == '+')
468     ++p;
469   return *p == ':';
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].enabled = 0;
482 }
483
484 /* Skip the username and password, if present here.  The function
485    should be called *not* with the complete URL, but with the part
486    right after the scheme.
487
488    If no username and password are found, return 0.  */
489 int
490 url_skip_uname (const char *url)
491 {
492   const char *p;
493
494   /* Look for '@' that comes before '/' or '?'. */
495   p = (const char *)strpbrk (url, "/?@");
496   if (!p || *p != '@')
497     return 0;
498
499   return p - url + 1;
500 }
501
502 static int
503 parse_uname (const char *str, int len, char **user, char **passwd)
504 {
505   char *colon;
506
507   if (len == 0)
508     /* Empty user name not allowed. */
509     return 0;
510
511   colon = memchr (str, ':', len);
512   if (colon == str)
513     /* Empty user name again. */
514     return 0;
515
516   if (colon)
517     {
518       int pwlen = len - (colon + 1 - str);
519       *passwd = xmalloc (pwlen + 1);
520       memcpy (*passwd, colon + 1, pwlen);
521       (*passwd)[pwlen] = '\0';
522       len -= pwlen + 1;
523     }
524   else
525     *passwd = NULL;
526
527   *user = xmalloc (len + 1);
528   memcpy (*user, str, len);
529   (*user)[len] = '\0';
530
531   if (*user)
532     decode_string (*user);
533   if (*passwd)
534     decode_string (*passwd);
535
536   return 1;
537 }
538
539 /* Used by main.c: detect URLs written using the "shorthand" URL forms
540    popularized by Netscape and NcFTP.  HTTP shorthands look like this:
541
542    www.foo.com[:port]/dir/file   -> http://www.foo.com[:port]/dir/file
543    www.foo.com[:port]            -> http://www.foo.com[:port]
544
545    FTP shorthands look like this:
546
547    foo.bar.com:dir/file          -> ftp://foo.bar.com/dir/file
548    foo.bar.com:/absdir/file      -> ftp://foo.bar.com//absdir/file
549
550    If the URL needs not or cannot be rewritten, return NULL.  */
551 char *
552 rewrite_shorthand_url (const char *url)
553 {
554   const char *p;
555
556   if (url_has_scheme (url))
557     return NULL;
558
559   /* Look for a ':' or '/'.  The former signifies NcFTP syntax, the
560      latter Netscape.  */
561   for (p = url; *p && *p != ':' && *p != '/'; p++)
562     ;
563
564   if (p == url)
565     return NULL;
566
567   if (*p == ':')
568     {
569       const char *pp;
570       char *res;
571       /* If the characters after the colon and before the next slash
572          or end of string are all digits, it's HTTP.  */
573       int digits = 0;
574       for (pp = p + 1; ISDIGIT (*pp); pp++)
575         ++digits;
576       if (digits > 0 && (*pp == '/' || *pp == '\0'))
577         goto http;
578
579       /* Prepend "ftp://" to the entire URL... */
580       res = xmalloc (6 + strlen (url) + 1);
581       sprintf (res, "ftp://%s", url);
582       /* ...and replace ':' with '/'. */
583       res[6 + (p - url)] = '/';
584       return res;
585     }
586   else
587     {
588       char *res;
589     http:
590       /* Just prepend "http://" to what we have. */
591       res = xmalloc (7 + strlen (url) + 1);
592       sprintf (res, "http://%s", url);
593       return res;
594     }
595 }
596 \f
597 static void parse_path PARAMS ((const char *, char **, char **));
598
599 static char *
600 strpbrk_or_eos (const char *s, const char *accept)
601 {
602   char *p = strpbrk (s, accept);
603   if (!p)
604     p = (char *)s + strlen (s);
605   return p;
606 }
607
608 /* Turn STR into lowercase; return non-zero if a character was
609    actually changed. */
610
611 static int
612 lowercase_str (char *str)
613 {
614   int change = 0;
615   for (; *str; str++)
616     if (ISUPPER (*str))
617       {
618         change = 1;
619         *str = TOLOWER (*str);
620       }
621   return change;
622 }
623
624 static char *parse_errors[] = {
625 #define PE_NO_ERROR                     0
626   "No error",
627 #define PE_UNSUPPORTED_SCHEME           1
628   "Unsupported scheme",
629 #define PE_EMPTY_HOST                   2
630   "Empty host",
631 #define PE_BAD_PORT_NUMBER              3
632   "Bad port number",
633 #define PE_INVALID_USER_NAME            4
634   "Invalid user name",
635 #define PE_UNTERMINATED_IPV6_ADDRESS    5
636   "Unterminated IPv6 numeric address",
637 #define PE_INVALID_IPV6_ADDRESS         6
638   "Invalid char in IPv6 numeric address"
639 };
640
641 #define SETERR(p, v) do {                       \
642   if (p)                                        \
643     *(p) = (v);                                 \
644 } while (0)
645
646 /* Parse a URL.
647
648    Return a new struct url if successful, NULL on error.  In case of
649    error, and if ERROR is not NULL, also set *ERROR to the appropriate
650    error code. */
651 struct url *
652 url_parse (const char *url, int *error)
653 {
654   struct url *u;
655   const char *p;
656   int path_modified, host_modified;
657
658   enum url_scheme scheme;
659
660   const char *uname_b,     *uname_e;
661   const char *host_b,      *host_e;
662   const char *path_b,      *path_e;
663   const char *params_b,    *params_e;
664   const char *query_b,     *query_e;
665   const char *fragment_b,  *fragment_e;
666
667   int port;
668   char *user = NULL, *passwd = NULL;
669
670   char *url_encoded;
671
672   scheme = url_scheme (url);
673   if (scheme == SCHEME_INVALID)
674     {
675       SETERR (error, PE_UNSUPPORTED_SCHEME);
676       return NULL;
677     }
678
679   url_encoded = reencode_string (url);
680   p = url_encoded;
681
682   p += strlen (supported_schemes[scheme].leading_string);
683   uname_b = p;
684   p += url_skip_uname (p);
685   uname_e = p;
686
687   /* scheme://user:pass@host[:port]... */
688   /*                    ^              */
689
690   /* We attempt to break down the URL into the components path,
691      params, query, and fragment.  They are ordered like this:
692
693        scheme://host[:port][/path][;params][?query][#fragment]  */
694
695   params_b   = params_e   = NULL;
696   query_b    = query_e    = NULL;
697   fragment_b = fragment_e = NULL;
698
699   host_b = p;
700
701   if (*p == '[')
702     {
703       /* Support http://[::1]/ used by IPv6. */
704       int invalid = 0;
705       ++p;
706       while (1)
707         {
708           char c = *p++;
709           switch (c)
710             {
711             case ']':
712               goto out;
713             case '\0':
714               SETERR (error, PE_UNTERMINATED_IPV6_ADDRESS);
715               return NULL;
716             case ':': case '.':
717               break;
718             default:
719               if (ISXDIGIT (c))
720                 break;
721               invalid = 1;
722             }
723         }
724     out:
725       if (invalid)
726         {
727           SETERR (error, PE_INVALID_IPV6_ADDRESS);
728           return NULL;
729         }
730       /* Don't include brackets in [host_b, host_p). */
731       ++host_b;
732       host_e = p - 1;
733     }
734   else
735     {
736       p = strpbrk_or_eos (p, ":/;?#");
737       host_e = p;
738     }
739
740   if (host_b == host_e)
741     {
742       SETERR (error, PE_EMPTY_HOST);
743       return NULL;
744     }
745
746   port = scheme_default_port (scheme);
747   if (*p == ':')
748     {
749       const char *port_b, *port_e, *pp;
750
751       /* scheme://host:port/tralala */
752       /*              ^             */
753       ++p;
754       port_b = p;
755       p = strpbrk_or_eos (p, "/;?#");
756       port_e = p;
757
758       if (port_b == port_e)
759         {
760           /* http://host:/whatever */
761           /*             ^         */
762           SETERR (error, PE_BAD_PORT_NUMBER);
763           return NULL;
764         }
765
766       for (port = 0, pp = port_b; pp < port_e; pp++)
767         {
768           if (!ISDIGIT (*pp))
769             {
770               /* http://host:12randomgarbage/blah */
771               /*               ^                  */
772               SETERR (error, PE_BAD_PORT_NUMBER);
773               return NULL;
774             }
775           port = 10 * port + (*pp - '0');
776         }
777     }
778
779   if (*p == '/')
780     {
781       ++p;
782       path_b = p;
783       p = strpbrk_or_eos (p, ";?#");
784       path_e = p;
785     }
786   else
787     {
788       /* Path is not allowed not to exist. */
789       path_b = path_e = p;
790     }
791
792   if (*p == ';')
793     {
794       ++p;
795       params_b = p;
796       p = strpbrk_or_eos (p, "?#");
797       params_e = p;
798     }
799   if (*p == '?')
800     {
801       ++p;
802       query_b = p;
803       p = strpbrk_or_eos (p, "#");
804       query_e = p;
805     }
806   if (*p == '#')
807     {
808       ++p;
809       fragment_b = p;
810       p += strlen (p);
811       fragment_e = p;
812     }
813   assert (*p == 0);
814
815   if (uname_b != uname_e)
816     {
817       /* http://user:pass@host */
818       /*        ^         ^    */
819       /*     uname_b   uname_e */
820       if (!parse_uname (uname_b, uname_e - uname_b - 1, &user, &passwd))
821         {
822           SETERR (error, PE_INVALID_USER_NAME);
823           return NULL;
824         }
825     }
826
827   u = (struct url *)xmalloc (sizeof (struct url));
828   memset (u, 0, sizeof (*u));
829
830   u->scheme = scheme;
831   u->host   = strdupdelim (host_b, host_e);
832   u->port   = port;
833   u->user   = user;
834   u->passwd = passwd;
835
836   u->path = strdupdelim (path_b, path_e);
837   path_modified = path_simplify (u->path);
838   parse_path (u->path, &u->dir, &u->file);
839
840   host_modified = lowercase_str (u->host);
841
842   if (params_b)
843     u->params = strdupdelim (params_b, params_e);
844   if (query_b)
845     u->query = strdupdelim (query_b, query_e);
846   if (fragment_b)
847     u->fragment = strdupdelim (fragment_b, fragment_e);
848
849   if (path_modified || u->fragment || host_modified || path_b == path_e)
850     {
851       /* If we suspect that a transformation has rendered what
852          url_string might return different from URL_ENCODED, rebuild
853          u->url using url_string.  */
854       u->url = url_string (u, 0);
855
856       if (url_encoded != url)
857         xfree ((char *) url_encoded);
858     }
859   else
860     {
861       if (url_encoded == url)
862         u->url    = xstrdup (url);
863       else
864         u->url    = url_encoded;
865     }
866   url_encoded = NULL;
867
868   return u;
869 }
870
871 const char *
872 url_error (int error_code)
873 {
874   assert (error_code >= 0 && error_code < ARRAY_SIZE (parse_errors));
875   return parse_errors[error_code];
876 }
877
878 static void
879 parse_path (const char *quoted_path, char **dir, char **file)
880 {
881   char *path, *last_slash;
882
883   STRDUP_ALLOCA (path, quoted_path);
884   decode_string (path);
885
886   last_slash = strrchr (path, '/');
887   if (!last_slash)
888     {
889       *dir = xstrdup ("");
890       *file = xstrdup (path);
891     }
892   else
893     {
894       *dir = strdupdelim (path, last_slash);
895       *file = xstrdup (last_slash + 1);
896     }
897 }
898
899 /* Note: URL's "full path" is the path with the query string and
900    params appended.  The "fragment" (#foo) is intentionally ignored,
901    but that might be changed.  For example, if the original URL was
902    "http://host:port/foo/bar/baz;bullshit?querystring#uselessfragment",
903    the full path will be "/foo/bar/baz;bullshit?querystring".  */
904
905 /* Return the length of the full path, without the terminating
906    zero.  */
907
908 static int
909 full_path_length (const struct url *url)
910 {
911   int len = 0;
912
913 #define FROB(el) if (url->el) len += 1 + strlen (url->el)
914
915   FROB (path);
916   FROB (params);
917   FROB (query);
918
919 #undef FROB
920
921   return len;
922 }
923
924 /* Write out the full path. */
925
926 static void
927 full_path_write (const struct url *url, char *where)
928 {
929 #define FROB(el, chr) do {                      \
930   char *f_el = url->el;                         \
931   if (f_el) {                                   \
932     int l = strlen (f_el);                      \
933     *where++ = chr;                             \
934     memcpy (where, f_el, l);                    \
935     where += l;                                 \
936   }                                             \
937 } while (0)
938
939   FROB (path, '/');
940   FROB (params, ';');
941   FROB (query, '?');
942
943 #undef FROB
944 }
945
946 /* Public function for getting the "full path".  E.g. if u->path is
947    "foo/bar" and u->query is "param=value", full_path will be
948    "/foo/bar?param=value". */
949
950 char *
951 url_full_path (const struct url *url)
952 {
953   int length = full_path_length (url);
954   char *full_path = (char *)xmalloc(length + 1);
955
956   full_path_write (url, full_path);
957   full_path[length] = '\0';
958
959   return full_path;
960 }
961
962 /* Sync u->path and u->url with u->dir and u->file. */
963
964 static void
965 sync_path (struct url *url)
966 {
967   char *newpath;
968
969   xfree (url->path);
970
971   if (!*url->dir)
972     {
973       newpath = xstrdup (url->file);
974       REENCODE (newpath);
975     }
976   else
977     {
978       int dirlen = strlen (url->dir);
979       int filelen = strlen (url->file);
980
981       newpath = xmalloc (dirlen + 1 + filelen + 1);
982       memcpy (newpath, url->dir, dirlen);
983       newpath[dirlen] = '/';
984       memcpy (newpath + dirlen + 1, url->file, filelen);
985       newpath[dirlen + 1 + filelen] = '\0';
986       REENCODE (newpath);
987     }
988
989   url->path = newpath;
990
991   /* Synchronize u->url. */
992   xfree (url->url);
993   url->url = url_string (url, 0);
994 }
995
996 /* Mutators.  Code in ftp.c insists on changing u->dir and u->file.
997    This way we can sync u->path and u->url when they get changed.  */
998
999 void
1000 url_set_dir (struct url *url, const char *newdir)
1001 {
1002   xfree (url->dir);
1003   url->dir = xstrdup (newdir);
1004   sync_path (url);
1005 }
1006
1007 void
1008 url_set_file (struct url *url, const char *newfile)
1009 {
1010   xfree (url->file);
1011   url->file = xstrdup (newfile);
1012   sync_path (url);
1013 }
1014
1015 void
1016 url_free (struct url *url)
1017 {
1018   xfree (url->host);
1019   xfree (url->path);
1020   xfree (url->url);
1021
1022   FREE_MAYBE (url->params);
1023   FREE_MAYBE (url->query);
1024   FREE_MAYBE (url->fragment);
1025   FREE_MAYBE (url->user);
1026   FREE_MAYBE (url->passwd);
1027
1028   xfree (url->dir);
1029   xfree (url->file);
1030
1031   xfree (url);
1032 }
1033 \f
1034 struct urlpos *
1035 get_urls_file (const char *file)
1036 {
1037   struct file_memory *fm;
1038   struct urlpos *head, *tail;
1039   const char *text, *text_end;
1040
1041   /* Load the file.  */
1042   fm = read_file (file);
1043   if (!fm)
1044     {
1045       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1046       return NULL;
1047     }
1048   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
1049
1050   head = tail = NULL;
1051   text = fm->content;
1052   text_end = fm->content + fm->length;
1053   while (text < text_end)
1054     {
1055       const char *line_beg = text;
1056       const char *line_end = memchr (text, '\n', text_end - text);
1057       if (!line_end)
1058         line_end = text_end;
1059       else
1060         ++line_end;
1061       text = line_end;
1062
1063       /* Strip whitespace from the beginning and end of line. */
1064       while (line_beg < line_end && ISSPACE (*line_beg))
1065         ++line_beg;
1066       while (line_end > line_beg && ISSPACE (*(line_end - 1)))
1067         --line_end;
1068
1069       if (line_end > line_beg)
1070         {
1071           /* URL is in the [line_beg, line_end) region. */
1072
1073           int up_error_code;
1074           char *url_text;
1075           struct urlpos *entry;
1076           struct url *url;
1077
1078           /* We must copy the URL to a zero-terminated string, and we
1079              can't use alloca because we're in a loop.  *sigh*.  */
1080           url_text = strdupdelim (line_beg, line_end);
1081
1082           if (opt.base_href)
1083             {
1084               /* Merge opt.base_href with URL. */
1085               char *merged = uri_merge (opt.base_href, url_text);
1086               xfree (url_text);
1087               url_text = merged;
1088             }
1089
1090           url = url_parse (url_text, &up_error_code);
1091           if (!url)
1092             {
1093               logprintf (LOG_NOTQUIET, "%s: Invalid URL %s: %s\n",
1094                          file, url_text, url_error (up_error_code));
1095               xfree (url_text);
1096               continue;
1097             }
1098           xfree (url_text);
1099
1100           entry = (struct urlpos *)xmalloc (sizeof (struct urlpos));
1101           memset (entry, 0, sizeof (*entry));
1102           entry->next = NULL;
1103           entry->url = url;
1104
1105           if (!head)
1106             head = entry;
1107           else
1108             tail->next = entry;
1109           tail = entry;
1110         }
1111     }
1112   read_file_free (fm);
1113   return head;
1114 }
1115 \f
1116 /* Free the linked list of urlpos.  */
1117 void
1118 free_urlpos (struct urlpos *l)
1119 {
1120   while (l)
1121     {
1122       struct urlpos *next = l->next;
1123       if (l->url)
1124         url_free (l->url);
1125       FREE_MAYBE (l->local_name);
1126       xfree (l);
1127       l = next;
1128     }
1129 }
1130
1131 /* Rotate FNAME opt.backups times */
1132 void
1133 rotate_backups(const char *fname)
1134 {
1135   int maxlen = strlen (fname) + 1 + numdigit (opt.backups) + 1;
1136   char *from = (char *)alloca (maxlen);
1137   char *to = (char *)alloca (maxlen);
1138   struct stat sb;
1139   int i;
1140
1141   if (stat (fname, &sb) == 0)
1142     if (S_ISREG (sb.st_mode) == 0)
1143       return;
1144
1145   for (i = opt.backups; i > 1; i--)
1146     {
1147       sprintf (from, "%s.%d", fname, i - 1);
1148       sprintf (to, "%s.%d", fname, i);
1149       /* #### This will fail on machines without the rename() system
1150          call.  */
1151       rename (from, to);
1152     }
1153
1154   sprintf (to, "%s.%d", fname, 1);
1155   rename(fname, to);
1156 }
1157
1158 /* Create all the necessary directories for PATH (a file).  Calls
1159    mkdirhier() internally.  */
1160 int
1161 mkalldirs (const char *path)
1162 {
1163   const char *p;
1164   char *t;
1165   struct stat st;
1166   int res;
1167
1168   p = path + strlen (path);
1169   for (; *p != '/' && p != path; p--);
1170   /* Don't create if it's just a file.  */
1171   if ((p == path) && (*p != '/'))
1172     return 0;
1173   t = strdupdelim (path, p);
1174   /* Check whether the directory exists.  */
1175   if ((stat (t, &st) == 0))
1176     {
1177       if (S_ISDIR (st.st_mode))
1178         {
1179           xfree (t);
1180           return 0;
1181         }
1182       else
1183         {
1184           /* If the dir exists as a file name, remove it first.  This
1185              is *only* for Wget to work with buggy old CERN http
1186              servers.  Here is the scenario: When Wget tries to
1187              retrieve a directory without a slash, e.g.
1188              http://foo/bar (bar being a directory), CERN server will
1189              not redirect it too http://foo/bar/ -- it will generate a
1190              directory listing containing links to bar/file1,
1191              bar/file2, etc.  Wget will lose because it saves this
1192              HTML listing to a file `bar', so it cannot create the
1193              directory.  To work around this, if the file of the same
1194              name exists, we just remove it and create the directory
1195              anyway.  */
1196           DEBUGP (("Removing %s because of directory danger!\n", t));
1197           unlink (t);
1198         }
1199     }
1200   res = make_directory (t);
1201   if (res != 0)
1202     logprintf (LOG_NOTQUIET, "%s: %s", t, strerror (errno));
1203   xfree (t);
1204   return res;
1205 }
1206
1207 static int
1208 count_slashes (const char *s)
1209 {
1210   int i = 0;
1211   while (*s)
1212     if (*s++ == '/')
1213       ++i;
1214   return i;
1215 }
1216
1217 /* Return the path name of the URL-equivalent file name, with a
1218    remote-like structure of directories.  */
1219 static char *
1220 mkstruct (const struct url *u)
1221 {
1222   char *dir, *dir_preencoding;
1223   char *file, *res, *dirpref;
1224   char *query = u->query && *u->query ? u->query : NULL;
1225   int l;
1226
1227   if (opt.cut_dirs)
1228     {
1229       char *ptr = u->dir + (*u->dir == '/');
1230       int slash_count = 1 + count_slashes (ptr);
1231       int cut = MINVAL (opt.cut_dirs, slash_count);
1232       for (; cut && *ptr; ptr++)
1233         if (*ptr == '/')
1234           --cut;
1235       STRDUP_ALLOCA (dir, ptr);
1236     }
1237   else
1238     dir = u->dir + (*u->dir == '/');
1239
1240   /* Check for the true name (or at least a consistent name for saving
1241      to directory) of HOST, reusing the hlist if possible.  */
1242   if (opt.add_hostdir)
1243     {
1244       /* Add dir_prefix and hostname (if required) to the beginning of
1245          dir.  */
1246       dirpref = (char *)alloca (strlen (opt.dir_prefix) + 1
1247                                 + strlen (u->host)
1248                                 + 1 + numdigit (u->port)
1249                                 + 1);
1250       if (!DOTP (opt.dir_prefix))
1251         sprintf (dirpref, "%s/%s", opt.dir_prefix, u->host);
1252       else
1253         strcpy (dirpref, u->host);
1254
1255       if (u->port != scheme_default_port (u->scheme))
1256         {
1257           int len = strlen (dirpref);
1258           dirpref[len] = ':';
1259           number_to_string (dirpref + len + 1, u->port);
1260         }
1261     }
1262   else                          /* not add_hostdir */
1263     {
1264       if (!DOTP (opt.dir_prefix))
1265         dirpref = opt.dir_prefix;
1266       else
1267         dirpref = "";
1268     }
1269
1270   /* If there is a prefix, prepend it.  */
1271   if (*dirpref)
1272     {
1273       char *newdir = (char *)alloca (strlen (dirpref) + 1 + strlen (dir) + 2);
1274       sprintf (newdir, "%s%s%s", dirpref, *dir == '/' ? "" : "/", dir);
1275       dir = newdir;
1276     }
1277
1278   dir_preencoding = dir;
1279   dir = reencode_string (dir_preencoding);
1280
1281   l = strlen (dir);
1282   if (l && dir[l - 1] == '/')
1283     dir[l - 1] = '\0';
1284
1285   if (!*u->file)
1286     file = "index.html";
1287   else
1288     file = u->file;
1289
1290   /* Finally, construct the full name.  */
1291   res = (char *)xmalloc (strlen (dir) + 1 + strlen (file)
1292                          + (query ? (1 + strlen (query)) : 0)
1293                          + 1);
1294   sprintf (res, "%s%s%s", dir, *dir ? "/" : "", file);
1295   if (query)
1296     {
1297       strcat (res, "?");
1298       strcat (res, query);
1299     }
1300   if (dir != dir_preencoding)
1301     xfree (dir);
1302   return res;
1303 }
1304
1305 /* Compose a file name out of BASE, an unescaped file name, and QUERY,
1306    an escaped query string.  The trick is to make sure that unsafe
1307    characters in BASE are escaped, and that slashes in QUERY are also
1308    escaped.  */
1309
1310 static char *
1311 compose_file_name (char *base, char *query)
1312 {
1313   char result[256];
1314   char *from;
1315   char *to = result;
1316
1317   /* Copy BASE to RESULT and encode all unsafe characters.  */
1318   from = base;
1319   while (*from && to - result < sizeof (result))
1320     {
1321       if (UNSAFE_CHAR (*from))
1322         {
1323           unsigned char c = *from++;
1324           *to++ = '%';
1325           *to++ = XDIGIT_TO_XCHAR (c >> 4);
1326           *to++ = XDIGIT_TO_XCHAR (c & 0xf);
1327         }
1328       else
1329         *to++ = *from++;
1330     }
1331
1332   if (query && to - result < sizeof (result))
1333     {
1334       *to++ = '?';
1335
1336       /* Copy QUERY to RESULT and encode all '/' characters. */
1337       from = query;
1338       while (*from && to - result < sizeof (result))
1339         {
1340           if (*from == '/')
1341             {
1342               *to++ = '%';
1343               *to++ = '2';
1344               *to++ = 'F';
1345               ++from;
1346             }
1347           else
1348             *to++ = *from++;
1349         }
1350     }
1351
1352   if (to - result < sizeof (result))
1353     *to = '\0';
1354   else
1355     /* Truncate input which is too long, presumably due to a huge
1356        query string.  */
1357     result[sizeof (result) - 1] = '\0';
1358
1359   return xstrdup (result);
1360 }
1361
1362 /* Create a unique filename, corresponding to a given URL.  Calls
1363    mkstruct if necessary.  Does *not* actually create any directories.  */
1364 char *
1365 url_filename (const struct url *u)
1366 {
1367   char *file, *name;
1368   int have_prefix = 0;          /* whether we must prepend opt.dir_prefix */
1369
1370   if (opt.dirstruct)
1371     {
1372       file = mkstruct (u);
1373       have_prefix = 1;
1374     }
1375   else
1376     {
1377       char *base = *u->file ? u->file : "index.html";
1378       char *query = u->query && *u->query ? u->query : NULL;
1379       file = compose_file_name (base, query);
1380     }
1381
1382   if (!have_prefix)
1383     {
1384       /* Check whether the prefix directory is something other than "."
1385          before prepending it.  */
1386       if (!DOTP (opt.dir_prefix))
1387         {
1388           char *nfile = (char *)xmalloc (strlen (opt.dir_prefix)
1389                                          + 1 + strlen (file) + 1);
1390           sprintf (nfile, "%s/%s", opt.dir_prefix, file);
1391           xfree (file);
1392           file = nfile;
1393         }
1394     }
1395   /* DOS-ish file systems don't like `%' signs in them; we change it
1396      to `@'.  */
1397 #ifdef WINDOWS
1398   {
1399     char *p = file;
1400     for (p = file; *p; p++)
1401       if (*p == '%')
1402         *p = '@';
1403   }
1404 #endif /* WINDOWS */
1405
1406   /* Check the cases in which the unique extensions are not used:
1407      1) Clobbering is turned off (-nc).
1408      2) Retrieval with regetting.
1409      3) Timestamping is used.
1410      4) Hierarchy is built.
1411
1412      The exception is the case when file does exist and is a
1413      directory (actually support for bad httpd-s).  */
1414   if ((opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct)
1415       && !(file_exists_p (file) && !file_non_directory_p (file)))
1416     return file;
1417
1418   /* Find a unique name.  */
1419   name = unique_name (file);
1420   xfree (file);
1421   return name;
1422 }
1423
1424 /* Return the langth of URL's path.  Path is considered to be
1425    terminated by one of '?', ';', '#', or by the end of the
1426    string.  */
1427 static int
1428 path_length (const char *url)
1429 {
1430   const char *q = strpbrk_or_eos (url, "?;#");
1431   return q - url;
1432 }
1433
1434 /* Find the last occurrence of character C in the range [b, e), or
1435    NULL, if none are present.  This is equivalent to strrchr(b, c),
1436    except that it accepts an END argument instead of requiring the
1437    string to be zero-terminated.  Why is there no memrchr()?  */
1438 static const char *
1439 find_last_char (const char *b, const char *e, char c)
1440 {
1441   for (; e > b; e--)
1442     if (*e == c)
1443       return e;
1444   return NULL;
1445 }
1446 \f
1447 /* Resolve "." and ".." elements of PATH by destructively modifying
1448    PATH.  "." is resolved by removing that path element, and ".." is
1449    resolved by removing the preceding path element.  Leading and
1450    trailing slashes are preserved.
1451
1452    Return non-zero if any changes have been made.
1453
1454    For example, "a/b/c/./../d/.." will yield "a/b/".  More exhaustive
1455    test examples are provided below.  If you change anything in this
1456    function, run test_path_simplify to make sure you haven't broken a
1457    test case.
1458
1459    A previous version of this function was based on path_simplify()
1460    from GNU Bash, but it has been rewritten for Wget 1.8.1.  */
1461
1462 static int
1463 path_simplify (char *path)
1464 {
1465   int change = 0;
1466   char *p, *end;
1467
1468   if (path[0] == '/')
1469     ++path;                     /* preserve the leading '/'. */
1470
1471   p = path;
1472   end = p + strlen (p) + 1;     /* position past the terminating zero. */
1473
1474   while (1)
1475     {
1476     again:
1477       /* P should point to the beginning of a path element. */
1478
1479       if (*p == '.' && (*(p + 1) == '/' || *(p + 1) == '\0'))
1480         {
1481           /* Handle "./foo" by moving "foo" two characters to the
1482              left. */
1483           if (*(p + 1) == '/')
1484             {
1485               change = 1;
1486               memmove (p, p + 2, end - p);
1487               end -= 2;
1488               goto again;
1489             }
1490           else
1491             {
1492               change = 1;
1493               *p = '\0';
1494               break;
1495             }
1496         }
1497       else if (*p == '.' && *(p + 1) == '.'
1498                && (*(p + 2) == '/' || *(p + 2) == '\0'))
1499         {
1500           /* Handle "../foo" by moving "foo" one path element to the
1501              left.  */
1502           char *b = p;          /* not p-1 because P can equal PATH */
1503
1504           /* Backtrack by one path element, but not past the beginning
1505              of PATH. */
1506
1507           /* foo/bar/../baz */
1508           /*         ^ p    */
1509           /*     ^ b        */
1510
1511           if (b > path)
1512             {
1513               /* Move backwards until B hits the beginning of the
1514                  previous path element or the beginning of path. */
1515               for (--b; b > path && *(b - 1) != '/'; b--)
1516                 ;
1517             }
1518
1519           change = 1;
1520           if (*(p + 2) == '/')
1521             {
1522               memmove (b, p + 3, end - (p + 3));
1523               end -= (p + 3) - b;
1524               p = b;
1525             }
1526           else
1527             {
1528               *b = '\0';
1529               break;
1530             }
1531
1532           goto again;
1533         }
1534       else if (*p == '/')
1535         {
1536           /* Remove empty path elements.  Not mandated by rfc1808 et
1537              al, but empty path elements are not all that useful, and
1538              the rest of Wget might not deal with them well. */
1539           char *q = p;
1540           while (*q == '/')
1541             ++q;
1542           change = 1;
1543           if (*q == '\0')
1544             {
1545               *p = '\0';
1546               break;
1547             }
1548           memmove (p, q, end - q);
1549           end -= q - p;
1550           goto again;
1551         }
1552
1553       /* Skip to the next path element. */
1554       while (*p && *p != '/')
1555         ++p;
1556       if (*p == '\0')
1557         break;
1558
1559       /* Make sure P points to the beginning of the next path element,
1560          which is location after the slash. */
1561       ++p;
1562     }
1563
1564   return change;
1565 }
1566 \f
1567 /* Resolve the result of "linking" a base URI (BASE) to a
1568    link-specified URI (LINK).
1569
1570    Either of the URIs may be absolute or relative, complete with the
1571    host name, or path only.  This tries to behave "reasonably" in all
1572    foreseeable cases.  It employs little specific knowledge about
1573    schemes or URL-specific stuff -- it just works on strings.
1574
1575    The parameters LINKLENGTH is useful if LINK is not zero-terminated.
1576    See uri_merge for a gentler interface to this functionality.
1577
1578    Perhaps this function should call path_simplify so that the callers
1579    don't have to call url_parse unconditionally.  */
1580 static char *
1581 uri_merge_1 (const char *base, const char *link, int linklength, int no_scheme)
1582 {
1583   char *constr;
1584
1585   if (no_scheme)
1586     {
1587       const char *end = base + path_length (base);
1588
1589       if (!*link)
1590         {
1591           /* Empty LINK points back to BASE, query string and all. */
1592           constr = xstrdup (base);
1593         }
1594       else if (*link == '?')
1595         {
1596           /* LINK points to the same location, but changes the query
1597              string.  Examples: */
1598           /* uri_merge("path",         "?new") -> "path?new"     */
1599           /* uri_merge("path?foo",     "?new") -> "path?new"     */
1600           /* uri_merge("path?foo#bar", "?new") -> "path?new"     */
1601           /* uri_merge("path#foo",     "?new") -> "path?new"     */
1602           int baselength = end - base;
1603           constr = xmalloc (baselength + linklength + 1);
1604           memcpy (constr, base, baselength);
1605           memcpy (constr + baselength, link, linklength);
1606           constr[baselength + linklength] = '\0';
1607         }
1608       else if (*link == '#')
1609         {
1610           /* uri_merge("path",         "#new") -> "path#new"     */
1611           /* uri_merge("path#foo",     "#new") -> "path#new"     */
1612           /* uri_merge("path?foo",     "#new") -> "path?foo#new" */
1613           /* uri_merge("path?foo#bar", "#new") -> "path?foo#new" */
1614           int baselength;
1615           const char *end1 = strchr (base, '#');
1616           if (!end1)
1617             end1 = base + strlen (base);
1618           baselength = end1 - base;
1619           constr = xmalloc (baselength + linklength + 1);
1620           memcpy (constr, base, baselength);
1621           memcpy (constr + baselength, link, linklength);
1622           constr[baselength + linklength] = '\0';
1623         }
1624       else if (linklength > 1 && *link == '/' && *(link + 1) == '/')
1625         {
1626           /* LINK begins with "//" and so is a net path: we need to
1627              replace everything after (and including) the double slash
1628              with LINK. */
1629
1630           /* uri_merge("foo", "//new/bar")            -> "//new/bar"      */
1631           /* uri_merge("//old/foo", "//new/bar")      -> "//new/bar"      */
1632           /* uri_merge("http://old/foo", "//new/bar") -> "http://new/bar" */
1633
1634           int span;
1635           const char *slash;
1636           const char *start_insert;
1637
1638           /* Look for first slash. */
1639           slash = memchr (base, '/', end - base);
1640           /* If found slash and it is a double slash, then replace
1641              from this point, else default to replacing from the
1642              beginning.  */
1643           if (slash && *(slash + 1) == '/')
1644             start_insert = slash;
1645           else
1646             start_insert = base;
1647
1648           span = start_insert - base;
1649           constr = (char *)xmalloc (span + linklength + 1);
1650           if (span)
1651             memcpy (constr, base, span);
1652           memcpy (constr + span, link, linklength);
1653           constr[span + linklength] = '\0';
1654         }
1655       else if (*link == '/')
1656         {
1657           /* LINK is an absolute path: we need to replace everything
1658              after (and including) the FIRST slash with LINK.
1659
1660              So, if BASE is "http://host/whatever/foo/bar", and LINK is
1661              "/qux/xyzzy", our result should be
1662              "http://host/qux/xyzzy".  */
1663           int span;
1664           const char *slash;
1665           const char *start_insert = NULL; /* for gcc to shut up. */
1666           const char *pos = base;
1667           int seen_slash_slash = 0;
1668           /* We're looking for the first slash, but want to ignore
1669              double slash. */
1670         again:
1671           slash = memchr (pos, '/', end - pos);
1672           if (slash && !seen_slash_slash)
1673             if (*(slash + 1) == '/')
1674               {
1675                 pos = slash + 2;
1676                 seen_slash_slash = 1;
1677                 goto again;
1678               }
1679
1680           /* At this point, SLASH is the location of the first / after
1681              "//", or the first slash altogether.  START_INSERT is the
1682              pointer to the location where LINK will be inserted.  When
1683              examining the last two examples, keep in mind that LINK
1684              begins with '/'. */
1685
1686           if (!slash && !seen_slash_slash)
1687             /* example: "foo" */
1688             /*           ^    */
1689             start_insert = base;
1690           else if (!slash && seen_slash_slash)
1691             /* example: "http://foo" */
1692             /*                     ^ */
1693             start_insert = end;
1694           else if (slash && !seen_slash_slash)
1695             /* example: "foo/bar" */
1696             /*           ^        */
1697             start_insert = base;
1698           else if (slash && seen_slash_slash)
1699             /* example: "http://something/" */
1700             /*                           ^  */
1701             start_insert = slash;
1702
1703           span = start_insert - base;
1704           constr = (char *)xmalloc (span + linklength + 1);
1705           if (span)
1706             memcpy (constr, base, span);
1707           if (linklength)
1708             memcpy (constr + span, link, linklength);
1709           constr[span + linklength] = '\0';
1710         }
1711       else
1712         {
1713           /* LINK is a relative URL: we need to replace everything
1714              after last slash (possibly empty) with LINK.
1715
1716              So, if BASE is "whatever/foo/bar", and LINK is "qux/xyzzy",
1717              our result should be "whatever/foo/qux/xyzzy".  */
1718           int need_explicit_slash = 0;
1719           int span;
1720           const char *start_insert;
1721           const char *last_slash = find_last_char (base, end, '/');
1722           if (!last_slash)
1723             {
1724               /* No slash found at all.  Append LINK to what we have,
1725                  but we'll need a slash as a separator.
1726
1727                  Example: if base == "foo" and link == "qux/xyzzy", then
1728                  we cannot just append link to base, because we'd get
1729                  "fooqux/xyzzy", whereas what we want is
1730                  "foo/qux/xyzzy".
1731
1732                  To make sure the / gets inserted, we set
1733                  need_explicit_slash to 1.  We also set start_insert
1734                  to end + 1, so that the length calculations work out
1735                  correctly for one more (slash) character.  Accessing
1736                  that character is fine, since it will be the
1737                  delimiter, '\0' or '?'.  */
1738               /* example: "foo?..." */
1739               /*               ^    ('?' gets changed to '/') */
1740               start_insert = end + 1;
1741               need_explicit_slash = 1;
1742             }
1743           else if (last_slash && last_slash != base && *(last_slash - 1) == '/')
1744             {
1745               /* example: http://host"  */
1746               /*                      ^ */
1747               start_insert = end + 1;
1748               need_explicit_slash = 1;
1749             }
1750           else
1751             {
1752               /* example: "whatever/foo/bar" */
1753               /*                        ^    */
1754               start_insert = last_slash + 1;
1755             }
1756
1757           span = start_insert - base;
1758           constr = (char *)xmalloc (span + linklength + 1);
1759           if (span)
1760             memcpy (constr, base, span);
1761           if (need_explicit_slash)
1762             constr[span - 1] = '/';
1763           if (linklength)
1764             memcpy (constr + span, link, linklength);
1765           constr[span + linklength] = '\0';
1766         }
1767     }
1768   else /* !no_scheme */
1769     {
1770       constr = strdupdelim (link, link + linklength);
1771     }
1772   return constr;
1773 }
1774
1775 /* Merge BASE with LINK and return the resulting URI.  This is an
1776    interface to uri_merge_1 that assumes that LINK is a
1777    zero-terminated string.  */
1778 char *
1779 uri_merge (const char *base, const char *link)
1780 {
1781   return uri_merge_1 (base, link, strlen (link), !url_has_scheme (link));
1782 }
1783 \f
1784 #define APPEND(p, s) do {                       \
1785   int len = strlen (s);                         \
1786   memcpy (p, s, len);                           \
1787   p += len;                                     \
1788 } while (0)
1789
1790 /* Use this instead of password when the actual password is supposed
1791    to be hidden.  We intentionally use a generic string without giving
1792    away the number of characters in the password, like previous
1793    versions did.  */
1794 #define HIDDEN_PASSWORD "*password*"
1795
1796 /* Recreate the URL string from the data in URL.
1797
1798    If HIDE is non-zero (as it is when we're calling this on a URL we
1799    plan to print, but not when calling it to canonicalize a URL for
1800    use within the program), password will be hidden.  Unsafe
1801    characters in the URL will be quoted.  */
1802
1803 char *
1804 url_string (const struct url *url, int hide_password)
1805 {
1806   int size;
1807   char *result, *p;
1808   char *quoted_user = NULL, *quoted_passwd = NULL;
1809
1810   int scheme_port  = supported_schemes[url->scheme].default_port;
1811   char *scheme_str = supported_schemes[url->scheme].leading_string;
1812   int fplen = full_path_length (url);
1813
1814   int brackets_around_host = 0;
1815
1816   assert (scheme_str != NULL);
1817
1818   /* Make sure the user name and password are quoted. */
1819   if (url->user)
1820     {
1821       quoted_user = encode_string_maybe (url->user);
1822       if (url->passwd)
1823         {
1824           if (hide_password)
1825             quoted_passwd = HIDDEN_PASSWORD;
1826           else
1827             quoted_passwd = encode_string_maybe (url->passwd);
1828         }
1829     }
1830
1831   if (strchr (url->host, ':'))
1832     brackets_around_host = 1;
1833
1834   size = (strlen (scheme_str)
1835           + strlen (url->host)
1836           + (brackets_around_host ? 2 : 0)
1837           + fplen
1838           + 1);
1839   if (url->port != scheme_port)
1840     size += 1 + numdigit (url->port);
1841   if (quoted_user)
1842     {
1843       size += 1 + strlen (quoted_user);
1844       if (quoted_passwd)
1845         size += 1 + strlen (quoted_passwd);
1846     }
1847
1848   p = result = xmalloc (size);
1849
1850   APPEND (p, scheme_str);
1851   if (quoted_user)
1852     {
1853       APPEND (p, quoted_user);
1854       if (quoted_passwd)
1855         {
1856           *p++ = ':';
1857           APPEND (p, quoted_passwd);
1858         }
1859       *p++ = '@';
1860     }
1861
1862   if (brackets_around_host)
1863     *p++ = '[';
1864   APPEND (p, url->host);
1865   if (brackets_around_host)
1866     *p++ = ']';
1867   if (url->port != scheme_port)
1868     {
1869       *p++ = ':';
1870       p = number_to_string (p, url->port);
1871     }
1872
1873   full_path_write (url, p);
1874   p += fplen;
1875   *p++ = '\0';
1876
1877   assert (p - result == size);
1878
1879   if (quoted_user && quoted_user != url->user)
1880     xfree (quoted_user);
1881   if (quoted_passwd && !hide_password
1882       && quoted_passwd != url->passwd)
1883     xfree (quoted_passwd);
1884
1885   return result;
1886 }
1887 \f
1888 /* Returns proxy host address, in accordance with SCHEME.  */
1889 char *
1890 getproxy (enum url_scheme scheme)
1891 {
1892   char *proxy = NULL;
1893   char *rewritten_url;
1894   static char rewritten_storage[1024];
1895
1896   switch (scheme)
1897     {
1898     case SCHEME_HTTP:
1899       proxy = opt.http_proxy ? opt.http_proxy : getenv ("http_proxy");
1900       break;
1901 #ifdef HAVE_SSL
1902     case SCHEME_HTTPS:
1903       proxy = opt.https_proxy ? opt.https_proxy : getenv ("https_proxy");
1904       break;
1905 #endif
1906     case SCHEME_FTP:
1907       proxy = opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftp_proxy");
1908       break;
1909     case SCHEME_INVALID:
1910       break;
1911     }
1912   if (!proxy || !*proxy)
1913     return NULL;
1914
1915   /* Handle shorthands. */
1916   rewritten_url = rewrite_shorthand_url (proxy);
1917   if (rewritten_url)
1918     {
1919       strncpy (rewritten_storage, rewritten_url, sizeof(rewritten_storage));
1920       rewritten_storage[sizeof (rewritten_storage) - 1] = '\0';
1921       proxy = rewritten_storage;
1922     }
1923
1924   return proxy;
1925 }
1926
1927 /* Should a host be accessed through proxy, concerning no_proxy?  */
1928 int
1929 no_proxy_match (const char *host, const char **no_proxy)
1930 {
1931   if (!no_proxy)
1932     return 1;
1933   else
1934     return !sufmatch (no_proxy, host);
1935 }
1936 \f
1937 /* Support for converting links for local viewing in downloaded HTML
1938    files.  This should be moved to another file, because it has
1939    nothing to do with processing URLs.  */
1940
1941 static void write_backup_file PARAMS ((const char *, downloaded_file_t));
1942 static const char *replace_attr PARAMS ((const char *, int, FILE *,
1943                                          const char *));
1944 static const char *replace_attr_refresh_hack PARAMS ((const char *, int, FILE *,
1945                                                       const char *, int));
1946 static char *local_quote_string PARAMS ((const char *));
1947
1948 /* Change the links in one HTML file.  LINKS is a list of links in the
1949    document, along with their positions and the desired direction of
1950    the conversion.  */
1951 void
1952 convert_links (const char *file, struct urlpos *links)
1953 {
1954   struct file_memory *fm;
1955   FILE *fp;
1956   const char *p;
1957   downloaded_file_t downloaded_file_return;
1958
1959   struct urlpos *link;
1960   int to_url_count = 0, to_file_count = 0;
1961
1962   logprintf (LOG_VERBOSE, _("Converting %s... "), file);
1963
1964   {
1965     /* First we do a "dry run": go through the list L and see whether
1966        any URL needs to be converted in the first place.  If not, just
1967        leave the file alone.  */
1968     int dry_count = 0;
1969     struct urlpos *dry = links;
1970     for (dry = links; dry; dry = dry->next)
1971       if (dry->convert != CO_NOCONVERT)
1972         ++dry_count;
1973     if (!dry_count)
1974       {
1975         logputs (LOG_VERBOSE, _("nothing to do.\n"));
1976         return;
1977       }
1978   }
1979
1980   fm = read_file (file);
1981   if (!fm)
1982     {
1983       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1984                  file, strerror (errno));
1985       return;
1986     }
1987
1988   downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
1989   if (opt.backup_converted && downloaded_file_return)
1990     write_backup_file (file, downloaded_file_return);
1991
1992   /* Before opening the file for writing, unlink the file.  This is
1993      important if the data in FM is mmaped.  In such case, nulling the
1994      file, which is what fopen() below does, would make us read all
1995      zeroes from the mmaped region.  */
1996   if (unlink (file) < 0 && errno != ENOENT)
1997     {
1998       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
1999                  file, strerror (errno));
2000       read_file_free (fm);
2001       return;
2002     }
2003   /* Now open the file for writing.  */
2004   fp = fopen (file, "wb");
2005   if (!fp)
2006     {
2007       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
2008                  file, strerror (errno));
2009       read_file_free (fm);
2010       return;
2011     }
2012
2013   /* Here we loop through all the URLs in file, replacing those of
2014      them that are downloaded with relative references.  */
2015   p = fm->content;
2016   for (link = links; link; link = link->next)
2017     {
2018       char *url_start = fm->content + link->pos;
2019
2020       if (link->pos >= fm->length)
2021         {
2022           DEBUGP (("Something strange is going on.  Please investigate."));
2023           break;
2024         }
2025       /* If the URL is not to be converted, skip it.  */
2026       if (link->convert == CO_NOCONVERT)
2027         {
2028           DEBUGP (("Skipping %s at position %d.\n", link->url->url, link->pos));
2029           continue;
2030         }
2031
2032       /* Echo the file contents, up to the offending URL's opening
2033          quote, to the outfile.  */
2034       fwrite (p, 1, url_start - p, fp);
2035       p = url_start;
2036
2037       switch (link->convert)
2038         {
2039         case CO_CONVERT_TO_RELATIVE:
2040           /* Convert absolute URL to relative. */
2041           {
2042             char *newname = construct_relative (file, link->local_name);
2043             char *quoted_newname = local_quote_string (newname);
2044
2045             if (!link->link_refresh_p)
2046               p = replace_attr (p, link->size, fp, quoted_newname);
2047             else
2048               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newname,
2049                                              link->refresh_timeout);
2050
2051             DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
2052                      link->url->url, newname, link->pos, file));
2053             xfree (newname);
2054             xfree (quoted_newname);
2055             ++to_file_count;
2056             break;
2057           }
2058         case CO_CONVERT_TO_COMPLETE:
2059           /* Convert the link to absolute URL. */
2060           {
2061             char *newlink = link->url->url;
2062             char *quoted_newlink = html_quote_string (newlink);
2063
2064             if (!link->link_refresh_p)
2065               p = replace_attr (p, link->size, fp, quoted_newlink);
2066             else
2067               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newlink,
2068                                              link->refresh_timeout);
2069
2070             DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
2071                      newlink, link->pos, file));
2072             xfree (quoted_newlink);
2073             ++to_url_count;
2074             break;
2075           }
2076         case CO_NULLIFY_BASE:
2077           /* Change the base href to "". */
2078           p = replace_attr (p, link->size, fp, "");
2079           break;
2080         case CO_NOCONVERT:
2081           abort ();
2082           break;
2083         }
2084     }
2085
2086   /* Output the rest of the file. */
2087   if (p - fm->content < fm->length)
2088     fwrite (p, 1, fm->length - (p - fm->content), fp);
2089   fclose (fp);
2090   read_file_free (fm);
2091
2092   logprintf (LOG_VERBOSE, "%d-%d\n", to_file_count, to_url_count);
2093 }
2094
2095 /* Construct and return a malloced copy of the relative link from two
2096    pieces of information: local name S1 of the referring file and
2097    local name S2 of the referred file.
2098
2099    So, if S1 is "jagor.srce.hr/index.html" and S2 is
2100    "jagor.srce.hr/images/news.gif", the function will return
2101    "images/news.gif".
2102
2103    Alternately, if S1 is "fly.cc.fer.hr/ioccc/index.html", and S2 is
2104    "fly.cc.fer.hr/images/fly.gif", the function will return
2105    "../images/fly.gif".
2106
2107    Caveats: S1 should not begin with `/', unless S2 also begins with
2108    '/'.  S1 should not contain things like ".." and such --
2109    construct_relative ("fly/ioccc/../index.html",
2110    "fly/images/fly.gif") will fail.  (A workaround is to call
2111    something like path_simplify() on S1).  */
2112 static char *
2113 construct_relative (const char *s1, const char *s2)
2114 {
2115   int i, cnt, sepdirs1;
2116   char *res;
2117
2118   if (*s2 == '/')
2119     return xstrdup (s2);
2120   /* S1 should *not* be absolute, if S2 wasn't.  */
2121   assert (*s1 != '/');
2122   i = cnt = 0;
2123   /* Skip the directories common to both strings.  */
2124   while (1)
2125     {
2126       while (s1[i] && s2[i]
2127              && (s1[i] == s2[i])
2128              && (s1[i] != '/')
2129              && (s2[i] != '/'))
2130         ++i;
2131       if (s1[i] == '/' && s2[i] == '/')
2132         cnt = ++i;
2133       else
2134         break;
2135     }
2136   for (sepdirs1 = 0; s1[i]; i++)
2137     if (s1[i] == '/')
2138       ++sepdirs1;
2139   /* Now, construct the file as of:
2140      - ../ repeated sepdirs1 time
2141      - all the non-mutual directories of S2.  */
2142   res = (char *)xmalloc (3 * sepdirs1 + strlen (s2 + cnt) + 1);
2143   for (i = 0; i < sepdirs1; i++)
2144     memcpy (res + 3 * i, "../", 3);
2145   strcpy (res + 3 * i, s2 + cnt);
2146   return res;
2147 }
2148 \f
2149 static void
2150 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
2151 {
2152   /* Rather than just writing over the original .html file with the
2153      converted version, save the former to *.orig.  Note we only do
2154      this for files we've _successfully_ downloaded, so we don't
2155      clobber .orig files sitting around from previous invocations. */
2156
2157   /* Construct the backup filename as the original name plus ".orig". */
2158   size_t         filename_len = strlen(file);
2159   char*          filename_plus_orig_suffix;
2160   boolean        already_wrote_backup_file = FALSE;
2161   slist*         converted_file_ptr;
2162   static slist*  converted_files = NULL;
2163
2164   if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
2165     {
2166       /* Just write "orig" over "html".  We need to do it this way
2167          because when we're checking to see if we've downloaded the
2168          file before (to see if we can skip downloading it), we don't
2169          know if it's a text/html file.  Therefore we don't know yet
2170          at that stage that -E is going to cause us to tack on
2171          ".html", so we need to compare vs. the original URL plus
2172          ".orig", not the original URL plus ".html.orig". */
2173       filename_plus_orig_suffix = alloca (filename_len + 1);
2174       strcpy(filename_plus_orig_suffix, file);
2175       strcpy((filename_plus_orig_suffix + filename_len) - 4, "orig");
2176     }
2177   else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
2178     {
2179       /* Append ".orig" to the name. */
2180       filename_plus_orig_suffix = alloca (filename_len + sizeof(".orig"));
2181       strcpy(filename_plus_orig_suffix, file);
2182       strcpy(filename_plus_orig_suffix + filename_len, ".orig");
2183     }
2184
2185   /* We can get called twice on the same URL thanks to the
2186      convert_all_links() call in main().  If we write the .orig file
2187      each time in such a case, it'll end up containing the first-pass
2188      conversion, not the original file.  So, see if we've already been
2189      called on this file. */
2190   converted_file_ptr = converted_files;
2191   while (converted_file_ptr != NULL)
2192     if (strcmp(converted_file_ptr->string, file) == 0)
2193       {
2194         already_wrote_backup_file = TRUE;
2195         break;
2196       }
2197     else
2198       converted_file_ptr = converted_file_ptr->next;
2199
2200   if (!already_wrote_backup_file)
2201     {
2202       /* Rename <file> to <file>.orig before former gets written over. */
2203       if (rename(file, filename_plus_orig_suffix) != 0)
2204         logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
2205                    file, filename_plus_orig_suffix, strerror (errno));
2206
2207       /* Remember that we've already written a .orig backup for this file.
2208          Note that we never free this memory since we need it till the
2209          convert_all_links() call, which is one of the last things the
2210          program does before terminating.  BTW, I'm not sure if it would be
2211          safe to just set 'converted_file_ptr->string' to 'file' below,
2212          rather than making a copy of the string...  Another note is that I
2213          thought I could just add a field to the urlpos structure saying
2214          that we'd written a .orig file for this URL, but that didn't work,
2215          so I had to make this separate list.
2216          -- Dan Harkless <wget@harkless.org>
2217
2218          This [adding a field to the urlpos structure] didn't work
2219          because convert_file() is called from convert_all_links at
2220          the end of the retrieval with a freshly built new urlpos
2221          list.
2222          -- Hrvoje Niksic <hniksic@arsdigita.com>
2223       */
2224       converted_file_ptr = xmalloc(sizeof(*converted_file_ptr));
2225       converted_file_ptr->string = xstrdup(file);  /* die on out-of-mem. */
2226       converted_file_ptr->next = converted_files;
2227       converted_files = converted_file_ptr;
2228     }
2229 }
2230
2231 static int find_fragment PARAMS ((const char *, int, const char **,
2232                                   const char **));
2233
2234 /* Replace an attribute's original text with NEW_TEXT. */
2235
2236 static const char *
2237 replace_attr (const char *p, int size, FILE *fp, const char *new_text)
2238 {
2239   int quote_flag = 0;
2240   char quote_char = '\"';       /* use "..." for quoting, unless the
2241                                    original value is quoted, in which
2242                                    case reuse its quoting char. */
2243   const char *frag_beg, *frag_end;
2244
2245   /* Structure of our string is:
2246        "...old-contents..."
2247        <---    size    --->  (with quotes)
2248      OR:
2249        ...old-contents...
2250        <---    size   -->    (no quotes)   */
2251
2252   if (*p == '\"' || *p == '\'')
2253     {
2254       quote_char = *p;
2255       quote_flag = 1;
2256       ++p;
2257       size -= 2;                /* disregard opening and closing quote */
2258     }
2259   putc (quote_char, fp);
2260   fputs (new_text, fp);
2261
2262   /* Look for fragment identifier, if any. */
2263   if (find_fragment (p, size, &frag_beg, &frag_end))
2264     fwrite (frag_beg, 1, frag_end - frag_beg, fp);
2265   p += size;
2266   if (quote_flag)
2267     ++p;
2268   putc (quote_char, fp);
2269
2270   return p;
2271 }
2272
2273 /* The same as REPLACE_ATTR, but used when replacing
2274    <meta http-equiv=refresh content="new_text"> because we need to
2275    append "timeout_value; URL=" before the next_text.  */
2276
2277 static const char *
2278 replace_attr_refresh_hack (const char *p, int size, FILE *fp,
2279                            const char *new_text, int timeout)
2280 {
2281   /* "0; URL=..." */
2282   char *new_with_timeout = (char *)alloca (numdigit (timeout)
2283                                            + 6 /* "; URL=" */
2284                                            + strlen (new_text)
2285                                            + 1);
2286   sprintf (new_with_timeout, "%d; URL=%s", timeout, new_text);
2287
2288   return replace_attr (p, size, fp, new_with_timeout);
2289 }
2290
2291 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
2292    preceded by '&'.  If the character is not found, return zero.  If
2293    the character is found, return 1 and set BP and EP to point to the
2294    beginning and end of the region.
2295
2296    This is used for finding the fragment indentifiers in URLs.  */
2297
2298 static int
2299 find_fragment (const char *beg, int size, const char **bp, const char **ep)
2300 {
2301   const char *end = beg + size;
2302   int saw_amp = 0;
2303   for (; beg < end; beg++)
2304     {
2305       switch (*beg)
2306         {
2307         case '&':
2308           saw_amp = 1;
2309           break;
2310         case '#':
2311           if (!saw_amp)
2312             {
2313               *bp = beg;
2314               *ep = end;
2315               return 1;
2316             }
2317           /* fallthrough */
2318         default:
2319           saw_amp = 0;
2320         }
2321     }
2322   return 0;
2323 }
2324
2325 /* Quote FILE for use as local reference to an HTML file.
2326
2327    We quote ? as %3F to avoid passing part of the file name as the
2328    parameter when browsing the converted file through HTTP.  However,
2329    it is safe to do this only when `--html-extension' is turned on.
2330    This is because converting "index.html?foo=bar" to
2331    "index.html%3Ffoo=bar" would break local browsing, as the latter
2332    isn't even recognized as an HTML file!  However, converting
2333    "index.html?foo=bar.html" to "index.html%3Ffoo=bar.html" should be
2334    safe for both local and HTTP-served browsing.  */
2335
2336 static char *
2337 local_quote_string (const char *file)
2338 {
2339   const char *file_sans_qmark;
2340   int qm;
2341
2342   if (!opt.html_extension)
2343     return html_quote_string (file);
2344
2345   qm = count_char (file, '?');
2346
2347   if (qm)
2348     {
2349       const char *from = file;
2350       char *to, *newname;
2351
2352       /* qm * 2 because we replace each question mark with "%3F",
2353          i.e. replace one char with three, hence two more.  */
2354       int fsqlen = strlen (file) + qm * 2;
2355
2356       to = newname = (char *)alloca (fsqlen + 1);
2357       for (; *from; from++)
2358         {
2359           if (*from != '?')
2360             *to++ = *from;
2361           else
2362             {
2363               *to++ = '%';
2364               *to++ = '3';
2365               *to++ = 'F';
2366             }
2367         }
2368       assert (to - newname == fsqlen);
2369       *to = '\0';
2370
2371       file_sans_qmark = newname;
2372     }
2373   else
2374     file_sans_qmark = file;
2375
2376   return html_quote_string (file_sans_qmark);
2377 }
2378
2379 /* We're storing "modes" of type downloaded_file_t in the hash table.
2380    However, our hash tables only accept pointers for keys and values.
2381    So when we need a pointer, we use the address of a
2382    downloaded_file_t variable of static storage.  */
2383    
2384 static downloaded_file_t *
2385 downloaded_mode_to_ptr (downloaded_file_t mode)
2386 {
2387   static downloaded_file_t
2388     v1 = FILE_NOT_ALREADY_DOWNLOADED,
2389     v2 = FILE_DOWNLOADED_NORMALLY,
2390     v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
2391     v4 = CHECK_FOR_FILE;
2392
2393   switch (mode)
2394     {
2395     case FILE_NOT_ALREADY_DOWNLOADED:
2396       return &v1;
2397     case FILE_DOWNLOADED_NORMALLY:
2398       return &v2;
2399     case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
2400       return &v3;
2401     case CHECK_FOR_FILE:
2402       return &v4;
2403     }
2404   return NULL;
2405 }
2406
2407 /* This should really be merged with dl_file_url_map and
2408    downloaded_html_files in recur.c.  This was originally a list, but
2409    I changed it to a hash table beause it was actually taking a lot of
2410    time to find things in it.  */
2411
2412 static struct hash_table *downloaded_files_hash;
2413
2414 /* Remembers which files have been downloaded.  In the standard case, should be
2415    called with mode == FILE_DOWNLOADED_NORMALLY for each file we actually
2416    download successfully (i.e. not for ones we have failures on or that we skip
2417    due to -N).
2418
2419    When we've downloaded a file and tacked on a ".html" extension due to -E,
2420    call this function with FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
2421    FILE_DOWNLOADED_NORMALLY.
2422
2423    If you just want to check if a file has been previously added without adding
2424    it, call with mode == CHECK_FOR_FILE.  Please be sure to call this function
2425    with local filenames, not remote URLs. */
2426 downloaded_file_t
2427 downloaded_file (downloaded_file_t mode, const char *file)
2428 {
2429   downloaded_file_t *ptr;
2430
2431   if (mode == CHECK_FOR_FILE)
2432     {
2433       if (!downloaded_files_hash)
2434         return FILE_NOT_ALREADY_DOWNLOADED;
2435       ptr = hash_table_get (downloaded_files_hash, file);
2436       if (!ptr)
2437         return FILE_NOT_ALREADY_DOWNLOADED;
2438       return *ptr;
2439     }
2440
2441   if (!downloaded_files_hash)
2442     downloaded_files_hash = make_string_hash_table (0);
2443
2444   ptr = hash_table_get (downloaded_files_hash, file);
2445   if (ptr)
2446     return *ptr;
2447
2448   ptr = downloaded_mode_to_ptr (mode);
2449   hash_table_put (downloaded_files_hash, xstrdup (file), &ptr);
2450
2451   return FILE_NOT_ALREADY_DOWNLOADED;
2452 }
2453
2454 static int
2455 df_free_mapper (void *key, void *value, void *ignored)
2456 {
2457   xfree (key);
2458   return 0;
2459 }
2460
2461 void
2462 downloaded_files_free (void)
2463 {
2464   if (downloaded_files_hash)
2465     {
2466       hash_table_map (downloaded_files_hash, df_free_mapper, NULL);
2467       hash_table_destroy (downloaded_files_hash);
2468       downloaded_files_hash = NULL;
2469     }
2470 }
2471 \f
2472 #if 0
2473 /* Debugging and testing support for path_simplify. */
2474
2475 /* Debug: run path_simplify on PATH and return the result in a new
2476    string.  Useful for calling from the debugger.  */
2477 static char *
2478 ps (char *path)
2479 {
2480   char *copy = xstrdup (path);
2481   path_simplify (copy);
2482   return copy;
2483 }
2484
2485 static void
2486 run_test (char *test, char *expected_result, int expected_change)
2487 {
2488   char *test_copy = xstrdup (test);
2489   int modified = path_simplify (test_copy);
2490
2491   if (0 != strcmp (test_copy, expected_result))
2492     {
2493       printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
2494               test, expected_result, test_copy);
2495     }
2496   if (modified != expected_change)
2497     {
2498       if (expected_change == 1)
2499         printf ("Expected no modification with path_simplify(\"%s\").\n",
2500                 test);
2501       else
2502         printf ("Expected modification with path_simplify(\"%s\").\n",
2503                 test);
2504     }
2505   xfree (test_copy);
2506 }
2507
2508 static void
2509 test_path_simplify (void)
2510 {
2511   static struct {
2512     char *test, *result;
2513     int should_modify;
2514   } tests[] = {
2515     { "",               "",             0 },
2516     { ".",              "",             1 },
2517     { "..",             "",             1 },
2518     { "foo",            "foo",          0 },
2519     { "foo/bar",        "foo/bar",      0 },
2520     { "foo///bar",      "foo/bar",      1 },
2521     { "foo/.",          "foo/",         1 },
2522     { "foo/./",         "foo/",         1 },
2523     { "foo./",          "foo./",        0 },
2524     { "foo/../bar",     "bar",          1 },
2525     { "foo/../bar/",    "bar/",         1 },
2526     { "foo/bar/..",     "foo/",         1 },
2527     { "foo/bar/../x",   "foo/x",        1 },
2528     { "foo/bar/../x/",  "foo/x/",       1 },
2529     { "foo/..",         "",             1 },
2530     { "foo/../..",      "",             1 },
2531     { "a/b/../../c",    "c",            1 },
2532     { "./a/../b",       "b",            1 }
2533   };
2534   int i;
2535
2536   for (i = 0; i < ARRAY_SIZE (tests); i++)
2537     {
2538       char *test = tests[i].test;
2539       char *expected_result = tests[i].result;
2540       int   expected_change = tests[i].should_modify;
2541       run_test (test, expected_result, expected_change);
2542     }
2543
2544   /* Now run all the tests with a leading slash before the test case,
2545      to prove that the slash is being preserved.  */
2546   for (i = 0; i < ARRAY_SIZE (tests); i++)
2547     {
2548       char *test, *expected_result;
2549       int expected_change = tests[i].should_modify;
2550
2551       test = xmalloc (1 + strlen (tests[i].test) + 1);
2552       sprintf (test, "/%s", tests[i].test);
2553
2554       expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
2555       sprintf (expected_result, "/%s", tests[i].result);
2556
2557       run_test (test, expected_result, expected_change);
2558
2559       xfree (test);
2560       xfree (expected_result);
2561     }
2562 }
2563 #endif