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