]> sjero.net Git - wget/blob - src/url.c
[svn] Move path_simplify to url.c.
[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 (*link == '/')
1579         {
1580           /* LINK is an absolute path: we need to replace everything
1581              after (and including) the FIRST slash with LINK.
1582
1583              So, if BASE is "http://host/whatever/foo/bar", and LINK is
1584              "/qux/xyzzy", our result should be
1585              "http://host/qux/xyzzy".  */
1586           int span;
1587           const char *slash;
1588           const char *start_insert = NULL; /* for gcc to shut up. */
1589           const char *pos = base;
1590           int seen_slash_slash = 0;
1591           /* We're looking for the first slash, but want to ignore
1592              double slash. */
1593         again:
1594           slash = memchr (pos, '/', end - pos);
1595           if (slash && !seen_slash_slash)
1596             if (*(slash + 1) == '/')
1597               {
1598                 pos = slash + 2;
1599                 seen_slash_slash = 1;
1600                 goto again;
1601               }
1602
1603           /* At this point, SLASH is the location of the first / after
1604              "//", or the first slash altogether.  START_INSERT is the
1605              pointer to the location where LINK will be inserted.  When
1606              examining the last two examples, keep in mind that LINK
1607              begins with '/'. */
1608
1609           if (!slash && !seen_slash_slash)
1610             /* example: "foo" */
1611             /*           ^    */
1612             start_insert = base;
1613           else if (!slash && seen_slash_slash)
1614             /* example: "http://foo" */
1615             /*                     ^ */
1616             start_insert = end;
1617           else if (slash && !seen_slash_slash)
1618             /* example: "foo/bar" */
1619             /*           ^        */
1620             start_insert = base;
1621           else if (slash && seen_slash_slash)
1622             /* example: "http://something/" */
1623             /*                           ^  */
1624             start_insert = slash;
1625
1626           span = start_insert - base;
1627           constr = (char *)xmalloc (span + linklength + 1);
1628           if (span)
1629             memcpy (constr, base, span);
1630           if (linklength)
1631             memcpy (constr + span, link, linklength);
1632           constr[span + linklength] = '\0';
1633         }
1634       else
1635         {
1636           /* LINK is a relative URL: we need to replace everything
1637              after last slash (possibly empty) with LINK.
1638
1639              So, if BASE is "whatever/foo/bar", and LINK is "qux/xyzzy",
1640              our result should be "whatever/foo/qux/xyzzy".  */
1641           int need_explicit_slash = 0;
1642           int span;
1643           const char *start_insert;
1644           const char *last_slash = find_last_char (base, end, '/');
1645           if (!last_slash)
1646             {
1647               /* No slash found at all.  Append LINK to what we have,
1648                  but we'll need a slash as a separator.
1649
1650                  Example: if base == "foo" and link == "qux/xyzzy", then
1651                  we cannot just append link to base, because we'd get
1652                  "fooqux/xyzzy", whereas what we want is
1653                  "foo/qux/xyzzy".
1654
1655                  To make sure the / gets inserted, we set
1656                  need_explicit_slash to 1.  We also set start_insert
1657                  to end + 1, so that the length calculations work out
1658                  correctly for one more (slash) character.  Accessing
1659                  that character is fine, since it will be the
1660                  delimiter, '\0' or '?'.  */
1661               /* example: "foo?..." */
1662               /*               ^    ('?' gets changed to '/') */
1663               start_insert = end + 1;
1664               need_explicit_slash = 1;
1665             }
1666           else if (last_slash && last_slash != base && *(last_slash - 1) == '/')
1667             {
1668               /* example: http://host"  */
1669               /*                      ^ */
1670               start_insert = end + 1;
1671               need_explicit_slash = 1;
1672             }
1673           else
1674             {
1675               /* example: "whatever/foo/bar" */
1676               /*                        ^    */
1677               start_insert = last_slash + 1;
1678             }
1679
1680           span = start_insert - base;
1681           constr = (char *)xmalloc (span + linklength + 1);
1682           if (span)
1683             memcpy (constr, base, span);
1684           if (need_explicit_slash)
1685             constr[span - 1] = '/';
1686           if (linklength)
1687             memcpy (constr + span, link, linklength);
1688           constr[span + linklength] = '\0';
1689         }
1690     }
1691   else /* !no_scheme */
1692     {
1693       constr = strdupdelim (link, link + linklength);
1694     }
1695   return constr;
1696 }
1697
1698 /* Merge BASE with LINK and return the resulting URI.  This is an
1699    interface to uri_merge_1 that assumes that LINK is a
1700    zero-terminated string.  */
1701 char *
1702 uri_merge (const char *base, const char *link)
1703 {
1704   return uri_merge_1 (base, link, strlen (link), !url_has_scheme (link));
1705 }
1706 \f
1707 #define APPEND(p, s) do {                       \
1708   int len = strlen (s);                         \
1709   memcpy (p, s, len);                           \
1710   p += len;                                     \
1711 } while (0)
1712
1713 /* Use this instead of password when the actual password is supposed
1714    to be hidden.  We intentionally use a generic string without giving
1715    away the number of characters in the password, like previous
1716    versions did.  */
1717 #define HIDDEN_PASSWORD "*password*"
1718
1719 /* Recreate the URL string from the data in URL.
1720
1721    If HIDE is non-zero (as it is when we're calling this on a URL we
1722    plan to print, but not when calling it to canonicalize a URL for
1723    use within the program), password will be hidden.  Unsafe
1724    characters in the URL will be quoted.  */
1725
1726 char *
1727 url_string (const struct url *url, int hide_password)
1728 {
1729   int size;
1730   char *result, *p;
1731   char *quoted_user = NULL, *quoted_passwd = NULL;
1732
1733   int scheme_port  = supported_schemes[url->scheme].default_port;
1734   char *scheme_str = supported_schemes[url->scheme].leading_string;
1735   int fplen = full_path_length (url);
1736
1737   assert (scheme_str != NULL);
1738
1739   /* Make sure the user name and password are quoted. */
1740   if (url->user)
1741     {
1742       quoted_user = encode_string_maybe (url->user);
1743       if (url->passwd)
1744         {
1745           if (hide_password)
1746             quoted_passwd = HIDDEN_PASSWORD;
1747           else
1748             quoted_passwd = encode_string_maybe (url->passwd);
1749         }
1750     }
1751
1752   size = (strlen (scheme_str)
1753           + strlen (url->host)
1754           + fplen
1755           + 1);
1756   if (url->port != scheme_port)
1757     size += 1 + numdigit (url->port);
1758   if (quoted_user)
1759     {
1760       size += 1 + strlen (quoted_user);
1761       if (quoted_passwd)
1762         size += 1 + strlen (quoted_passwd);
1763     }
1764
1765   p = result = xmalloc (size);
1766
1767   APPEND (p, scheme_str);
1768   if (quoted_user)
1769     {
1770       APPEND (p, quoted_user);
1771       if (quoted_passwd)
1772         {
1773           *p++ = ':';
1774           APPEND (p, quoted_passwd);
1775         }
1776       *p++ = '@';
1777     }
1778
1779   APPEND (p, url->host);
1780   if (url->port != scheme_port)
1781     {
1782       *p++ = ':';
1783       p = number_to_string (p, url->port);
1784     }
1785
1786   full_path_write (url, p);
1787   p += fplen;
1788   *p++ = '\0';
1789
1790   assert (p - result == size);
1791
1792   if (quoted_user && quoted_user != url->user)
1793     xfree (quoted_user);
1794   if (quoted_passwd && !hide_password
1795       && quoted_passwd != url->passwd)
1796     xfree (quoted_passwd);
1797
1798   return result;
1799 }
1800 \f
1801 /* Returns proxy host address, in accordance with SCHEME.  */
1802 char *
1803 getproxy (enum url_scheme scheme)
1804 {
1805   char *proxy = NULL;
1806   char *rewritten_url;
1807   static char rewritten_storage[1024];
1808
1809   switch (scheme)
1810     {
1811     case SCHEME_HTTP:
1812       proxy = opt.http_proxy ? opt.http_proxy : getenv ("http_proxy");
1813       break;
1814 #ifdef HAVE_SSL
1815     case SCHEME_HTTPS:
1816       proxy = opt.https_proxy ? opt.https_proxy : getenv ("https_proxy");
1817       break;
1818 #endif
1819     case SCHEME_FTP:
1820       proxy = opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftp_proxy");
1821       break;
1822     case SCHEME_INVALID:
1823       break;
1824     }
1825   if (!proxy || !*proxy)
1826     return NULL;
1827
1828   /* Handle shorthands. */
1829   rewritten_url = rewrite_shorthand_url (proxy);
1830   if (rewritten_url)
1831     {
1832       strncpy (rewritten_storage, rewritten_url, sizeof(rewritten_storage));
1833       rewritten_storage[sizeof (rewritten_storage) - 1] = '\0';
1834       proxy = rewritten_storage;
1835     }
1836
1837   return proxy;
1838 }
1839
1840 /* Should a host be accessed through proxy, concerning no_proxy?  */
1841 int
1842 no_proxy_match (const char *host, const char **no_proxy)
1843 {
1844   if (!no_proxy)
1845     return 1;
1846   else
1847     return !sufmatch (no_proxy, host);
1848 }
1849 \f
1850 /* Support for converting links for local viewing in downloaded HTML
1851    files.  This should be moved to another file, because it has
1852    nothing to do with processing URLs.  */
1853
1854 static void write_backup_file PARAMS ((const char *, downloaded_file_t));
1855 static const char *replace_attr PARAMS ((const char *, int, FILE *,
1856                                          const char *));
1857 static const char *replace_attr_refresh_hack PARAMS ((const char *, int, FILE *,
1858                                                       const char *, int));
1859 static char *local_quote_string PARAMS ((const char *));
1860
1861 /* Change the links in one HTML file.  LINKS is a list of links in the
1862    document, along with their positions and the desired direction of
1863    the conversion.  */
1864 void
1865 convert_links (const char *file, struct urlpos *links)
1866 {
1867   struct file_memory *fm;
1868   FILE *fp;
1869   const char *p;
1870   downloaded_file_t downloaded_file_return;
1871
1872   struct urlpos *link;
1873   int to_url_count = 0, to_file_count = 0;
1874
1875   logprintf (LOG_VERBOSE, _("Converting %s... "), file);
1876
1877   {
1878     /* First we do a "dry run": go through the list L and see whether
1879        any URL needs to be converted in the first place.  If not, just
1880        leave the file alone.  */
1881     int dry_count = 0;
1882     struct urlpos *dry = links;
1883     for (dry = links; dry; dry = dry->next)
1884       if (dry->convert != CO_NOCONVERT)
1885         ++dry_count;
1886     if (!dry_count)
1887       {
1888         logputs (LOG_VERBOSE, _("nothing to do.\n"));
1889         return;
1890       }
1891   }
1892
1893   fm = read_file (file);
1894   if (!fm)
1895     {
1896       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1897                  file, strerror (errno));
1898       return;
1899     }
1900
1901   downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
1902   if (opt.backup_converted && downloaded_file_return)
1903     write_backup_file (file, downloaded_file_return);
1904
1905   /* Before opening the file for writing, unlink the file.  This is
1906      important if the data in FM is mmaped.  In such case, nulling the
1907      file, which is what fopen() below does, would make us read all
1908      zeroes from the mmaped region.  */
1909   if (unlink (file) < 0 && errno != ENOENT)
1910     {
1911       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
1912                  file, strerror (errno));
1913       read_file_free (fm);
1914       return;
1915     }
1916   /* Now open the file for writing.  */
1917   fp = fopen (file, "wb");
1918   if (!fp)
1919     {
1920       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1921                  file, strerror (errno));
1922       read_file_free (fm);
1923       return;
1924     }
1925
1926   /* Here we loop through all the URLs in file, replacing those of
1927      them that are downloaded with relative references.  */
1928   p = fm->content;
1929   for (link = links; link; link = link->next)
1930     {
1931       char *url_start = fm->content + link->pos;
1932
1933       if (link->pos >= fm->length)
1934         {
1935           DEBUGP (("Something strange is going on.  Please investigate."));
1936           break;
1937         }
1938       /* If the URL is not to be converted, skip it.  */
1939       if (link->convert == CO_NOCONVERT)
1940         {
1941           DEBUGP (("Skipping %s at position %d.\n", link->url->url, link->pos));
1942           continue;
1943         }
1944
1945       /* Echo the file contents, up to the offending URL's opening
1946          quote, to the outfile.  */
1947       fwrite (p, 1, url_start - p, fp);
1948       p = url_start;
1949
1950       switch (link->convert)
1951         {
1952         case CO_CONVERT_TO_RELATIVE:
1953           /* Convert absolute URL to relative. */
1954           {
1955             char *newname = construct_relative (file, link->local_name);
1956             char *quoted_newname = local_quote_string (newname);
1957
1958             if (!link->link_refresh_p)
1959               p = replace_attr (p, link->size, fp, quoted_newname);
1960             else
1961               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newname,
1962                                              link->refresh_timeout);
1963
1964             DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
1965                      link->url->url, newname, link->pos, file));
1966             xfree (newname);
1967             xfree (quoted_newname);
1968             ++to_file_count;
1969             break;
1970           }
1971         case CO_CONVERT_TO_COMPLETE:
1972           /* Convert the link to absolute URL. */
1973           {
1974             char *newlink = link->url->url;
1975             char *quoted_newlink = html_quote_string (newlink);
1976
1977             if (!link->link_refresh_p)
1978               p = replace_attr (p, link->size, fp, quoted_newlink);
1979             else
1980               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newlink,
1981                                              link->refresh_timeout);
1982
1983             DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
1984                      newlink, link->pos, file));
1985             xfree (quoted_newlink);
1986             ++to_url_count;
1987             break;
1988           }
1989         case CO_NULLIFY_BASE:
1990           /* Change the base href to "". */
1991           p = replace_attr (p, link->size, fp, "");
1992           break;
1993         case CO_NOCONVERT:
1994           abort ();
1995           break;
1996         }
1997     }
1998
1999   /* Output the rest of the file. */
2000   if (p - fm->content < fm->length)
2001     fwrite (p, 1, fm->length - (p - fm->content), fp);
2002   fclose (fp);
2003   read_file_free (fm);
2004
2005   logprintf (LOG_VERBOSE, "%d-%d\n", to_file_count, to_url_count);
2006 }
2007
2008 /* Construct and return a malloced copy of the relative link from two
2009    pieces of information: local name S1 of the referring file and
2010    local name S2 of the referred file.
2011
2012    So, if S1 is "jagor.srce.hr/index.html" and S2 is
2013    "jagor.srce.hr/images/news.gif", the function will return
2014    "images/news.gif".
2015
2016    Alternately, if S1 is "fly.cc.fer.hr/ioccc/index.html", and S2 is
2017    "fly.cc.fer.hr/images/fly.gif", the function will return
2018    "../images/fly.gif".
2019
2020    Caveats: S1 should not begin with `/', unless S2 also begins with
2021    '/'.  S1 should not contain things like ".." and such --
2022    construct_relative ("fly/ioccc/../index.html",
2023    "fly/images/fly.gif") will fail.  (A workaround is to call
2024    something like path_simplify() on S1).  */
2025 static char *
2026 construct_relative (const char *s1, const char *s2)
2027 {
2028   int i, cnt, sepdirs1;
2029   char *res;
2030
2031   if (*s2 == '/')
2032     return xstrdup (s2);
2033   /* S1 should *not* be absolute, if S2 wasn't.  */
2034   assert (*s1 != '/');
2035   i = cnt = 0;
2036   /* Skip the directories common to both strings.  */
2037   while (1)
2038     {
2039       while (s1[i] && s2[i]
2040              && (s1[i] == s2[i])
2041              && (s1[i] != '/')
2042              && (s2[i] != '/'))
2043         ++i;
2044       if (s1[i] == '/' && s2[i] == '/')
2045         cnt = ++i;
2046       else
2047         break;
2048     }
2049   for (sepdirs1 = 0; s1[i]; i++)
2050     if (s1[i] == '/')
2051       ++sepdirs1;
2052   /* Now, construct the file as of:
2053      - ../ repeated sepdirs1 time
2054      - all the non-mutual directories of S2.  */
2055   res = (char *)xmalloc (3 * sepdirs1 + strlen (s2 + cnt) + 1);
2056   for (i = 0; i < sepdirs1; i++)
2057     memcpy (res + 3 * i, "../", 3);
2058   strcpy (res + 3 * i, s2 + cnt);
2059   return res;
2060 }
2061 \f
2062 static void
2063 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
2064 {
2065   /* Rather than just writing over the original .html file with the
2066      converted version, save the former to *.orig.  Note we only do
2067      this for files we've _successfully_ downloaded, so we don't
2068      clobber .orig files sitting around from previous invocations. */
2069
2070   /* Construct the backup filename as the original name plus ".orig". */
2071   size_t         filename_len = strlen(file);
2072   char*          filename_plus_orig_suffix;
2073   boolean        already_wrote_backup_file = FALSE;
2074   slist*         converted_file_ptr;
2075   static slist*  converted_files = NULL;
2076
2077   if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
2078     {
2079       /* Just write "orig" over "html".  We need to do it this way
2080          because when we're checking to see if we've downloaded the
2081          file before (to see if we can skip downloading it), we don't
2082          know if it's a text/html file.  Therefore we don't know yet
2083          at that stage that -E is going to cause us to tack on
2084          ".html", so we need to compare vs. the original URL plus
2085          ".orig", not the original URL plus ".html.orig". */
2086       filename_plus_orig_suffix = alloca (filename_len + 1);
2087       strcpy(filename_plus_orig_suffix, file);
2088       strcpy((filename_plus_orig_suffix + filename_len) - 4, "orig");
2089     }
2090   else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
2091     {
2092       /* Append ".orig" to the name. */
2093       filename_plus_orig_suffix = alloca (filename_len + sizeof(".orig"));
2094       strcpy(filename_plus_orig_suffix, file);
2095       strcpy(filename_plus_orig_suffix + filename_len, ".orig");
2096     }
2097
2098   /* We can get called twice on the same URL thanks to the
2099      convert_all_links() call in main().  If we write the .orig file
2100      each time in such a case, it'll end up containing the first-pass
2101      conversion, not the original file.  So, see if we've already been
2102      called on this file. */
2103   converted_file_ptr = converted_files;
2104   while (converted_file_ptr != NULL)
2105     if (strcmp(converted_file_ptr->string, file) == 0)
2106       {
2107         already_wrote_backup_file = TRUE;
2108         break;
2109       }
2110     else
2111       converted_file_ptr = converted_file_ptr->next;
2112
2113   if (!already_wrote_backup_file)
2114     {
2115       /* Rename <file> to <file>.orig before former gets written over. */
2116       if (rename(file, filename_plus_orig_suffix) != 0)
2117         logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
2118                    file, filename_plus_orig_suffix, strerror (errno));
2119
2120       /* Remember that we've already written a .orig backup for this file.
2121          Note that we never free this memory since we need it till the
2122          convert_all_links() call, which is one of the last things the
2123          program does before terminating.  BTW, I'm not sure if it would be
2124          safe to just set 'converted_file_ptr->string' to 'file' below,
2125          rather than making a copy of the string...  Another note is that I
2126          thought I could just add a field to the urlpos structure saying
2127          that we'd written a .orig file for this URL, but that didn't work,
2128          so I had to make this separate list.
2129          -- Dan Harkless <wget@harkless.org>
2130
2131          This [adding a field to the urlpos structure] didn't work
2132          because convert_file() is called from convert_all_links at
2133          the end of the retrieval with a freshly built new urlpos
2134          list.
2135          -- Hrvoje Niksic <hniksic@arsdigita.com>
2136       */
2137       converted_file_ptr = xmalloc(sizeof(*converted_file_ptr));
2138       converted_file_ptr->string = xstrdup(file);  /* die on out-of-mem. */
2139       converted_file_ptr->next = converted_files;
2140       converted_files = converted_file_ptr;
2141     }
2142 }
2143
2144 static int find_fragment PARAMS ((const char *, int, const char **,
2145                                   const char **));
2146
2147 /* Replace an attribute's original text with NEW_TEXT. */
2148
2149 static const char *
2150 replace_attr (const char *p, int size, FILE *fp, const char *new_text)
2151 {
2152   int quote_flag = 0;
2153   char quote_char = '\"';       /* use "..." for quoting, unless the
2154                                    original value is quoted, in which
2155                                    case reuse its quoting char. */
2156   const char *frag_beg, *frag_end;
2157
2158   /* Structure of our string is:
2159        "...old-contents..."
2160        <---    size    --->  (with quotes)
2161      OR:
2162        ...old-contents...
2163        <---    size   -->    (no quotes)   */
2164
2165   if (*p == '\"' || *p == '\'')
2166     {
2167       quote_char = *p;
2168       quote_flag = 1;
2169       ++p;
2170       size -= 2;                /* disregard opening and closing quote */
2171     }
2172   putc (quote_char, fp);
2173   fputs (new_text, fp);
2174
2175   /* Look for fragment identifier, if any. */
2176   if (find_fragment (p, size, &frag_beg, &frag_end))
2177     fwrite (frag_beg, 1, frag_end - frag_beg, fp);
2178   p += size;
2179   if (quote_flag)
2180     ++p;
2181   putc (quote_char, fp);
2182
2183   return p;
2184 }
2185
2186 /* The same as REPLACE_ATTR, but used when replacing
2187    <meta http-equiv=refresh content="new_text"> because we need to
2188    append "timeout_value; URL=" before the next_text.  */
2189
2190 static const char *
2191 replace_attr_refresh_hack (const char *p, int size, FILE *fp,
2192                            const char *new_text, int timeout)
2193 {
2194   /* "0; URL=..." */
2195   char *new_with_timeout = (char *)alloca (numdigit (timeout)
2196                                            + 6 /* "; URL=" */
2197                                            + strlen (new_text)
2198                                            + 1);
2199   sprintf (new_with_timeout, "%d; URL=%s", timeout, new_text);
2200
2201   return replace_attr (p, size, fp, new_with_timeout);
2202 }
2203
2204 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
2205    preceded by '&'.  If the character is not found, return zero.  If
2206    the character is found, return 1 and set BP and EP to point to the
2207    beginning and end of the region.
2208
2209    This is used for finding the fragment indentifiers in URLs.  */
2210
2211 static int
2212 find_fragment (const char *beg, int size, const char **bp, const char **ep)
2213 {
2214   const char *end = beg + size;
2215   int saw_amp = 0;
2216   for (; beg < end; beg++)
2217     {
2218       switch (*beg)
2219         {
2220         case '&':
2221           saw_amp = 1;
2222           break;
2223         case '#':
2224           if (!saw_amp)
2225             {
2226               *bp = beg;
2227               *ep = end;
2228               return 1;
2229             }
2230           /* fallthrough */
2231         default:
2232           saw_amp = 0;
2233         }
2234     }
2235   return 0;
2236 }
2237
2238 /* Quote FILE for use as local reference to an HTML file.
2239
2240    We quote ? as %3F to avoid passing part of the file name as the
2241    parameter when browsing the converted file through HTTP.  However,
2242    it is safe to do this only when `--html-extension' is turned on.
2243    This is because converting "index.html?foo=bar" to
2244    "index.html%3Ffoo=bar" would break local browsing, as the latter
2245    isn't even recognized as an HTML file!  However, converting
2246    "index.html?foo=bar.html" to "index.html%3Ffoo=bar.html" should be
2247    safe for both local and HTTP-served browsing.  */
2248
2249 static char *
2250 local_quote_string (const char *file)
2251 {
2252   const char *file_sans_qmark;
2253   int qm;
2254
2255   if (!opt.html_extension)
2256     return html_quote_string (file);
2257
2258   qm = count_char (file, '?');
2259
2260   if (qm)
2261     {
2262       const char *from = file;
2263       char *to, *newname;
2264
2265       /* qm * 2 because we replace each question mark with "%3F",
2266          i.e. replace one char with three, hence two more.  */
2267       int fsqlen = strlen (file) + qm * 2;
2268
2269       to = newname = (char *)alloca (fsqlen + 1);
2270       for (; *from; from++)
2271         {
2272           if (*from != '?')
2273             *to++ = *from;
2274           else
2275             {
2276               *to++ = '%';
2277               *to++ = '3';
2278               *to++ = 'F';
2279             }
2280         }
2281       assert (to - newname == fsqlen);
2282       *to = '\0';
2283
2284       file_sans_qmark = newname;
2285     }
2286   else
2287     file_sans_qmark = file;
2288
2289   return html_quote_string (file_sans_qmark);
2290 }
2291
2292 /* We're storing "modes" of type downloaded_file_t in the hash table.
2293    However, our hash tables only accept pointers for keys and values.
2294    So when we need a pointer, we use the address of a
2295    downloaded_file_t variable of static storage.  */
2296    
2297 static downloaded_file_t *
2298 downloaded_mode_to_ptr (downloaded_file_t mode)
2299 {
2300   static downloaded_file_t
2301     v1 = FILE_NOT_ALREADY_DOWNLOADED,
2302     v2 = FILE_DOWNLOADED_NORMALLY,
2303     v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
2304     v4 = CHECK_FOR_FILE;
2305
2306   switch (mode)
2307     {
2308     case FILE_NOT_ALREADY_DOWNLOADED:
2309       return &v1;
2310     case FILE_DOWNLOADED_NORMALLY:
2311       return &v2;
2312     case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
2313       return &v3;
2314     case CHECK_FOR_FILE:
2315       return &v4;
2316     }
2317   return NULL;
2318 }
2319
2320 /* This should really be merged with dl_file_url_map and
2321    downloaded_html_files in recur.c.  This was originally a list, but
2322    I changed it to a hash table beause it was actually taking a lot of
2323    time to find things in it.  */
2324
2325 static struct hash_table *downloaded_files_hash;
2326
2327 /* Remembers which files have been downloaded.  In the standard case, should be
2328    called with mode == FILE_DOWNLOADED_NORMALLY for each file we actually
2329    download successfully (i.e. not for ones we have failures on or that we skip
2330    due to -N).
2331
2332    When we've downloaded a file and tacked on a ".html" extension due to -E,
2333    call this function with FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
2334    FILE_DOWNLOADED_NORMALLY.
2335
2336    If you just want to check if a file has been previously added without adding
2337    it, call with mode == CHECK_FOR_FILE.  Please be sure to call this function
2338    with local filenames, not remote URLs. */
2339 downloaded_file_t
2340 downloaded_file (downloaded_file_t mode, const char *file)
2341 {
2342   downloaded_file_t *ptr;
2343
2344   if (mode == CHECK_FOR_FILE)
2345     {
2346       if (!downloaded_files_hash)
2347         return FILE_NOT_ALREADY_DOWNLOADED;
2348       ptr = hash_table_get (downloaded_files_hash, file);
2349       if (!ptr)
2350         return FILE_NOT_ALREADY_DOWNLOADED;
2351       return *ptr;
2352     }
2353
2354   if (!downloaded_files_hash)
2355     downloaded_files_hash = make_string_hash_table (0);
2356
2357   ptr = hash_table_get (downloaded_files_hash, file);
2358   if (ptr)
2359     return *ptr;
2360
2361   ptr = downloaded_mode_to_ptr (mode);
2362   hash_table_put (downloaded_files_hash, xstrdup (file), &ptr);
2363
2364   return FILE_NOT_ALREADY_DOWNLOADED;
2365 }
2366
2367 static int
2368 df_free_mapper (void *key, void *value, void *ignored)
2369 {
2370   xfree (key);
2371   return 0;
2372 }
2373
2374 void
2375 downloaded_files_free (void)
2376 {
2377   if (downloaded_files_hash)
2378     {
2379       hash_table_map (downloaded_files_hash, df_free_mapper, NULL);
2380       hash_table_destroy (downloaded_files_hash);
2381       downloaded_files_hash = NULL;
2382     }
2383 }
2384 \f
2385 #if 0
2386 /* Debugging and testing support for path_simplify. */
2387
2388 /* Debug: run path_simplify on PATH and return the result in a new
2389    string.  Useful for calling from the debugger.  */
2390 static char *
2391 ps (char *path)
2392 {
2393   char *copy = xstrdup (path);
2394   path_simplify (copy);
2395   return copy;
2396 }
2397
2398 static void
2399 run_test (char *test, char *expected_result, int expected_change)
2400 {
2401   char *test_copy = xstrdup (test);
2402   int modified = path_simplify (test_copy);
2403
2404   if (0 != strcmp (test_copy, expected_result))
2405     {
2406       printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
2407               test, expected_result, test_copy);
2408     }
2409   if (modified != expected_change)
2410     {
2411       if (expected_change == 1)
2412         printf ("Expected no modification with path_simplify(\"%s\").\n",
2413                 test);
2414       else
2415         printf ("Expected modification with path_simplify(\"%s\").\n",
2416                 test);
2417     }
2418   xfree (test_copy);
2419 }
2420
2421 static void
2422 test_path_simplify (void)
2423 {
2424   static struct {
2425     char *test, *result;
2426     int should_modify;
2427   } tests[] = {
2428     { "",               "",             0 },
2429     { ".",              "",             1 },
2430     { "..",             "",             1 },
2431     { "foo",            "foo",          0 },
2432     { "foo/bar",        "foo/bar",      0 },
2433     { "foo///bar",      "foo/bar",      1 },
2434     { "foo/.",          "foo/",         1 },
2435     { "foo/./",         "foo/",         1 },
2436     { "foo./",          "foo./",        0 },
2437     { "foo/../bar",     "bar",          1 },
2438     { "foo/../bar/",    "bar/",         1 },
2439     { "foo/bar/..",     "foo/",         1 },
2440     { "foo/bar/../x",   "foo/x",        1 },
2441     { "foo/bar/../x/",  "foo/x/",       1 },
2442     { "foo/..",         "",             1 },
2443     { "foo/../..",      "",             1 },
2444     { "a/b/../../c",    "c",            1 },
2445     { "./a/../b",       "b",            1 }
2446   };
2447   int i;
2448
2449   for (i = 0; i < ARRAY_SIZE (tests); i++)
2450     {
2451       char *test = tests[i].test;
2452       char *expected_result = tests[i].result;
2453       int   expected_change = tests[i].should_modify;
2454       run_test (test, expected_result, expected_change);
2455     }
2456
2457   /* Now run all the tests with a leading slash before the test case,
2458      to prove that the slash is being preserved.  */
2459   for (i = 0; i < ARRAY_SIZE (tests); i++)
2460     {
2461       char *test, *expected_result;
2462       int expected_change = tests[i].should_modify;
2463
2464       test = xmalloc (1 + strlen (tests[i].test) + 1);
2465       sprintf (test, "/%s", tests[i].test);
2466
2467       expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
2468       sprintf (expected_result, "/%s", tests[i].result);
2469
2470       run_test (test, expected_result, expected_change);
2471
2472       xfree (test);
2473       xfree (expected_result);
2474     }
2475 }
2476 #endif