]> sjero.net Git - wget/blob - src/convert.c
[svn] Make timers measure seconds directly, not milliseconds.
[wget] / src / convert.c
1 /* Conversion of links to local files.
2    Copyright (C) 2003-2005 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
9  (at 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 Foundation, Inc.,
18 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 #include <config.h>
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif /* HAVE_UNISTD_H */
38 #include <errno.h>
39 #include <assert.h>
40
41 #include "wget.h"
42 #include "convert.h"
43 #include "url.h"
44 #include "recur.h"
45 #include "utils.h"
46 #include "hash.h"
47 #include "ptimer.h"
48
49 static struct hash_table *dl_file_url_map;
50 struct hash_table *dl_url_file_map;
51
52 /* Set of HTML files downloaded in this Wget run, used for link
53    conversion after Wget is done.  */
54 struct hash_table *downloaded_html_set;
55
56 static void convert_links (const char *, struct urlpos *);
57
58 /* This function is called when the retrieval is done to convert the
59    links that have been downloaded.  It has to be called at the end of
60    the retrieval, because only then does Wget know conclusively which
61    URLs have been downloaded, and which not, so it can tell which
62    direction to convert to.
63
64    The "direction" means that the URLs to the files that have been
65    downloaded get converted to the relative URL which will point to
66    that file.  And the other URLs get converted to the remote URL on
67    the server.
68
69    All the downloaded HTMLs are kept in downloaded_html_files, and
70    downloaded URLs in urls_downloaded.  All the information is
71    extracted from these two lists.  */
72
73 void
74 convert_all_links (void)
75 {
76   int i;
77   double secs;
78   int file_count = 0;
79
80   struct ptimer *timer = ptimer_new ();
81
82   int cnt;
83   char **file_array;
84
85   cnt = 0;
86   if (downloaded_html_set)
87     cnt = hash_table_count (downloaded_html_set);
88   if (cnt == 0)
89     return;
90   file_array = alloca_array (char *, cnt);
91   string_set_to_array (downloaded_html_set, file_array);
92
93   for (i = 0; i < cnt; i++)
94     {
95       struct urlpos *urls, *cur_url;
96       char *url;
97       char *file = file_array[i];
98
99       /* Determine the URL of the HTML file.  get_urls_html will need
100          it.  */
101       url = hash_table_get (dl_file_url_map, file);
102       if (!url)
103         {
104           DEBUGP (("Apparently %s has been removed.\n", file));
105           continue;
106         }
107
108       DEBUGP (("Scanning %s (from %s)\n", file, url));
109
110       /* Parse the HTML file...  */
111       urls = get_urls_html (file, url, NULL);
112
113       /* We don't respect meta_disallow_follow here because, even if
114          the file is not followed, we might still want to convert the
115          links that have been followed from other files.  */
116
117       for (cur_url = urls; cur_url; cur_url = cur_url->next)
118         {
119           char *local_name;
120           struct url *u = cur_url->url;
121
122           if (cur_url->link_base_p)
123             {
124               /* Base references have been resolved by our parser, so
125                  we turn the base URL into an empty string.  (Perhaps
126                  we should remove the tag entirely?)  */
127               cur_url->convert = CO_NULLIFY_BASE;
128               continue;
129             }
130
131           /* We decide the direction of conversion according to whether
132              a URL was downloaded.  Downloaded URLs will be converted
133              ABS2REL, whereas non-downloaded will be converted REL2ABS.  */
134           local_name = hash_table_get (dl_url_file_map, u->url);
135
136           /* Decide on the conversion type.  */
137           if (local_name)
138             {
139               /* We've downloaded this URL.  Convert it to relative
140                  form.  We do this even if the URL already is in
141                  relative form, because our directory structure may
142                  not be identical to that on the server (think `-nd',
143                  `--cut-dirs', etc.)  */
144               cur_url->convert = CO_CONVERT_TO_RELATIVE;
145               cur_url->local_name = xstrdup (local_name);
146               DEBUGP (("will convert url %s to local %s\n", u->url, local_name));
147             }
148           else
149             {
150               /* We haven't downloaded this URL.  If it's not already
151                  complete (including a full host name), convert it to
152                  that form, so it can be reached while browsing this
153                  HTML locally.  */
154               if (!cur_url->link_complete_p)
155                 cur_url->convert = CO_CONVERT_TO_COMPLETE;
156               cur_url->local_name = NULL;
157               DEBUGP (("will convert url %s to complete\n", u->url));
158             }
159         }
160
161       /* Convert the links in the file.  */
162       convert_links (file, urls);
163       ++file_count;
164
165       /* Free the data.  */
166       free_urlpos (urls);
167     }
168
169   secs = ptimer_measure (timer);
170   ptimer_destroy (timer);
171   logprintf (LOG_VERBOSE, _("Converted %d files in %s seconds.\n"),
172              file_count, print_decimal (secs));
173 }
174
175 static void write_backup_file (const char *, downloaded_file_t);
176 static const char *replace_attr (const char *, int, FILE *, const char *);
177 static const char *replace_attr_refresh_hack (const char *, int, FILE *,
178                                               const char *, int);
179 static char *local_quote_string (const char *);
180 static char *construct_relative (const char *, const char *);
181
182 /* Change the links in one HTML file.  LINKS is a list of links in the
183    document, along with their positions and the desired direction of
184    the conversion.  */
185 static void
186 convert_links (const char *file, struct urlpos *links)
187 {
188   struct file_memory *fm;
189   FILE *fp;
190   const char *p;
191   downloaded_file_t downloaded_file_return;
192
193   struct urlpos *link;
194   int to_url_count = 0, to_file_count = 0;
195
196   logprintf (LOG_VERBOSE, _("Converting %s... "), file);
197
198   {
199     /* First we do a "dry run": go through the list L and see whether
200        any URL needs to be converted in the first place.  If not, just
201        leave the file alone.  */
202     int dry_count = 0;
203     struct urlpos *dry;
204     for (dry = links; dry; dry = dry->next)
205       if (dry->convert != CO_NOCONVERT)
206         ++dry_count;
207     if (!dry_count)
208       {
209         logputs (LOG_VERBOSE, _("nothing to do.\n"));
210         return;
211       }
212   }
213
214   fm = read_file (file);
215   if (!fm)
216     {
217       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
218                  file, strerror (errno));
219       return;
220     }
221
222   downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
223   if (opt.backup_converted && downloaded_file_return)
224     write_backup_file (file, downloaded_file_return);
225
226   /* Before opening the file for writing, unlink the file.  This is
227      important if the data in FM is mmaped.  In such case, nulling the
228      file, which is what fopen() below does, would make us read all
229      zeroes from the mmaped region.  */
230   if (unlink (file) < 0 && errno != ENOENT)
231     {
232       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
233                  file, strerror (errno));
234       read_file_free (fm);
235       return;
236     }
237   /* Now open the file for writing.  */
238   fp = fopen (file, "wb");
239   if (!fp)
240     {
241       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
242                  file, strerror (errno));
243       read_file_free (fm);
244       return;
245     }
246
247   /* Here we loop through all the URLs in file, replacing those of
248      them that are downloaded with relative references.  */
249   p = fm->content;
250   for (link = links; link; link = link->next)
251     {
252       char *url_start = fm->content + link->pos;
253
254       if (link->pos >= fm->length)
255         {
256           DEBUGP (("Something strange is going on.  Please investigate."));
257           break;
258         }
259       /* If the URL is not to be converted, skip it.  */
260       if (link->convert == CO_NOCONVERT)
261         {
262           DEBUGP (("Skipping %s at position %d.\n", link->url->url, link->pos));
263           continue;
264         }
265
266       /* Echo the file contents, up to the offending URL's opening
267          quote, to the outfile.  */
268       fwrite (p, 1, url_start - p, fp);
269       p = url_start;
270
271       switch (link->convert)
272         {
273         case CO_CONVERT_TO_RELATIVE:
274           /* Convert absolute URL to relative. */
275           {
276             char *newname = construct_relative (file, link->local_name);
277             char *quoted_newname = local_quote_string (newname);
278
279             if (!link->link_refresh_p)
280               p = replace_attr (p, link->size, fp, quoted_newname);
281             else
282               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newname,
283                                              link->refresh_timeout);
284
285             DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
286                      link->url->url, newname, link->pos, file));
287             xfree (newname);
288             xfree (quoted_newname);
289             ++to_file_count;
290             break;
291           }
292         case CO_CONVERT_TO_COMPLETE:
293           /* Convert the link to absolute URL. */
294           {
295             char *newlink = link->url->url;
296             char *quoted_newlink = html_quote_string (newlink);
297
298             if (!link->link_refresh_p)
299               p = replace_attr (p, link->size, fp, quoted_newlink);
300             else
301               p = replace_attr_refresh_hack (p, link->size, fp, quoted_newlink,
302                                              link->refresh_timeout);
303
304             DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
305                      newlink, link->pos, file));
306             xfree (quoted_newlink);
307             ++to_url_count;
308             break;
309           }
310         case CO_NULLIFY_BASE:
311           /* Change the base href to "". */
312           p = replace_attr (p, link->size, fp, "");
313           break;
314         case CO_NOCONVERT:
315           abort ();
316           break;
317         }
318     }
319
320   /* Output the rest of the file. */
321   if (p - fm->content < fm->length)
322     fwrite (p, 1, fm->length - (p - fm->content), fp);
323   fclose (fp);
324   read_file_free (fm);
325
326   logprintf (LOG_VERBOSE, "%d-%d\n", to_file_count, to_url_count);
327 }
328
329 /* Construct and return a link that points from BASEFILE to LINKFILE.
330    Both files should be local file names, BASEFILE of the referrering
331    file, and LINKFILE of the referred file.
332
333    Examples:
334
335    cr("foo", "bar")         -> "bar"
336    cr("A/foo", "A/bar")     -> "bar"
337    cr("A/foo", "A/B/bar")   -> "B/bar"
338    cr("A/X/foo", "A/Y/bar") -> "../Y/bar"
339    cr("X/", "Y/bar")        -> "../Y/bar" (trailing slash does matter in BASE)
340
341    Both files should be absolute or relative, otherwise strange
342    results might ensue.  The function makes no special efforts to
343    handle "." and ".." in links, so make sure they're not there
344    (e.g. using path_simplify).  */
345
346 static char *
347 construct_relative (const char *basefile, const char *linkfile)
348 {
349   char *link;
350   int basedirs;
351   const char *b, *l;
352   int i, start;
353
354   /* First, skip the initial directory components common to both
355      files.  */
356   start = 0;
357   for (b = basefile, l = linkfile; *b == *l && *b != '\0'; ++b, ++l)
358     {
359       if (*b == '/')
360         start = (b - basefile) + 1;
361     }
362   basefile += start;
363   linkfile += start;
364
365   /* With common directories out of the way, the situation we have is
366      as follows:
367          b - b1/b2/[...]/bfile
368          l - l1/l2/[...]/lfile
369
370      The link we're constructing needs to be:
371        lnk - ../../l1/l2/[...]/lfile
372
373      Where the number of ".."'s equals the number of bN directory
374      components in B.  */
375
376   /* Count the directory components in B. */
377   basedirs = 0;
378   for (b = basefile; *b; b++)
379     {
380       if (*b == '/')
381         ++basedirs;
382     }
383
384   /* Construct LINK as explained above. */
385   link = xmalloc (3 * basedirs + strlen (linkfile) + 1);
386   for (i = 0; i < basedirs; i++)
387     memcpy (link + 3 * i, "../", 3);
388   strcpy (link + 3 * i, linkfile);
389   return link;
390 }
391
392 /* Used by write_backup_file to remember which files have been
393    written. */
394 static struct hash_table *converted_files;
395
396 static void
397 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
398 {
399   /* Rather than just writing over the original .html file with the
400      converted version, save the former to *.orig.  Note we only do
401      this for files we've _successfully_ downloaded, so we don't
402      clobber .orig files sitting around from previous invocations. */
403
404   /* Construct the backup filename as the original name plus ".orig". */
405   size_t         filename_len = strlen (file);
406   char*          filename_plus_orig_suffix;
407
408   if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
409     {
410       /* Just write "orig" over "html".  We need to do it this way
411          because when we're checking to see if we've downloaded the
412          file before (to see if we can skip downloading it), we don't
413          know if it's a text/html file.  Therefore we don't know yet
414          at that stage that -E is going to cause us to tack on
415          ".html", so we need to compare vs. the original URL plus
416          ".orig", not the original URL plus ".html.orig". */
417       filename_plus_orig_suffix = alloca (filename_len + 1);
418       strcpy (filename_plus_orig_suffix, file);
419       strcpy ((filename_plus_orig_suffix + filename_len) - 4, "orig");
420     }
421   else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
422     {
423       /* Append ".orig" to the name. */
424       filename_plus_orig_suffix = alloca (filename_len + sizeof (".orig"));
425       strcpy (filename_plus_orig_suffix, file);
426       strcpy (filename_plus_orig_suffix + filename_len, ".orig");
427     }
428
429   if (!converted_files)
430     converted_files = make_string_hash_table (0);
431
432   /* We can get called twice on the same URL thanks to the
433      convert_all_links() call in main().  If we write the .orig file
434      each time in such a case, it'll end up containing the first-pass
435      conversion, not the original file.  So, see if we've already been
436      called on this file. */
437   if (!string_set_contains (converted_files, file))
438     {
439       /* Rename <file> to <file>.orig before former gets written over. */
440       if (rename (file, filename_plus_orig_suffix) != 0)
441         logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
442                    file, filename_plus_orig_suffix, strerror (errno));
443
444       /* Remember that we've already written a .orig backup for this file.
445          Note that we never free this memory since we need it till the
446          convert_all_links() call, which is one of the last things the
447          program does before terminating.  BTW, I'm not sure if it would be
448          safe to just set 'converted_file_ptr->string' to 'file' below,
449          rather than making a copy of the string...  Another note is that I
450          thought I could just add a field to the urlpos structure saying
451          that we'd written a .orig file for this URL, but that didn't work,
452          so I had to make this separate list.
453          -- Dan Harkless <wget@harkless.org>
454
455          This [adding a field to the urlpos structure] didn't work
456          because convert_file() is called from convert_all_links at
457          the end of the retrieval with a freshly built new urlpos
458          list.
459          -- Hrvoje Niksic <hniksic@xemacs.org>
460       */
461       string_set_add (converted_files, file);
462     }
463 }
464
465 static bool find_fragment (const char *, int, const char **, const char **);
466
467 /* Replace an attribute's original text with NEW_TEXT. */
468
469 static const char *
470 replace_attr (const char *p, int size, FILE *fp, const char *new_text)
471 {
472   bool quote_flag = false;
473   char quote_char = '\"';       /* use "..." for quoting, unless the
474                                    original value is quoted, in which
475                                    case reuse its quoting char. */
476   const char *frag_beg, *frag_end;
477
478   /* Structure of our string is:
479        "...old-contents..."
480        <---    size    --->  (with quotes)
481      OR:
482        ...old-contents...
483        <---    size   -->    (no quotes)   */
484
485   if (*p == '\"' || *p == '\'')
486     {
487       quote_char = *p;
488       quote_flag = true;
489       ++p;
490       size -= 2;                /* disregard opening and closing quote */
491     }
492   putc (quote_char, fp);
493   fputs (new_text, fp);
494
495   /* Look for fragment identifier, if any. */
496   if (find_fragment (p, size, &frag_beg, &frag_end))
497     fwrite (frag_beg, 1, frag_end - frag_beg, fp);
498   p += size;
499   if (quote_flag)
500     ++p;
501   putc (quote_char, fp);
502
503   return p;
504 }
505
506 /* The same as REPLACE_ATTR, but used when replacing
507    <meta http-equiv=refresh content="new_text"> because we need to
508    append "timeout_value; URL=" before the next_text.  */
509
510 static const char *
511 replace_attr_refresh_hack (const char *p, int size, FILE *fp,
512                            const char *new_text, int timeout)
513 {
514   /* "0; URL=..." */
515   char *new_with_timeout = (char *)alloca (numdigit (timeout)
516                                            + 6 /* "; URL=" */
517                                            + strlen (new_text)
518                                            + 1);
519   sprintf (new_with_timeout, "%d; URL=%s", timeout, new_text);
520
521   return replace_attr (p, size, fp, new_with_timeout);
522 }
523
524 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
525    preceded by '&'.  If the character is not found, return zero.  If
526    the character is found, return true and set BP and EP to point to
527    the beginning and end of the region.
528
529    This is used for finding the fragment indentifiers in URLs.  */
530
531 static bool
532 find_fragment (const char *beg, int size, const char **bp, const char **ep)
533 {
534   const char *end = beg + size;
535   bool saw_amp = false;
536   for (; beg < end; beg++)
537     {
538       switch (*beg)
539         {
540         case '&':
541           saw_amp = true;
542           break;
543         case '#':
544           if (!saw_amp)
545             {
546               *bp = beg;
547               *ep = end;
548               return true;
549             }
550           /* fallthrough */
551         default:
552           saw_amp = false;
553         }
554     }
555   return false;
556 }
557
558 /* Quote FILE for use as local reference to an HTML file.
559
560    We quote ? as %3F to avoid passing part of the file name as the
561    parameter when browsing the converted file through HTTP.  However,
562    it is safe to do this only when `--html-extension' is turned on.
563    This is because converting "index.html?foo=bar" to
564    "index.html%3Ffoo=bar" would break local browsing, as the latter
565    isn't even recognized as an HTML file!  However, converting
566    "index.html?foo=bar.html" to "index.html%3Ffoo=bar.html" should be
567    safe for both local and HTTP-served browsing.
568
569    We always quote "#" as "%23" and "%" as "%25" because those
570    characters have special meanings in URLs.  */
571
572 static char *
573 local_quote_string (const char *file)
574 {
575   const char *from;
576   char *newname, *to;
577
578   char *any = strpbrk (file, "?#%");
579   if (!any)
580     return html_quote_string (file);
581
582   /* Allocate space assuming the worst-case scenario, each character
583      having to be quoted.  */
584   to = newname = (char *)alloca (3 * strlen (file) + 1);
585   for (from = file; *from; from++)
586     switch (*from)
587       {
588       case '%':
589         *to++ = '%';
590         *to++ = '2';
591         *to++ = '5';
592         break;
593       case '#':
594         *to++ = '%';
595         *to++ = '2';
596         *to++ = '3';
597         break;
598       case '?':
599         if (opt.html_extension)
600           {
601             *to++ = '%';
602             *to++ = '3';
603             *to++ = 'F';
604             break;
605           }
606         /* fallthrough */
607       default:
608         *to++ = *from;
609       }
610   *to = '\0';
611
612   return html_quote_string (newname);
613 }
614 \f
615 /* Book-keeping code for dl_file_url_map, dl_url_file_map,
616    downloaded_html_list, and downloaded_html_set.  Other code calls
617    these functions to let us know that a file has been downloaded.  */
618
619 #define ENSURE_TABLES_EXIST do {                        \
620   if (!dl_file_url_map)                                 \
621     dl_file_url_map = make_string_hash_table (0);       \
622   if (!dl_url_file_map)                                 \
623     dl_url_file_map = make_string_hash_table (0);       \
624 } while (0)
625
626 /* Return true if S1 and S2 are the same, except for "/index.html".
627    The three cases in which it returns one are (substitute any
628    substring for "foo"):
629
630    m("foo/index.html", "foo/")  ==> 1
631    m("foo/", "foo/index.html")  ==> 1
632    m("foo", "foo/index.html")   ==> 1
633    m("foo", "foo/"              ==> 1
634    m("foo", "foo")              ==> 1  */
635
636 static bool
637 match_except_index (const char *s1, const char *s2)
638 {
639   int i;
640   const char *lng;
641
642   /* Skip common substring. */
643   for (i = 0; *s1 && *s2 && *s1 == *s2; s1++, s2++, i++)
644     ;
645   if (i == 0)
646     /* Strings differ at the very beginning -- bail out.  We need to
647        check this explicitly to avoid `lng - 1' reading outside the
648        array.  */
649     return false;
650
651   if (!*s1 && !*s2)
652     /* Both strings hit EOF -- strings are equal. */
653     return true;
654   else if (*s1 && *s2)
655     /* Strings are randomly different, e.g. "/foo/bar" and "/foo/qux". */
656     return false;
657   else if (*s1)
658     /* S1 is the longer one. */
659     lng = s1;
660   else
661     /* S2 is the longer one. */
662     lng = s2;
663
664   /* foo            */            /* foo/           */
665   /* foo/index.html */  /* or */  /* foo/index.html */
666   /*    ^           */            /*     ^          */
667
668   if (*lng != '/')
669     /* The right-hand case. */
670     --lng;
671
672   if (*lng == '/' && *(lng + 1) == '\0')
673     /* foo  */
674     /* foo/ */
675     return true;
676
677   return 0 == strcmp (lng, "/index.html");
678 }
679
680 static int
681 dissociate_urls_from_file_mapper (void *key, void *value, void *arg)
682 {
683   char *mapping_url = (char *)key;
684   char *mapping_file = (char *)value;
685   char *file = (char *)arg;
686
687   if (0 == strcmp (mapping_file, file))
688     {
689       hash_table_remove (dl_url_file_map, mapping_url);
690       xfree (mapping_url);
691       xfree (mapping_file);
692     }
693
694   /* Continue mapping. */
695   return 0;
696 }
697
698 /* Remove all associations from various URLs to FILE from dl_url_file_map. */
699
700 static void
701 dissociate_urls_from_file (const char *file)
702 {
703   hash_table_map (dl_url_file_map, dissociate_urls_from_file_mapper,
704                   (char *)file);
705 }
706
707 /* Register that URL has been successfully downloaded to FILE.  This
708    is used by the link conversion code to convert references to URLs
709    to references to local files.  It is also being used to check if a
710    URL has already been downloaded.  */
711
712 void
713 register_download (const char *url, const char *file)
714 {
715   char *old_file, *old_url;
716
717   ENSURE_TABLES_EXIST;
718
719   /* With some forms of retrieval, it is possible, although not likely
720      or particularly desirable.  If both are downloaded, the second
721      download will override the first one.  When that happens,
722      dissociate the old file name from the URL.  */
723
724   if (hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
725     {
726       if (0 == strcmp (url, old_url))
727         /* We have somehow managed to download the same URL twice.
728            Nothing to do.  */
729         return;
730
731       if (match_except_index (url, old_url)
732           && !hash_table_contains (dl_url_file_map, url))
733         /* The two URLs differ only in the "index.html" ending.  For
734            example, one is "http://www.server.com/", and the other is
735            "http://www.server.com/index.html".  Don't remove the old
736            one, just add the new one as a non-canonical entry.  */
737         goto url_only;
738
739       hash_table_remove (dl_file_url_map, file);
740       xfree (old_file);
741       xfree (old_url);
742
743       /* Remove all the URLs that point to this file.  Yes, there can
744          be more than one such URL, because we store redirections as
745          multiple entries in dl_url_file_map.  For example, if URL1
746          redirects to URL2 which gets downloaded to FILE, we map both
747          URL1 and URL2 to FILE in dl_url_file_map.  (dl_file_url_map
748          only points to URL2.)  When another URL gets loaded to FILE,
749          we want both URL1 and URL2 dissociated from it.
750
751          This is a relatively expensive operation because it performs
752          a linear search of the whole hash table, but it should be
753          called very rarely, only when two URLs resolve to the same
754          file name, *and* the "<file>.1" extensions are turned off.
755          In other words, almost never.  */
756       dissociate_urls_from_file (file);
757     }
758
759   hash_table_put (dl_file_url_map, xstrdup (file), xstrdup (url));
760
761  url_only:
762   /* A URL->FILE mapping is not possible without a FILE->URL mapping.
763      If the latter were present, it should have been removed by the
764      above `if'.  So we could write:
765
766          assert (!hash_table_contains (dl_url_file_map, url));
767
768      The above is correct when running in recursive mode where the
769      same URL always resolves to the same file.  But if you do
770      something like:
771
772          wget URL URL
773
774      then the first URL will resolve to "FILE", and the other to
775      "FILE.1".  In that case, FILE.1 will not be found in
776      dl_file_url_map, but URL will still point to FILE in
777      dl_url_file_map.  */
778   if (hash_table_get_pair (dl_url_file_map, url, &old_url, &old_file))
779     {
780       hash_table_remove (dl_url_file_map, url);
781       xfree (old_url);
782       xfree (old_file);
783     }
784
785   hash_table_put (dl_url_file_map, xstrdup (url), xstrdup (file));
786 }
787
788 /* Register that FROM has been redirected to TO.  This assumes that TO
789    is successfully downloaded and already registered using
790    register_download() above.  */
791
792 void
793 register_redirection (const char *from, const char *to)
794 {
795   char *file;
796
797   ENSURE_TABLES_EXIST;
798
799   file = hash_table_get (dl_url_file_map, to);
800   assert (file != NULL);
801   if (!hash_table_contains (dl_url_file_map, from))
802     hash_table_put (dl_url_file_map, xstrdup (from), xstrdup (file));
803 }
804
805 /* Register that the file has been deleted. */
806
807 void
808 register_delete_file (const char *file)
809 {
810   char *old_url, *old_file;
811
812   ENSURE_TABLES_EXIST;
813
814   if (!hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
815     return;
816
817   hash_table_remove (dl_file_url_map, file);
818   xfree (old_file);
819   xfree (old_url);
820   dissociate_urls_from_file (file);
821 }
822
823 /* Register that FILE is an HTML file that has been downloaded. */
824
825 void
826 register_html (const char *url, const char *file)
827 {
828   if (!downloaded_html_set)
829     downloaded_html_set = make_string_hash_table (0);
830   string_set_add (downloaded_html_set, file);
831 }
832
833 static void downloaded_files_free (void);
834
835 /* Cleanup the data structures associated with this file.  */
836
837 void
838 convert_cleanup (void)
839 {
840   if (dl_file_url_map)
841     {
842       free_keys_and_values (dl_file_url_map);
843       hash_table_destroy (dl_file_url_map);
844       dl_file_url_map = NULL;
845     }
846   if (dl_url_file_map)
847     {
848       free_keys_and_values (dl_url_file_map);
849       hash_table_destroy (dl_url_file_map);
850       dl_url_file_map = NULL;
851     }
852   if (downloaded_html_set)
853     string_set_free (downloaded_html_set);
854   downloaded_files_free ();
855   if (converted_files)
856     string_set_free (converted_files);
857 }
858 \f
859 /* Book-keeping code for downloaded files that enables extension
860    hacks.  */
861
862 /* This table should really be merged with dl_file_url_map and
863    downloaded_html_files.  This was originally a list, but I changed
864    it to a hash table beause it was actually taking a lot of time to
865    find things in it.  */
866
867 static struct hash_table *downloaded_files_hash;
868
869 /* We're storing "modes" of type downloaded_file_t in the hash table.
870    However, our hash tables only accept pointers for keys and values.
871    So when we need a pointer, we use the address of a
872    downloaded_file_t variable of static storage.  */
873    
874 static downloaded_file_t *
875 downloaded_mode_to_ptr (downloaded_file_t mode)
876 {
877   static downloaded_file_t
878     v1 = FILE_NOT_ALREADY_DOWNLOADED,
879     v2 = FILE_DOWNLOADED_NORMALLY,
880     v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
881     v4 = CHECK_FOR_FILE;
882
883   switch (mode)
884     {
885     case FILE_NOT_ALREADY_DOWNLOADED:
886       return &v1;
887     case FILE_DOWNLOADED_NORMALLY:
888       return &v2;
889     case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
890       return &v3;
891     case CHECK_FOR_FILE:
892       return &v4;
893     }
894   return NULL;
895 }
896
897 /* Remembers which files have been downloaded.  In the standard case,
898    should be called with mode == FILE_DOWNLOADED_NORMALLY for each
899    file we actually download successfully (i.e. not for ones we have
900    failures on or that we skip due to -N).
901
902    When we've downloaded a file and tacked on a ".html" extension due
903    to -E, call this function with
904    FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
905    FILE_DOWNLOADED_NORMALLY.
906
907    If you just want to check if a file has been previously added
908    without adding it, call with mode == CHECK_FOR_FILE.  Please be
909    sure to call this function with local filenames, not remote
910    URLs.  */
911
912 downloaded_file_t
913 downloaded_file (downloaded_file_t mode, const char *file)
914 {
915   downloaded_file_t *ptr;
916
917   if (mode == CHECK_FOR_FILE)
918     {
919       if (!downloaded_files_hash)
920         return FILE_NOT_ALREADY_DOWNLOADED;
921       ptr = hash_table_get (downloaded_files_hash, file);
922       if (!ptr)
923         return FILE_NOT_ALREADY_DOWNLOADED;
924       return *ptr;
925     }
926
927   if (!downloaded_files_hash)
928     downloaded_files_hash = make_string_hash_table (0);
929
930   ptr = hash_table_get (downloaded_files_hash, file);
931   if (ptr)
932     return *ptr;
933
934   ptr = downloaded_mode_to_ptr (mode);
935   hash_table_put (downloaded_files_hash, xstrdup (file), &ptr);
936
937   return FILE_NOT_ALREADY_DOWNLOADED;
938 }
939
940 static int
941 df_free_mapper (void *key, void *value, void *ignored)
942 {
943   xfree (key);
944   return 0;
945 }
946
947 static void
948 downloaded_files_free (void)
949 {
950   if (downloaded_files_hash)
951     {
952       hash_table_map (downloaded_files_hash, df_free_mapper, NULL);
953       hash_table_destroy (downloaded_files_hash);
954       downloaded_files_hash = NULL;
955     }
956 }
957 \f
958 /* The function returns the pointer to the malloc-ed quoted version of
959    string s.  It will recognize and quote numeric and special graphic
960    entities, as per RFC1866:
961
962    `&' -> `&amp;'
963    `<' -> `&lt;'
964    `>' -> `&gt;'
965    `"' -> `&quot;'
966    SP  -> `&#32;'
967
968    No other entities are recognized or replaced.  */
969 char *
970 html_quote_string (const char *s)
971 {
972   const char *b = s;
973   char *p, *res;
974   int i;
975
976   /* Pass through the string, and count the new size.  */
977   for (i = 0; *s; s++, i++)
978     {
979       if (*s == '&')
980         i += 4;                 /* `amp;' */
981       else if (*s == '<' || *s == '>')
982         i += 3;                 /* `lt;' and `gt;' */
983       else if (*s == '\"')
984         i += 5;                 /* `quot;' */
985       else if (*s == ' ')
986         i += 4;                 /* #32; */
987     }
988   res = xmalloc (i + 1);
989   s = b;
990   for (p = res; *s; s++)
991     {
992       switch (*s)
993         {
994         case '&':
995           *p++ = '&';
996           *p++ = 'a';
997           *p++ = 'm';
998           *p++ = 'p';
999           *p++ = ';';
1000           break;
1001         case '<': case '>':
1002           *p++ = '&';
1003           *p++ = (*s == '<' ? 'l' : 'g');
1004           *p++ = 't';
1005           *p++ = ';';
1006           break;
1007         case '\"':
1008           *p++ = '&';
1009           *p++ = 'q';
1010           *p++ = 'u';
1011           *p++ = 'o';
1012           *p++ = 't';
1013           *p++ = ';';
1014           break;
1015         case ' ':
1016           *p++ = '&';
1017           *p++ = '#';
1018           *p++ = '3';
1019           *p++ = '2';
1020           *p++ = ';';
1021           break;
1022         default:
1023           *p++ = *s;
1024         }
1025     }
1026   *p = '\0';
1027   return res;
1028 }