]> sjero.net Git - wget/blob - src/url.c
[svn] Handle links to relative "net locations," e.g. <a href="//www.server.com/">.
[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,  U,   U,  U,  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   return 1;
532 }
533
534 /* Used by main.c: detect URLs written using the "shorthand" URL forms
535    popularized by Netscape and NcFTP.  HTTP shorthands look 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 char *
547 rewrite_shorthand_url (const char *url)
548 {
549   const char *p;
550
551   if (url_has_scheme (url))
552     return NULL;
553
554   /* Look for a ':' or '/'.  The former signifies NcFTP syntax, the
555      latter Netscape.  */
556   for (p = url; *p && *p != ':' && *p != '/'; p++)
557     ;
558
559   if (p == url)
560     return NULL;
561
562   if (*p == ':')
563     {
564       const char *pp;
565       char *res;
566       /* If the characters after the colon and before the next slash
567          or end of string are all digits, it's HTTP.  */
568       int digits = 0;
569       for (pp = p + 1; ISDIGIT (*pp); pp++)
570         ++digits;
571       if (digits > 0 && (*pp == '/' || *pp == '\0'))
572         goto http;
573
574       /* Prepend "ftp://" to the entire URL... */
575       res = xmalloc (6 + strlen (url) + 1);
576       sprintf (res, "ftp://%s", url);
577       /* ...and replace ':' with '/'. */
578       res[6 + (p - url)] = '/';
579       return res;
580     }
581   else
582     {
583       char *res;
584     http:
585       /* Just prepend "http://" to what we have. */
586       res = xmalloc (7 + strlen (url) + 1);
587       sprintf (res, "http://%s", url);
588       return res;
589     }
590 }
591 \f
592 static void parse_path PARAMS ((const char *, char **, char **));
593
594 static char *
595 strpbrk_or_eos (const char *s, const char *accept)
596 {
597   char *p = strpbrk (s, accept);
598   if (!p)
599     p = (char *)s + strlen (s);
600   return p;
601 }
602
603 /* Turn STR into lowercase; return non-zero if a character was
604    actually changed. */
605
606 static int
607 lowercase_str (char *str)
608 {
609   int change = 0;
610   for (; *str; str++)
611     if (ISUPPER (*str))
612       {
613         change = 1;
614         *str = TOLOWER (*str);
615       }
616   return change;
617 }
618
619 static char *parse_errors[] = {
620 #define PE_NO_ERROR            0
621   "No error",
622 #define PE_UNSUPPORTED_SCHEME 1
623   "Unsupported scheme",
624 #define PE_EMPTY_HOST          2
625   "Empty host",
626 #define PE_BAD_PORT_NUMBER     3
627   "Bad port number",
628 #define PE_INVALID_USER_NAME   4
629   "Invalid user name"
630 };
631
632 #define SETERR(p, v) do {                       \
633   if (p)                                        \
634     *(p) = (v);                                 \
635 } while (0)
636
637 /* Parse a URL.
638
639    Return a new struct url if successful, NULL on error.  In case of
640    error, and if ERROR is not NULL, also set *ERROR to the appropriate
641    error code. */
642 struct url *
643 url_parse (const char *url, int *error)
644 {
645   struct url *u;
646   const char *p;
647   int path_modified, host_modified;
648
649   enum url_scheme scheme;
650
651   const char *uname_b,     *uname_e;
652   const char *host_b,      *host_e;
653   const char *path_b,      *path_e;
654   const char *params_b,    *params_e;
655   const char *query_b,     *query_e;
656   const char *fragment_b,  *fragment_e;
657
658   int port;
659   char *user = NULL, *passwd = NULL;
660
661   char *url_encoded;
662
663   scheme = url_scheme (url);
664   if (scheme == SCHEME_INVALID)
665     {
666       SETERR (error, PE_UNSUPPORTED_SCHEME);
667       return NULL;
668     }
669
670   url_encoded = reencode_string (url);
671   p = url_encoded;
672
673   p += strlen (supported_schemes[scheme].leading_string);
674   uname_b = p;
675   p += url_skip_uname (p);
676   uname_e = p;
677
678   /* scheme://user:pass@host[:port]... */
679   /*                    ^              */
680
681   /* We attempt to break down the URL into the components path,
682      params, query, and fragment.  They are ordered like this:
683
684        scheme://host[:port][/path][;params][?query][#fragment]  */
685
686   params_b   = params_e   = NULL;
687   query_b    = query_e    = NULL;
688   fragment_b = fragment_e = NULL;
689
690   host_b = p;
691   p = strpbrk_or_eos (p, ":/;?#");
692   host_e = p;
693
694   if (host_b == host_e)
695     {
696       SETERR (error, PE_EMPTY_HOST);
697       return NULL;
698     }
699
700   port = scheme_default_port (scheme);
701   if (*p == ':')
702     {
703       const char *port_b, *port_e, *pp;
704
705       /* scheme://host:port/tralala */
706       /*              ^             */
707       ++p;
708       port_b = p;
709       p = strpbrk_or_eos (p, "/;?#");
710       port_e = p;
711
712       if (port_b == port_e)
713         {
714           /* http://host:/whatever */
715           /*             ^         */
716           SETERR (error, PE_BAD_PORT_NUMBER);
717           return NULL;
718         }
719
720       for (port = 0, pp = port_b; pp < port_e; pp++)
721         {
722           if (!ISDIGIT (*pp))
723             {
724               /* http://host:12randomgarbage/blah */
725               /*               ^                  */
726               SETERR (error, PE_BAD_PORT_NUMBER);
727               return NULL;
728             }
729           port = 10 * port + (*pp - '0');
730         }
731     }
732
733   if (*p == '/')
734     {
735       ++p;
736       path_b = p;
737       p = strpbrk_or_eos (p, ";?#");
738       path_e = p;
739     }
740   else
741     {
742       /* Path is not allowed not to exist. */
743       path_b = path_e = p;
744     }
745
746   if (*p == ';')
747     {
748       ++p;
749       params_b = p;
750       p = strpbrk_or_eos (p, "?#");
751       params_e = p;
752     }
753   if (*p == '?')
754     {
755       ++p;
756       query_b = p;
757       p = strpbrk_or_eos (p, "#");
758       query_e = p;
759     }
760   if (*p == '#')
761     {
762       ++p;
763       fragment_b = p;
764       p += strlen (p);
765       fragment_e = p;
766     }
767   assert (*p == 0);
768
769   if (uname_b != uname_e)
770     {
771       /* http://user:pass@host */
772       /*        ^         ^    */
773       /*     uname_b   uname_e */
774       if (!parse_uname (uname_b, uname_e - uname_b - 1, &user, &passwd))
775         {
776           SETERR (error, PE_INVALID_USER_NAME);
777           return NULL;
778         }
779     }
780
781   u = (struct url *)xmalloc (sizeof (struct url));
782   memset (u, 0, sizeof (*u));
783
784   u->scheme = scheme;
785   u->host   = strdupdelim (host_b, host_e);
786   u->port   = port;
787   u->user   = user;
788   u->passwd = passwd;
789
790   u->path = strdupdelim (path_b, path_e);
791   path_modified = path_simplify (u->path);
792   parse_path (u->path, &u->dir, &u->file);
793
794   host_modified = lowercase_str (u->host);
795
796   if (params_b)
797     u->params = strdupdelim (params_b, params_e);
798   if (query_b)
799     u->query = strdupdelim (query_b, query_e);
800   if (fragment_b)
801     u->fragment = strdupdelim (fragment_b, fragment_e);
802
803   if (path_modified || u->fragment || host_modified || path_b == path_e)
804     {
805       /* If we suspect that a transformation has rendered what
806          url_string might return different from URL_ENCODED, rebuild
807          u->url using url_string.  */
808       u->url = url_string (u, 0);
809
810       if (url_encoded != url)
811         xfree ((char *) url_encoded);
812     }
813   else
814     {
815       if (url_encoded == url)
816         u->url    = xstrdup (url);
817       else
818         u->url    = url_encoded;
819     }
820   url_encoded = NULL;
821
822   return u;
823 }
824
825 const char *
826 url_error (int error_code)
827 {
828   assert (error_code >= 0 && error_code < ARRAY_SIZE (parse_errors));
829   return parse_errors[error_code];
830 }
831
832 static void
833 parse_path (const char *quoted_path, char **dir, char **file)
834 {
835   char *path, *last_slash;
836
837   STRDUP_ALLOCA (path, quoted_path);
838   decode_string (path);
839
840   last_slash = strrchr (path, '/');
841   if (!last_slash)
842     {
843       *dir = xstrdup ("");
844       *file = xstrdup (path);
845     }
846   else
847     {
848       *dir = strdupdelim (path, last_slash);
849       *file = xstrdup (last_slash + 1);
850     }
851 }
852
853 /* Note: URL's "full path" is the path with the query string and
854    params appended.  The "fragment" (#foo) is intentionally ignored,
855    but that might be changed.  For example, if the original URL was
856    "http://host:port/foo/bar/baz;bullshit?querystring#uselessfragment",
857    the full path will be "/foo/bar/baz;bullshit?querystring".  */
858
859 /* Return the length of the full path, without the terminating
860    zero.  */
861
862 static int
863 full_path_length (const struct url *url)
864 {
865   int len = 0;
866
867 #define FROB(el) if (url->el) len += 1 + strlen (url->el)
868
869   FROB (path);
870   FROB (params);
871   FROB (query);
872
873 #undef FROB
874
875   return len;
876 }
877
878 /* Write out the full path. */
879
880 static void
881 full_path_write (const struct url *url, char *where)
882 {
883 #define FROB(el, chr) do {                      \
884   char *f_el = url->el;                         \
885   if (f_el) {                                   \
886     int l = strlen (f_el);                      \
887     *where++ = chr;                             \
888     memcpy (where, f_el, l);                    \
889     where += l;                                 \
890   }                                             \
891 } while (0)
892
893   FROB (path, '/');
894   FROB (params, ';');
895   FROB (query, '?');
896
897 #undef FROB
898 }
899
900 /* Public function for getting the "full path".  E.g. if u->path is
901    "foo/bar" and u->query is "param=value", full_path will be
902    "/foo/bar?param=value". */
903
904 char *
905 url_full_path (const struct url *url)
906 {
907   int length = full_path_length (url);
908   char *full_path = (char *)xmalloc(length + 1);
909
910   full_path_write (url, full_path);
911   full_path[length] = '\0';
912
913   return full_path;
914 }
915
916 /* Sync u->path and u->url with u->dir and u->file. */
917
918 static void
919 sync_path (struct url *url)
920 {
921   char *newpath;
922
923   xfree (url->path);
924
925   if (!*url->dir)
926     {
927       newpath = xstrdup (url->file);
928       REENCODE (newpath);
929     }
930   else
931     {
932       int dirlen = strlen (url->dir);
933       int filelen = strlen (url->file);
934
935       newpath = xmalloc (dirlen + 1 + filelen + 1);
936       memcpy (newpath, url->dir, dirlen);
937       newpath[dirlen] = '/';
938       memcpy (newpath + dirlen + 1, url->file, filelen);
939       newpath[dirlen + 1 + filelen] = '\0';
940       REENCODE (newpath);
941     }
942
943   url->path = newpath;
944
945   /* Synchronize u->url. */
946   xfree (url->url);
947   url->url = url_string (url, 0);
948 }
949
950 /* Mutators.  Code in ftp.c insists on changing u->dir and u->file.
951    This way we can sync u->path and u->url when they get changed.  */
952
953 void
954 url_set_dir (struct url *url, const char *newdir)
955 {
956   xfree (url->dir);
957   url->dir = xstrdup (newdir);
958   sync_path (url);
959 }
960
961 void
962 url_set_file (struct url *url, const char *newfile)
963 {
964   xfree (url->file);
965   url->file = xstrdup (newfile);
966   sync_path (url);
967 }
968
969 void
970 url_free (struct url *url)
971 {
972   xfree (url->host);
973   xfree (url->path);
974   xfree (url->url);
975
976   FREE_MAYBE (url->params);
977   FREE_MAYBE (url->query);
978   FREE_MAYBE (url->fragment);
979   FREE_MAYBE (url->user);
980   FREE_MAYBE (url->passwd);
981
982   xfree (url->dir);
983   xfree (url->file);
984
985   xfree (url);
986 }
987 \f
988 struct urlpos *
989 get_urls_file (const char *file)
990 {
991   struct file_memory *fm;
992   struct urlpos *head, *tail;
993   const char *text, *text_end;
994
995   /* Load the file.  */
996   fm = read_file (file);
997   if (!fm)
998     {
999       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1000       return NULL;
1001     }
1002   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
1003
1004   head = tail = NULL;
1005   text = fm->content;
1006   text_end = fm->content + fm->length;
1007   while (text < text_end)
1008     {
1009       const char *line_beg = text;
1010       const char *line_end = memchr (text, '\n', text_end - text);
1011       if (!line_end)
1012         line_end = text_end;
1013       else
1014         ++line_end;
1015       text = line_end;
1016
1017       /* Strip whitespace from the beginning and end of line. */
1018       while (line_beg < line_end && ISSPACE (*line_beg))
1019         ++line_beg;
1020       while (line_end > line_beg && ISSPACE (*(line_end - 1)))
1021         --line_end;
1022
1023       if (line_end > line_beg)
1024         {
1025           /* URL is in the [line_beg, line_end) region. */
1026
1027           int up_error_code;
1028           char *url_text;
1029           struct urlpos *entry;
1030           struct url *url;
1031
1032           /* We must copy the URL to a zero-terminated string, and we
1033              can't use alloca because we're in a loop.  *sigh*.  */
1034           url_text = strdupdelim (line_beg, line_end);
1035
1036           if (opt.base_href)
1037             {
1038               /* Merge opt.base_href with URL. */
1039               char *merged = uri_merge (opt.base_href, url_text);
1040               xfree (url_text);
1041               url_text = merged;
1042             }
1043
1044           url = url_parse (url_text, &up_error_code);
1045           if (!url)
1046             {
1047               logprintf (LOG_NOTQUIET, "%s: Invalid URL %s: %s\n",
1048                          file, url_text, url_error (up_error_code));
1049               xfree (url_text);
1050               continue;
1051             }
1052           xfree (url_text);
1053
1054           entry = (struct urlpos *)xmalloc (sizeof (struct urlpos));
1055           memset (entry, 0, sizeof (*entry));
1056           entry->next = NULL;
1057           entry->url = url;
1058
1059           if (!head)
1060             head = entry;
1061           else
1062             tail->next = entry;
1063           tail = entry;
1064         }
1065     }
1066   read_file_free (fm);
1067   return head;
1068 }
1069 \f
1070 /* Free the linked list of urlpos.  */
1071 void
1072 free_urlpos (struct urlpos *l)
1073 {
1074   while (l)
1075     {
1076       struct urlpos *next = l->next;
1077       if (l->url)
1078         url_free (l->url);
1079       FREE_MAYBE (l->local_name);
1080       xfree (l);
1081       l = next;
1082     }
1083 }
1084
1085 /* Rotate FNAME opt.backups times */
1086 void
1087 rotate_backups(const char *fname)
1088 {
1089   int maxlen = strlen (fname) + 1 + numdigit (opt.backups) + 1;
1090   char *from = (char *)alloca (maxlen);
1091   char *to = (char *)alloca (maxlen);
1092   struct stat sb;
1093   int i;
1094
1095   if (stat (fname, &sb) == 0)
1096     if (S_ISREG (sb.st_mode) == 0)
1097       return;
1098
1099   for (i = opt.backups; i > 1; i--)
1100     {
1101       sprintf (from, "%s.%d", fname, i - 1);
1102       sprintf (to, "%s.%d", fname, i);
1103       /* #### This will fail on machines without the rename() system
1104          call.  */
1105       rename (from, to);
1106     }
1107
1108   sprintf (to, "%s.%d", fname, 1);
1109   rename(fname, to);
1110 }
1111
1112 /* Create all the necessary directories for PATH (a file).  Calls
1113    mkdirhier() internally.  */
1114 int
1115 mkalldirs (const char *path)
1116 {
1117   const char *p;
1118   char *t;
1119   struct stat st;
1120   int res;
1121
1122   p = path + strlen (path);
1123   for (; *p != '/' && p != path; p--);
1124   /* Don't create if it's just a file.  */
1125   if ((p == path) && (*p != '/'))
1126     return 0;
1127   t = strdupdelim (path, p);
1128   /* Check whether the directory exists.  */
1129   if ((stat (t, &st) == 0))
1130     {
1131       if (S_ISDIR (st.st_mode))
1132         {
1133           xfree (t);
1134           return 0;
1135         }
1136       else
1137         {
1138           /* If the dir exists as a file name, remove it first.  This
1139              is *only* for Wget to work with buggy old CERN http
1140              servers.  Here is the scenario: When Wget tries to
1141              retrieve a directory without a slash, e.g.
1142              http://foo/bar (bar being a directory), CERN server will
1143              not redirect it too http://foo/bar/ -- it will generate a
1144              directory listing containing links to bar/file1,
1145              bar/file2, etc.  Wget will lose because it saves this
1146              HTML listing to a file `bar', so it cannot create the
1147              directory.  To work around this, if the file of the same
1148              name exists, we just remove it and create the directory
1149              anyway.  */
1150           DEBUGP (("Removing %s because of directory danger!\n", t));
1151           unlink (t);
1152         }
1153     }
1154   res = make_directory (t);
1155   if (res != 0)
1156     logprintf (LOG_NOTQUIET, "%s: %s", t, strerror (errno));
1157   xfree (t);
1158   return res;
1159 }
1160
1161 static int
1162 count_slashes (const char *s)
1163 {
1164   int i = 0;
1165   while (*s)
1166     if (*s++ == '/')
1167       ++i;
1168   return i;
1169 }
1170
1171 /* Return the path name of the URL-equivalent file name, with a
1172    remote-like structure of directories.  */
1173 static char *
1174 mkstruct (const struct url *u)
1175 {
1176   char *dir, *dir_preencoding;
1177   char *file, *res, *dirpref;
1178   char *query = u->query && *u->query ? u->query : NULL;
1179   int l;
1180
1181   if (opt.cut_dirs)
1182     {
1183       char *ptr = u->dir + (*u->dir == '/');
1184       int slash_count = 1 + count_slashes (ptr);
1185       int cut = MINVAL (opt.cut_dirs, slash_count);
1186       for (; cut && *ptr; ptr++)
1187         if (*ptr == '/')
1188           --cut;
1189       STRDUP_ALLOCA (dir, ptr);
1190     }
1191   else
1192     dir = u->dir + (*u->dir == '/');
1193
1194   /* Check for the true name (or at least a consistent name for saving
1195      to directory) of HOST, reusing the hlist if possible.  */
1196   if (opt.add_hostdir)
1197     {
1198       /* Add dir_prefix and hostname (if required) to the beginning of
1199          dir.  */
1200       dirpref = (char *)alloca (strlen (opt.dir_prefix) + 1
1201                                 + strlen (u->host)
1202                                 + 1 + numdigit (u->port)
1203                                 + 1);
1204       if (!DOTP (opt.dir_prefix))
1205         sprintf (dirpref, "%s/%s", opt.dir_prefix, u->host);
1206       else
1207         strcpy (dirpref, u->host);
1208
1209       if (u->port != scheme_default_port (u->scheme))
1210         {
1211           int len = strlen (dirpref);
1212           dirpref[len] = ':';
1213           number_to_string (dirpref + len + 1, u->port);
1214         }
1215     }
1216   else                          /* not add_hostdir */
1217     {
1218       if (!DOTP (opt.dir_prefix))
1219         dirpref = opt.dir_prefix;
1220       else
1221         dirpref = "";
1222     }
1223
1224   /* If there is a prefix, prepend it.  */
1225   if (*dirpref)
1226     {
1227       char *newdir = (char *)alloca (strlen (dirpref) + 1 + strlen (dir) + 2);
1228       sprintf (newdir, "%s%s%s", dirpref, *dir == '/' ? "" : "/", dir);
1229       dir = newdir;
1230     }
1231
1232   dir_preencoding = dir;
1233   dir = reencode_string (dir_preencoding);
1234
1235   l = strlen (dir);
1236   if (l && dir[l - 1] == '/')
1237     dir[l - 1] = '\0';
1238
1239   if (!*u->file)
1240     file = "index.html";
1241   else
1242     file = u->file;
1243
1244   /* Finally, construct the full name.  */
1245   res = (char *)xmalloc (strlen (dir) + 1 + strlen (file)
1246                          + (query ? (1 + strlen (query)) : 0)
1247                          + 1);
1248   sprintf (res, "%s%s%s", dir, *dir ? "/" : "", file);
1249   if (query)
1250     {
1251       strcat (res, "?");
1252       strcat (res, query);
1253     }
1254   if (dir != dir_preencoding)
1255     xfree (dir);
1256   return res;
1257 }
1258
1259 /* Compose a file name out of BASE, an unescaped file name, and QUERY,
1260    an escaped query string.  The trick is to make sure that unsafe
1261    characters in BASE are escaped, and that slashes in QUERY are also
1262    escaped.  */
1263
1264 static char *
1265 compose_file_name (char *base, char *query)
1266 {
1267   char result[256];
1268   char *from;
1269   char *to = result;
1270
1271   /* Copy BASE to RESULT and encode all unsafe characters.  */
1272   from = base;
1273   while (*from && to - result < sizeof (result))
1274     {
1275       if (UNSAFE_CHAR (*from))
1276         {
1277           unsigned char c = *from++;
1278           *to++ = '%';
1279           *to++ = XDIGIT_TO_XCHAR (c >> 4);
1280           *to++ = XDIGIT_TO_XCHAR (c & 0xf);
1281         }
1282       else
1283         *to++ = *from++;
1284     }
1285
1286   if (query && to - result < sizeof (result))
1287     {
1288       *to++ = '?';
1289
1290       /* Copy QUERY to RESULT and encode all '/' characters. */
1291       from = query;
1292       while (*from && to - result < sizeof (result))
1293         {
1294           if (*from == '/')
1295             {
1296               *to++ = '%';
1297               *to++ = '2';
1298               *to++ = 'F';
1299               ++from;
1300             }
1301           else
1302             *to++ = *from++;
1303         }
1304     }
1305
1306   if (to - result < sizeof (result))
1307     *to = '\0';
1308   else
1309     /* Truncate input which is too long, presumably due to a huge
1310        query string.  */
1311     result[sizeof (result) - 1] = '\0';
1312
1313   return xstrdup (result);
1314 }
1315
1316 /* Create a unique filename, corresponding to a given URL.  Calls
1317    mkstruct if necessary.  Does *not* actually create any directories.  */
1318 char *
1319 url_filename (const struct url *u)
1320 {
1321   char *file, *name;
1322   int have_prefix = 0;          /* whether we must prepend opt.dir_prefix */
1323
1324   if (opt.dirstruct)
1325     {
1326       file = mkstruct (u);
1327       have_prefix = 1;
1328     }
1329   else
1330     {
1331       char *base = *u->file ? u->file : "index.html";
1332       char *query = u->query && *u->query ? u->query : NULL;
1333       file = compose_file_name (base, query);
1334     }
1335
1336   if (!have_prefix)
1337     {
1338       /* Check whether the prefix directory is something other than "."
1339          before prepending it.  */
1340       if (!DOTP (opt.dir_prefix))
1341         {
1342           char *nfile = (char *)xmalloc (strlen (opt.dir_prefix)
1343                                          + 1 + strlen (file) + 1);
1344           sprintf (nfile, "%s/%s", opt.dir_prefix, file);
1345           xfree (file);
1346           file = nfile;
1347         }
1348     }
1349   /* DOS-ish file systems don't like `%' signs in them; we change it
1350      to `@'.  */
1351 #ifdef WINDOWS
1352   {
1353     char *p = file;
1354     for (p = file; *p; p++)
1355       if (*p == '%')
1356         *p = '@';
1357   }
1358 #endif /* WINDOWS */
1359
1360   /* Check the cases in which the unique extensions are not used:
1361      1) Clobbering is turned off (-nc).
1362      2) Retrieval with regetting.
1363      3) Timestamping is used.
1364      4) Hierarchy is built.
1365
1366      The exception is the case when file does exist and is a
1367      directory (actually support for bad httpd-s).  */
1368   if ((opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct)
1369       && !(file_exists_p (file) && !file_non_directory_p (file)))
1370     return file;
1371
1372   /* Find a unique name.  */
1373   name = unique_name (file);
1374   xfree (file);
1375   return name;
1376 }
1377
1378 /* Return the langth of URL's path.  Path is considered to be
1379    terminated by one of '?', ';', '#', or by the end of the
1380    string.  */
1381 static int
1382 path_length (const char *url)
1383 {
1384   const char *q = strpbrk_or_eos (url, "?;#");
1385   return q - url;
1386 }
1387
1388 /* Find the last occurrence of character C in the range [b, e), or
1389    NULL, if none are present.  This is equivalent to strrchr(b, c),
1390    except that it accepts an END argument instead of requiring the
1391    string to be zero-terminated.  Why is there no memrchr()?  */
1392 static const char *
1393 find_last_char (const char *b, const char *e, char c)
1394 {
1395   for (; e > b; e--)
1396     if (*e == c)
1397       return e;
1398   return NULL;
1399 }
1400 \f
1401 /* Resolve "." and ".." elements of PATH by destructively modifying
1402    PATH.  "." is resolved by removing that path element, and ".." is
1403    resolved by removing the preceding path element.  Leading and
1404    trailing slashes are preserved.
1405
1406    Return non-zero if any changes have been made.
1407
1408    For example, "a/b/c/./../d/.." will yield "a/b/".  More exhaustive
1409    test examples are provided below.  If you change anything in this
1410    function, run test_path_simplify to make sure you haven't broken a
1411    test case.
1412
1413    A previous version of this function was based on path_simplify()
1414    from GNU Bash, but it has been rewritten for Wget 1.8.1.  */
1415
1416 static int
1417 path_simplify (char *path)
1418 {
1419   int change = 0;
1420   char *p, *end;
1421
1422   if (path[0] == '/')
1423     ++path;                     /* preserve the leading '/'. */
1424
1425   p = path;
1426   end = p + strlen (p) + 1;     /* position past the terminating zero. */
1427
1428   while (1)
1429     {
1430     again:
1431       /* P should point to the beginning of a path element. */
1432
1433       if (*p == '.' && (*(p + 1) == '/' || *(p + 1) == '\0'))
1434         {
1435           /* Handle "./foo" by moving "foo" two characters to the
1436              left. */
1437           if (*(p + 1) == '/')
1438             {
1439               change = 1;
1440               memmove (p, p + 2, end - p);
1441               end -= 2;
1442               goto again;
1443             }
1444           else
1445             {
1446               change = 1;
1447               *p = '\0';
1448               break;
1449             }
1450         }
1451       else if (*p == '.' && *(p + 1) == '.'
1452                && (*(p + 2) == '/' || *(p + 2) == '\0'))
1453         {
1454           /* Handle "../foo" by moving "foo" one path element to the
1455              left.  */
1456           char *b = p;          /* not p-1 because P can equal PATH */
1457
1458           /* Backtrack by one path element, but not past the beginning
1459              of PATH. */
1460
1461           /* foo/bar/../baz */
1462           /*         ^ p    */
1463           /*     ^ b        */
1464
1465           if (b > path)
1466             {
1467               /* Move backwards until B hits the beginning of the
1468                  previous path element or the beginning of path. */
1469               for (--b; b > path && *(b - 1) != '/'; b--)
1470                 ;
1471             }
1472
1473           change = 1;
1474           if (*(p + 2) == '/')
1475             {
1476               memmove (b, p + 3, end - (p + 3));
1477               end -= (p + 3) - b;
1478               p = b;
1479             }
1480           else
1481             {
1482               *b = '\0';
1483               break;
1484             }
1485
1486           goto again;
1487         }
1488       else if (*p == '/')
1489         {
1490           /* Remove empty path elements.  Not mandated by rfc1808 et
1491              al, but empty path elements are not all that useful, and
1492              the rest of Wget might not deal with them well. */
1493           char *q = p;
1494           while (*q == '/')
1495             ++q;
1496           change = 1;
1497           if (*q == '\0')
1498             {
1499               *p = '\0';
1500               break;
1501             }
1502           memmove (p, q, end - q);
1503           end -= q - p;
1504           goto again;
1505         }
1506
1507       /* Skip to the next path element. */
1508       while (*p && *p != '/')
1509         ++p;
1510       if (*p == '\0')
1511         break;
1512
1513       /* Make sure P points to the beginning of the next path element,
1514          which is location after the slash. */
1515       ++p;
1516     }
1517
1518   return change;
1519 }
1520 \f
1521 /* Resolve the result of "linking" a base URI (BASE) to a
1522    link-specified URI (LINK).
1523
1524    Either of the URIs may be absolute or relative, complete with the
1525    host name, or path only.  This tries to behave "reasonably" in all
1526    foreseeable cases.  It employs little specific knowledge about
1527    schemes or URL-specific stuff -- it just works on strings.
1528
1529    The parameters LINKLENGTH is useful if LINK is not zero-terminated.
1530    See uri_merge for a gentler interface to this functionality.
1531
1532    Perhaps this function should call path_simplify so that the callers
1533    don't have to call url_parse unconditionally.  */
1534 static char *
1535 uri_merge_1 (const char *base, const char *link, int linklength, int no_scheme)
1536 {
1537   char *constr;
1538
1539   if (no_scheme)
1540     {
1541       const char *end = base + path_length (base);
1542
1543       if (!*link)
1544         {
1545           /* Empty LINK points back to BASE, query string and all. */
1546           constr = xstrdup (base);
1547         }
1548       else if (*link == '?')
1549         {
1550           /* LINK points to the same location, but changes the query
1551              string.  Examples: */
1552           /* uri_merge("path",         "?new") -> "path?new"     */
1553           /* uri_merge("path?foo",     "?new") -> "path?new"     */
1554           /* uri_merge("path?foo#bar", "?new") -> "path?new"     */
1555           /* uri_merge("path#foo",     "?new") -> "path?new"     */
1556           int baselength = end - base;
1557           constr = xmalloc (baselength + linklength + 1);
1558           memcpy (constr, base, baselength);
1559           memcpy (constr + baselength, link, linklength);
1560           constr[baselength + linklength] = '\0';
1561         }
1562       else if (*link == '#')
1563         {
1564           /* uri_merge("path",         "#new") -> "path#new"     */
1565           /* uri_merge("path#foo",     "#new") -> "path#new"     */
1566           /* uri_merge("path?foo",     "#new") -> "path?foo#new" */
1567           /* uri_merge("path?foo#bar", "#new") -> "path?foo#new" */
1568           int baselength;
1569           const char *end1 = strchr (base, '#');
1570           if (!end1)
1571             end1 = base + strlen (base);
1572           baselength = end1 - base;
1573           constr = xmalloc (baselength + linklength + 1);
1574           memcpy (constr, base, baselength);
1575           memcpy (constr + baselength, link, linklength);
1576           constr[baselength + linklength] = '\0';
1577         }
1578       else if (linklength > 1 && *link == '/' && *(link + 1) == '/')
1579         {
1580           /* LINK begins with "//" and so is a net path: we need to
1581              replace everything after (and including) the double slash
1582              with LINK. */
1583
1584           /* uri_merge("foo", "//new/bar")            -> "//new/bar"      */
1585           /* uri_merge("//old/foo", "//new/bar")      -> "//new/bar"      */
1586           /* uri_merge("http://old/foo", "//new/bar") -> "http://new/bar" */
1587
1588           int span;
1589           const char *slash;
1590           const char *start_insert;
1591
1592           /* Look for first slash. */
1593           slash = memchr (base, '/', end - base);
1594           /* If found slash and it is a double slash, then replace
1595              from this point, else default to replacing from the
1596              beginning.  */
1597           if (slash && *(slash + 1) == '/')
1598             start_insert = slash;
1599           else
1600             start_insert = base;
1601
1602           span = start_insert - base;
1603           constr = (char *)xmalloc (span + linklength + 1);
1604           if (span)
1605             memcpy (constr, base, span);
1606           memcpy (constr + span, link, linklength);
1607           constr[span + linklength] = '\0';
1608         }
1609       else if (*link == '/')
1610         {
1611           /* LINK is an absolute path: we need to replace everything
1612              after (and including) the FIRST slash with LINK.
1613
1614              So, if BASE is "http://host/whatever/foo/bar", and LINK is
1615              "/qux/xyzzy", our result should be
1616              "http://host/qux/xyzzy".  */
1617           int span;
1618           const char *slash;
1619           const char *start_insert = NULL; /* for gcc to shut up. */
1620           const char *pos = base;
1621           int seen_slash_slash = 0;
1622           /* We're looking for the first slash, but want to ignore
1623              double slash. */
1624         again:
1625           slash = memchr (pos, '/', end - pos);
1626           if (slash && !seen_slash_slash)
1627             if (*(slash + 1) == '/')
1628               {
1629                 pos = slash + 2;
1630                 seen_slash_slash = 1;
1631                 goto again;
1632               }
1633
1634           /* At this point, SLASH is the location of the first / after
1635              "//", or the first slash altogether.  START_INSERT is the
1636              pointer to the location where LINK will be inserted.  When
1637              examining the last two examples, keep in mind that LINK
1638              begins with '/'. */
1639
1640           if (!slash && !seen_slash_slash)
1641             /* example: "foo" */
1642             /*           ^    */
1643             start_insert = base;
1644           else if (!slash && seen_slash_slash)
1645             /* example: "http://foo" */
1646             /*                     ^ */
1647             start_insert = end;
1648           else if (slash && !seen_slash_slash)
1649             /* example: "foo/bar" */
1650             /*           ^        */
1651             start_insert = base;
1652           else if (slash && seen_slash_slash)
1653             /* example: "http://something/" */
1654             /*                           ^  */
1655             start_insert = slash;
1656
1657           span = start_insert - base;
1658           constr = (char *)xmalloc (span + linklength + 1);
1659           if (span)
1660             memcpy (constr, base, span);
1661           if (linklength)
1662             memcpy (constr + span, link, linklength);
1663           constr[span + linklength] = '\0';
1664         }
1665       else
1666         {
1667           /* LINK is a relative URL: we need to replace everything
1668              after last slash (possibly empty) with LINK.
1669
1670              So, if BASE is "whatever/foo/bar", and LINK is "qux/xyzzy",
1671              our result should be "whatever/foo/qux/xyzzy".  */
1672           int need_explicit_slash = 0;
1673           int span;
1674           const char *start_insert;
1675           const char *last_slash = find_last_char (base, end, '/');
1676           if (!last_slash)
1677             {
1678               /* No slash found at all.  Append LINK to what we have,
1679                  but we'll need a slash as a separator.
1680
1681                  Example: if base == "foo" and link == "qux/xyzzy", then
1682                  we cannot just append link to base, because we'd get
1683                  "fooqux/xyzzy", whereas what we want is
1684                  "foo/qux/xyzzy".
1685
1686                  To make sure the / gets inserted, we set
1687                  need_explicit_slash to 1.  We also set start_insert
1688                  to end + 1, so that the length calculations work out
1689                  correctly for one more (slash) character.  Accessing
1690                  that character is fine, since it will be the
1691                  delimiter, '\0' or '?'.  */
1692               /* example: "foo?..." */
1693               /*               ^    ('?' gets changed to '/') */
1694               start_insert = end + 1;
1695               need_explicit_slash = 1;
1696             }
1697           else if (last_slash && last_slash != base && *(last_slash - 1) == '/')
1698             {
1699               /* example: http://host"  */
1700               /*                      ^ */
1701               start_insert = end + 1;
1702               need_explicit_slash = 1;
1703             }
1704           else
1705             {
1706               /* example: "whatever/foo/bar" */
1707               /*                        ^    */
1708               start_insert = last_slash + 1;
1709             }
1710
1711           span = start_insert - base;
1712           constr = (char *)xmalloc (span + linklength + 1);
1713           if (span)
1714             memcpy (constr, base, span);
1715           if (need_explicit_slash)
1716             constr[span - 1] = '/';
1717           if (linklength)
1718             memcpy (constr + span, link, linklength);
1719           constr[span + linklength] = '\0';
1720         }
1721     }
1722   else /* !no_scheme */
1723     {
1724       constr = strdupdelim (link, link + linklength);
1725     }
1726   return constr;
1727 }
1728
1729 /* Merge BASE with LINK and return the resulting URI.  This is an
1730    interface to uri_merge_1 that assumes that LINK is a
1731    zero-terminated string.  */
1732 char *
1733 uri_merge (const char *base, const char *link)
1734 {
1735   return uri_merge_1 (base, link, strlen (link), !url_has_scheme (link));
1736 }
1737 \f
1738 #define APPEND(p, s) do {                       \
1739   int len = strlen (s);                         \
1740   memcpy (p, s, len);                           \
1741   p += len;                                     \
1742 } while (0)
1743
1744 /* Use this instead of password when the actual password is supposed
1745    to be hidden.  We intentionally use a generic string without giving
1746    away the number of characters in the password, like previous
1747    versions did.  */
1748 #define HIDDEN_PASSWORD "*password*"
1749
1750 /* Recreate the URL string from the data in URL.
1751
1752    If HIDE is non-zero (as it is when we're calling this on a URL we
1753    plan to print, but not when calling it to canonicalize a URL for
1754    use within the program), password will be hidden.  Unsafe
1755    characters in the URL will be quoted.  */
1756
1757 char *
1758 url_string (const struct url *url, int hide_password)
1759 {
1760   int size;
1761   char *result, *p;
1762   char *quoted_user = NULL, *quoted_passwd = NULL;
1763
1764   int scheme_port  = supported_schemes[url->scheme].default_port;
1765   char *scheme_str = supported_schemes[url->scheme].leading_string;
1766   int fplen = full_path_length (url);
1767
1768   assert (scheme_str != NULL);
1769
1770   /* Make sure the user name and password are quoted. */
1771   if (url->user)
1772     {
1773       quoted_user = encode_string_maybe (url->user);
1774       if (url->passwd)
1775         {
1776           if (hide_password)
1777             quoted_passwd = HIDDEN_PASSWORD;
1778           else
1779             quoted_passwd = encode_string_maybe (url->passwd);
1780         }
1781     }
1782
1783   size = (strlen (scheme_str)
1784           + strlen (url->host)
1785           + fplen
1786           + 1);
1787   if (url->port != scheme_port)
1788     size += 1 + numdigit (url->port);
1789   if (quoted_user)
1790     {
1791       size += 1 + strlen (quoted_user);
1792       if (quoted_passwd)
1793         size += 1 + strlen (quoted_passwd);
1794     }
1795
1796   p = result = xmalloc (size);
1797
1798   APPEND (p, scheme_str);
1799   if (quoted_user)
1800     {
1801       APPEND (p, quoted_user);
1802       if (quoted_passwd)
1803         {
1804           *p++ = ':';
1805           APPEND (p, quoted_passwd);
1806         }
1807       *p++ = '@';
1808     }
1809
1810   APPEND (p, url->host);
1811   if (url->port != scheme_port)
1812     {
1813       *p++ = ':';
1814       p = number_to_string (p, url->port);
1815     }
1816
1817   full_path_write (url, p);
1818   p += fplen;
1819   *p++ = '\0';
1820
1821   assert (p - result == size);
1822
1823   if (quoted_user && quoted_user != url->user)
1824     xfree (quoted_user);
1825   if (quoted_passwd && !hide_password
1826       && quoted_passwd != url->passwd)
1827     xfree (quoted_passwd);
1828
1829   return result;
1830 }
1831 \f
1832 /* Returns proxy host address, in accordance with SCHEME.  */
1833 char *
1834 getproxy (enum url_scheme scheme)
1835 {
1836   char *proxy = NULL;
1837   char *rewritten_url;
1838   static char rewritten_storage[1024];
1839
1840   switch (scheme)
1841     {
1842     case SCHEME_HTTP:
1843       proxy = opt.http_proxy ? opt.http_proxy : getenv ("http_proxy");
1844       break;
1845 #ifdef HAVE_SSL
1846     case SCHEME_HTTPS:
1847       proxy = opt.https_proxy ? opt.https_proxy : getenv ("https_proxy");
1848       break;
1849 #endif
1850     case SCHEME_FTP:
1851       proxy = opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftp_proxy");
1852       break;
1853     case SCHEME_INVALID:
1854       break;
1855     }
1856   if (!proxy || !*proxy)
1857     return NULL;
1858
1859   /* Handle shorthands. */
1860   rewritten_url = rewrite_shorthand_url (proxy);
1861   if (rewritten_url)
1862     {
1863       strncpy (rewritten_storage, rewritten_url, sizeof(rewritten_storage));
1864       rewritten_storage[sizeof (rewritten_storage) - 1] = '\0';
1865       proxy = rewritten_storage;
1866     }
1867
1868   return proxy;
1869 }
1870
1871 /* Should a host be accessed through proxy, concerning no_proxy?  */
1872 int
1873 no_proxy_match (const char *host, const char **no_proxy)
1874 {
1875   if (!no_proxy)
1876     return 1;
1877   else
1878     return !sufmatch (no_proxy, host);
1879 }
1880 \f
1881 /* Support for converting links for local viewing in downloaded HTML
1882    files.  This should be moved to another file, because it has
1883    nothing to do with processing URLs.  */
1884
1885 static void write_backup_file PARAMS ((const char *, downloaded_file_t));
1886 static const char *replace_attr PARAMS ((const char *, int, FILE *,
1887                                          const char *));
1888 static const char *replace_attr_refresh_hack PARAMS ((const char *, int, FILE *,
1889                                                       const char *, int));
1890 static char *local_quote_string PARAMS ((const char *));
1891
1892 /* Change the links in one HTML file.  LINKS is a list of links in the
1893    document, along with their positions and the desired direction of
1894    the conversion.  */
1895 void
1896 convert_links (const char *file, struct urlpos *links)
1897 {
1898   struct file_memory *fm;
1899   FILE *fp;
1900   const char *p;
1901   downloaded_file_t downloaded_file_return;
1902
1903   struct urlpos *link;
1904   int to_url_count = 0, to_file_count = 0;
1905
1906   logprintf (LOG_VERBOSE, _("Converting %s... "), file);
1907
1908   {
1909     /* First we do a "dry run": go through the list L and see whether
1910        any URL needs to be converted in the first place.  If not, just
1911        leave the file alone.  */
1912     int dry_count = 0;
1913     struct urlpos *dry = links;
1914     for (dry = links; dry; dry = dry->next)
1915       if (dry->convert != CO_NOCONVERT)
1916         ++dry_count;
1917     if (!dry_count)
1918       {
1919         logputs (LOG_VERBOSE, _("nothing to do.\n"));
1920         return;
1921       }
1922   }
1923
1924   fm = read_file (file);
1925   if (!fm)
1926     {
1927       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1928                  file, strerror (errno));
1929       return;
1930     }
1931
1932   downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
1933   if (opt.backup_converted && downloaded_file_return)
1934     write_backup_file (file, downloaded_file_return);
1935
1936   /* Before opening the file for writing, unlink the file.  This is
1937      important if the data in FM is mmaped.  In such case, nulling the
1938      file, which is what fopen() below does, would make us read all
1939      zeroes from the mmaped region.  */
1940   if (unlink (file) < 0 && errno != ENOENT)
1941     {
1942       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
1943                  file, strerror (errno));
1944       read_file_free (fm);
1945       return;
1946     }
1947   /* Now open the file for writing.  */
1948   fp = fopen (file, "wb");
1949   if (!fp)
1950     {
1951       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1952                  file, strerror (errno));
1953       read_file_free (fm);
1954       return;
1955     }
1956
1957   /* Here we loop through all the URLs in file, replacing those of
1958      them that are downloaded with relative references.  */
1959   p = fm->content;
1960   for (link = links; link; link = link->next)
1961     {
1962       char *url_start = fm->content + link->pos;
1963
1964       if (link->pos >= fm->length)
1965         {
1966           DEBUGP (("Something strange is going on.  Please investigate."));
1967           break;
1968         }
1969       /* If the URL is not to be converted, skip it.  */
1970       if (link->convert == CO_NOCONVERT)
1971         {
1972           DEBUGP (("Skipping %s at position %d.\n", link->url->url, link->pos));
1973           continue;
1974         }
1975
1976       /* Echo the file contents, up to the offending URL's opening
1977          quote, to the outfile.  */
1978       fwrite (p, 1, url_start - p, fp);
1979       p = url_start;
1980
1981       switch (link->convert)
1982         {
1983         case CO_CONVERT_TO_RELATIVE:
1984           /* Convert absolute URL to relative. */
1985           {
1986             char *newname = construct_relative (file, link->local_name);
1987             char *quoted_newname = local_quote_string (newname);
1988
1989             if (!link->link_refresh_p)
1990               p = replace_attr (p, link->size, fp, quoted_newname);
1991             else
1992               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newname,
1993                                              link->refresh_timeout);
1994
1995             DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
1996                      link->url->url, newname, link->pos, file));
1997             xfree (newname);
1998             xfree (quoted_newname);
1999             ++to_file_count;
2000             break;
2001           }
2002         case CO_CONVERT_TO_COMPLETE:
2003           /* Convert the link to absolute URL. */
2004           {
2005             char *newlink = link->url->url;
2006             char *quoted_newlink = html_quote_string (newlink);
2007
2008             if (!link->link_refresh_p)
2009               p = replace_attr (p, link->size, fp, quoted_newlink);
2010             else
2011               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newlink,
2012                                              link->refresh_timeout);
2013
2014             DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
2015                      newlink, link->pos, file));
2016             xfree (quoted_newlink);
2017             ++to_url_count;
2018             break;
2019           }
2020         case CO_NULLIFY_BASE:
2021           /* Change the base href to "". */
2022           p = replace_attr (p, link->size, fp, "");
2023           break;
2024         case CO_NOCONVERT:
2025           abort ();
2026           break;
2027         }
2028     }
2029
2030   /* Output the rest of the file. */
2031   if (p - fm->content < fm->length)
2032     fwrite (p, 1, fm->length - (p - fm->content), fp);
2033   fclose (fp);
2034   read_file_free (fm);
2035
2036   logprintf (LOG_VERBOSE, "%d-%d\n", to_file_count, to_url_count);
2037 }
2038
2039 /* Construct and return a malloced copy of the relative link from two
2040    pieces of information: local name S1 of the referring file and
2041    local name S2 of the referred file.
2042
2043    So, if S1 is "jagor.srce.hr/index.html" and S2 is
2044    "jagor.srce.hr/images/news.gif", the function will return
2045    "images/news.gif".
2046
2047    Alternately, if S1 is "fly.cc.fer.hr/ioccc/index.html", and S2 is
2048    "fly.cc.fer.hr/images/fly.gif", the function will return
2049    "../images/fly.gif".
2050
2051    Caveats: S1 should not begin with `/', unless S2 also begins with
2052    '/'.  S1 should not contain things like ".." and such --
2053    construct_relative ("fly/ioccc/../index.html",
2054    "fly/images/fly.gif") will fail.  (A workaround is to call
2055    something like path_simplify() on S1).  */
2056 static char *
2057 construct_relative (const char *s1, const char *s2)
2058 {
2059   int i, cnt, sepdirs1;
2060   char *res;
2061
2062   if (*s2 == '/')
2063     return xstrdup (s2);
2064   /* S1 should *not* be absolute, if S2 wasn't.  */
2065   assert (*s1 != '/');
2066   i = cnt = 0;
2067   /* Skip the directories common to both strings.  */
2068   while (1)
2069     {
2070       while (s1[i] && s2[i]
2071              && (s1[i] == s2[i])
2072              && (s1[i] != '/')
2073              && (s2[i] != '/'))
2074         ++i;
2075       if (s1[i] == '/' && s2[i] == '/')
2076         cnt = ++i;
2077       else
2078         break;
2079     }
2080   for (sepdirs1 = 0; s1[i]; i++)
2081     if (s1[i] == '/')
2082       ++sepdirs1;
2083   /* Now, construct the file as of:
2084      - ../ repeated sepdirs1 time
2085      - all the non-mutual directories of S2.  */
2086   res = (char *)xmalloc (3 * sepdirs1 + strlen (s2 + cnt) + 1);
2087   for (i = 0; i < sepdirs1; i++)
2088     memcpy (res + 3 * i, "../", 3);
2089   strcpy (res + 3 * i, s2 + cnt);
2090   return res;
2091 }
2092 \f
2093 static void
2094 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
2095 {
2096   /* Rather than just writing over the original .html file with the
2097      converted version, save the former to *.orig.  Note we only do
2098      this for files we've _successfully_ downloaded, so we don't
2099      clobber .orig files sitting around from previous invocations. */
2100
2101   /* Construct the backup filename as the original name plus ".orig". */
2102   size_t         filename_len = strlen(file);
2103   char*          filename_plus_orig_suffix;
2104   boolean        already_wrote_backup_file = FALSE;
2105   slist*         converted_file_ptr;
2106   static slist*  converted_files = NULL;
2107
2108   if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
2109     {
2110       /* Just write "orig" over "html".  We need to do it this way
2111          because when we're checking to see if we've downloaded the
2112          file before (to see if we can skip downloading it), we don't
2113          know if it's a text/html file.  Therefore we don't know yet
2114          at that stage that -E is going to cause us to tack on
2115          ".html", so we need to compare vs. the original URL plus
2116          ".orig", not the original URL plus ".html.orig". */
2117       filename_plus_orig_suffix = alloca (filename_len + 1);
2118       strcpy(filename_plus_orig_suffix, file);
2119       strcpy((filename_plus_orig_suffix + filename_len) - 4, "orig");
2120     }
2121   else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
2122     {
2123       /* Append ".orig" to the name. */
2124       filename_plus_orig_suffix = alloca (filename_len + sizeof(".orig"));
2125       strcpy(filename_plus_orig_suffix, file);
2126       strcpy(filename_plus_orig_suffix + filename_len, ".orig");
2127     }
2128
2129   /* We can get called twice on the same URL thanks to the
2130      convert_all_links() call in main().  If we write the .orig file
2131      each time in such a case, it'll end up containing the first-pass
2132      conversion, not the original file.  So, see if we've already been
2133      called on this file. */
2134   converted_file_ptr = converted_files;
2135   while (converted_file_ptr != NULL)
2136     if (strcmp(converted_file_ptr->string, file) == 0)
2137       {
2138         already_wrote_backup_file = TRUE;
2139         break;
2140       }
2141     else
2142       converted_file_ptr = converted_file_ptr->next;
2143
2144   if (!already_wrote_backup_file)
2145     {
2146       /* Rename <file> to <file>.orig before former gets written over. */
2147       if (rename(file, filename_plus_orig_suffix) != 0)
2148         logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
2149                    file, filename_plus_orig_suffix, strerror (errno));
2150
2151       /* Remember that we've already written a .orig backup for this file.
2152          Note that we never free this memory since we need it till the
2153          convert_all_links() call, which is one of the last things the
2154          program does before terminating.  BTW, I'm not sure if it would be
2155          safe to just set 'converted_file_ptr->string' to 'file' below,
2156          rather than making a copy of the string...  Another note is that I
2157          thought I could just add a field to the urlpos structure saying
2158          that we'd written a .orig file for this URL, but that didn't work,
2159          so I had to make this separate list.
2160          -- Dan Harkless <wget@harkless.org>
2161
2162          This [adding a field to the urlpos structure] didn't work
2163          because convert_file() is called from convert_all_links at
2164          the end of the retrieval with a freshly built new urlpos
2165          list.
2166          -- Hrvoje Niksic <hniksic@arsdigita.com>
2167       */
2168       converted_file_ptr = xmalloc(sizeof(*converted_file_ptr));
2169       converted_file_ptr->string = xstrdup(file);  /* die on out-of-mem. */
2170       converted_file_ptr->next = converted_files;
2171       converted_files = converted_file_ptr;
2172     }
2173 }
2174
2175 static int find_fragment PARAMS ((const char *, int, const char **,
2176                                   const char **));
2177
2178 /* Replace an attribute's original text with NEW_TEXT. */
2179
2180 static const char *
2181 replace_attr (const char *p, int size, FILE *fp, const char *new_text)
2182 {
2183   int quote_flag = 0;
2184   char quote_char = '\"';       /* use "..." for quoting, unless the
2185                                    original value is quoted, in which
2186                                    case reuse its quoting char. */
2187   const char *frag_beg, *frag_end;
2188
2189   /* Structure of our string is:
2190        "...old-contents..."
2191        <---    size    --->  (with quotes)
2192      OR:
2193        ...old-contents...
2194        <---    size   -->    (no quotes)   */
2195
2196   if (*p == '\"' || *p == '\'')
2197     {
2198       quote_char = *p;
2199       quote_flag = 1;
2200       ++p;
2201       size -= 2;                /* disregard opening and closing quote */
2202     }
2203   putc (quote_char, fp);
2204   fputs (new_text, fp);
2205
2206   /* Look for fragment identifier, if any. */
2207   if (find_fragment (p, size, &frag_beg, &frag_end))
2208     fwrite (frag_beg, 1, frag_end - frag_beg, fp);
2209   p += size;
2210   if (quote_flag)
2211     ++p;
2212   putc (quote_char, fp);
2213
2214   return p;
2215 }
2216
2217 /* The same as REPLACE_ATTR, but used when replacing
2218    <meta http-equiv=refresh content="new_text"> because we need to
2219    append "timeout_value; URL=" before the next_text.  */
2220
2221 static const char *
2222 replace_attr_refresh_hack (const char *p, int size, FILE *fp,
2223                            const char *new_text, int timeout)
2224 {
2225   /* "0; URL=..." */
2226   char *new_with_timeout = (char *)alloca (numdigit (timeout)
2227                                            + 6 /* "; URL=" */
2228                                            + strlen (new_text)
2229                                            + 1);
2230   sprintf (new_with_timeout, "%d; URL=%s", timeout, new_text);
2231
2232   return replace_attr (p, size, fp, new_with_timeout);
2233 }
2234
2235 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
2236    preceded by '&'.  If the character is not found, return zero.  If
2237    the character is found, return 1 and set BP and EP to point to the
2238    beginning and end of the region.
2239
2240    This is used for finding the fragment indentifiers in URLs.  */
2241
2242 static int
2243 find_fragment (const char *beg, int size, const char **bp, const char **ep)
2244 {
2245   const char *end = beg + size;
2246   int saw_amp = 0;
2247   for (; beg < end; beg++)
2248     {
2249       switch (*beg)
2250         {
2251         case '&':
2252           saw_amp = 1;
2253           break;
2254         case '#':
2255           if (!saw_amp)
2256             {
2257               *bp = beg;
2258               *ep = end;
2259               return 1;
2260             }
2261           /* fallthrough */
2262         default:
2263           saw_amp = 0;
2264         }
2265     }
2266   return 0;
2267 }
2268
2269 /* Quote FILE for use as local reference to an HTML file.
2270
2271    We quote ? as %3F to avoid passing part of the file name as the
2272    parameter when browsing the converted file through HTTP.  However,
2273    it is safe to do this only when `--html-extension' is turned on.
2274    This is because converting "index.html?foo=bar" to
2275    "index.html%3Ffoo=bar" would break local browsing, as the latter
2276    isn't even recognized as an HTML file!  However, converting
2277    "index.html?foo=bar.html" to "index.html%3Ffoo=bar.html" should be
2278    safe for both local and HTTP-served browsing.  */
2279
2280 static char *
2281 local_quote_string (const char *file)
2282 {
2283   const char *file_sans_qmark;
2284   int qm;
2285
2286   if (!opt.html_extension)
2287     return html_quote_string (file);
2288
2289   qm = count_char (file, '?');
2290
2291   if (qm)
2292     {
2293       const char *from = file;
2294       char *to, *newname;
2295
2296       /* qm * 2 because we replace each question mark with "%3F",
2297          i.e. replace one char with three, hence two more.  */
2298       int fsqlen = strlen (file) + qm * 2;
2299
2300       to = newname = (char *)alloca (fsqlen + 1);
2301       for (; *from; from++)
2302         {
2303           if (*from != '?')
2304             *to++ = *from;
2305           else
2306             {
2307               *to++ = '%';
2308               *to++ = '3';
2309               *to++ = 'F';
2310             }
2311         }
2312       assert (to - newname == fsqlen);
2313       *to = '\0';
2314
2315       file_sans_qmark = newname;
2316     }
2317   else
2318     file_sans_qmark = file;
2319
2320   return html_quote_string (file_sans_qmark);
2321 }
2322
2323 /* We're storing "modes" of type downloaded_file_t in the hash table.
2324    However, our hash tables only accept pointers for keys and values.
2325    So when we need a pointer, we use the address of a
2326    downloaded_file_t variable of static storage.  */
2327    
2328 static downloaded_file_t *
2329 downloaded_mode_to_ptr (downloaded_file_t mode)
2330 {
2331   static downloaded_file_t
2332     v1 = FILE_NOT_ALREADY_DOWNLOADED,
2333     v2 = FILE_DOWNLOADED_NORMALLY,
2334     v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
2335     v4 = CHECK_FOR_FILE;
2336
2337   switch (mode)
2338     {
2339     case FILE_NOT_ALREADY_DOWNLOADED:
2340       return &v1;
2341     case FILE_DOWNLOADED_NORMALLY:
2342       return &v2;
2343     case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
2344       return &v3;
2345     case CHECK_FOR_FILE:
2346       return &v4;
2347     }
2348   return NULL;
2349 }
2350
2351 /* This should really be merged with dl_file_url_map and
2352    downloaded_html_files in recur.c.  This was originally a list, but
2353    I changed it to a hash table beause it was actually taking a lot of
2354    time to find things in it.  */
2355
2356 static struct hash_table *downloaded_files_hash;
2357
2358 /* Remembers which files have been downloaded.  In the standard case, should be
2359    called with mode == FILE_DOWNLOADED_NORMALLY for each file we actually
2360    download successfully (i.e. not for ones we have failures on or that we skip
2361    due to -N).
2362
2363    When we've downloaded a file and tacked on a ".html" extension due to -E,
2364    call this function with FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
2365    FILE_DOWNLOADED_NORMALLY.
2366
2367    If you just want to check if a file has been previously added without adding
2368    it, call with mode == CHECK_FOR_FILE.  Please be sure to call this function
2369    with local filenames, not remote URLs. */
2370 downloaded_file_t
2371 downloaded_file (downloaded_file_t mode, const char *file)
2372 {
2373   downloaded_file_t *ptr;
2374
2375   if (mode == CHECK_FOR_FILE)
2376     {
2377       if (!downloaded_files_hash)
2378         return FILE_NOT_ALREADY_DOWNLOADED;
2379       ptr = hash_table_get (downloaded_files_hash, file);
2380       if (!ptr)
2381         return FILE_NOT_ALREADY_DOWNLOADED;
2382       return *ptr;
2383     }
2384
2385   if (!downloaded_files_hash)
2386     downloaded_files_hash = make_string_hash_table (0);
2387
2388   ptr = hash_table_get (downloaded_files_hash, file);
2389   if (ptr)
2390     return *ptr;
2391
2392   ptr = downloaded_mode_to_ptr (mode);
2393   hash_table_put (downloaded_files_hash, xstrdup (file), &ptr);
2394
2395   return FILE_NOT_ALREADY_DOWNLOADED;
2396 }
2397
2398 static int
2399 df_free_mapper (void *key, void *value, void *ignored)
2400 {
2401   xfree (key);
2402   return 0;
2403 }
2404
2405 void
2406 downloaded_files_free (void)
2407 {
2408   if (downloaded_files_hash)
2409     {
2410       hash_table_map (downloaded_files_hash, df_free_mapper, NULL);
2411       hash_table_destroy (downloaded_files_hash);
2412       downloaded_files_hash = NULL;
2413     }
2414 }
2415 \f
2416 #if 0
2417 /* Debugging and testing support for path_simplify. */
2418
2419 /* Debug: run path_simplify on PATH and return the result in a new
2420    string.  Useful for calling from the debugger.  */
2421 static char *
2422 ps (char *path)
2423 {
2424   char *copy = xstrdup (path);
2425   path_simplify (copy);
2426   return copy;
2427 }
2428
2429 static void
2430 run_test (char *test, char *expected_result, int expected_change)
2431 {
2432   char *test_copy = xstrdup (test);
2433   int modified = path_simplify (test_copy);
2434
2435   if (0 != strcmp (test_copy, expected_result))
2436     {
2437       printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
2438               test, expected_result, test_copy);
2439     }
2440   if (modified != expected_change)
2441     {
2442       if (expected_change == 1)
2443         printf ("Expected no modification with path_simplify(\"%s\").\n",
2444                 test);
2445       else
2446         printf ("Expected modification with path_simplify(\"%s\").\n",
2447                 test);
2448     }
2449   xfree (test_copy);
2450 }
2451
2452 static void
2453 test_path_simplify (void)
2454 {
2455   static struct {
2456     char *test, *result;
2457     int should_modify;
2458   } tests[] = {
2459     { "",               "",             0 },
2460     { ".",              "",             1 },
2461     { "..",             "",             1 },
2462     { "foo",            "foo",          0 },
2463     { "foo/bar",        "foo/bar",      0 },
2464     { "foo///bar",      "foo/bar",      1 },
2465     { "foo/.",          "foo/",         1 },
2466     { "foo/./",         "foo/",         1 },
2467     { "foo./",          "foo./",        0 },
2468     { "foo/../bar",     "bar",          1 },
2469     { "foo/../bar/",    "bar/",         1 },
2470     { "foo/bar/..",     "foo/",         1 },
2471     { "foo/bar/../x",   "foo/x",        1 },
2472     { "foo/bar/../x/",  "foo/x/",       1 },
2473     { "foo/..",         "",             1 },
2474     { "foo/../..",      "",             1 },
2475     { "a/b/../../c",    "c",            1 },
2476     { "./a/../b",       "b",            1 }
2477   };
2478   int i;
2479
2480   for (i = 0; i < ARRAY_SIZE (tests); i++)
2481     {
2482       char *test = tests[i].test;
2483       char *expected_result = tests[i].result;
2484       int   expected_change = tests[i].should_modify;
2485       run_test (test, expected_result, expected_change);
2486     }
2487
2488   /* Now run all the tests with a leading slash before the test case,
2489      to prove that the slash is being preserved.  */
2490   for (i = 0; i < ARRAY_SIZE (tests); i++)
2491     {
2492       char *test, *expected_result;
2493       int expected_change = tests[i].should_modify;
2494
2495       test = xmalloc (1 + strlen (tests[i].test) + 1);
2496       sprintf (test, "/%s", tests[i].test);
2497
2498       expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
2499       sprintf (expected_result, "/%s", tests[i].result);
2500
2501       run_test (test, expected_result, expected_change);
2502
2503       xfree (test);
2504       xfree (expected_result);
2505     }
2506 }
2507 #endif