]> sjero.net Git - wget/blob - src/convert.c
[svn] Avoid unneeded initialization of local var.
[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;
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    We always quote "#" as "%23" and "%" as "%25" because those
582    characters have special meanings in URLs.  */
583
584 static char *
585 local_quote_string (const char *file)
586 {
587   const char *from;
588   char *newname, *to;
589
590   char *any = strpbrk (file, "?#%");
591   if (!any)
592     return html_quote_string (file);
593
594   /* Allocate space assuming the worst-case scenario, each character
595      having to be quoted.  */
596   to = newname = (char *)alloca (3 * strlen (file) + 1);
597   for (from = file; *from; from++)
598     switch (*from)
599       {
600       case '%':
601         *to++ = '%';
602         *to++ = '2';
603         *to++ = '5';
604         break;
605       case '#':
606         *to++ = '%';
607         *to++ = '2';
608         *to++ = '3';
609         break;
610       case '?':
611         if (opt.html_extension)
612           {
613             *to++ = '%';
614             *to++ = '3';
615             *to++ = 'F';
616             break;
617           }
618         /* fallthrough */
619       default:
620         *to++ = *from;
621       }
622   *to = '\0';
623
624   return html_quote_string (newname);
625 }
626 \f
627 /* Book-keeping code for dl_file_url_map, dl_url_file_map,
628    downloaded_html_list, and downloaded_html_set.  Other code calls
629    these functions to let us know that a file has been downloaded.  */
630
631 #define ENSURE_TABLES_EXIST do {                        \
632   if (!dl_file_url_map)                                 \
633     dl_file_url_map = make_string_hash_table (0);       \
634   if (!dl_url_file_map)                                 \
635     dl_url_file_map = make_string_hash_table (0);       \
636 } while (0)
637
638 /* Return 1 if S1 and S2 are the same, except for "/index.html".  The
639    three cases in which it returns one are (substitute any substring
640    for "foo"):
641
642    m("foo/index.html", "foo/")  ==> 1
643    m("foo/", "foo/index.html")  ==> 1
644    m("foo", "foo/index.html")   ==> 1
645    m("foo", "foo/"              ==> 1
646    m("foo", "foo")              ==> 1  */
647
648 static int
649 match_except_index (const char *s1, const char *s2)
650 {
651   int i;
652   const char *lng;
653
654   /* Skip common substring. */
655   for (i = 0; *s1 && *s2 && *s1 == *s2; s1++, s2++, i++)
656     ;
657   if (i == 0)
658     /* Strings differ at the very beginning -- bail out.  We need to
659        check this explicitly to avoid `lng - 1' reading outside the
660        array.  */
661     return 0;
662
663   if (!*s1 && !*s2)
664     /* Both strings hit EOF -- strings are equal. */
665     return 1;
666   else if (*s1 && *s2)
667     /* Strings are randomly different, e.g. "/foo/bar" and "/foo/qux". */
668     return 0;
669   else if (*s1)
670     /* S1 is the longer one. */
671     lng = s1;
672   else
673     /* S2 is the longer one. */
674     lng = s2;
675
676   /* foo            */            /* foo/           */
677   /* foo/index.html */  /* or */  /* foo/index.html */
678   /*    ^           */            /*     ^          */
679
680   if (*lng != '/')
681     /* The right-hand case. */
682     --lng;
683
684   if (*lng == '/' && *(lng + 1) == '\0')
685     /* foo  */
686     /* foo/ */
687     return 1;
688
689   return 0 == strcmp (lng, "/index.html");
690 }
691
692 static int
693 dissociate_urls_from_file_mapper (void *key, void *value, void *arg)
694 {
695   char *mapping_url = (char *)key;
696   char *mapping_file = (char *)value;
697   char *file = (char *)arg;
698
699   if (0 == strcmp (mapping_file, file))
700     {
701       hash_table_remove (dl_url_file_map, mapping_url);
702       xfree (mapping_url);
703       xfree (mapping_file);
704     }
705
706   /* Continue mapping. */
707   return 0;
708 }
709
710 /* Remove all associations from various URLs to FILE from dl_url_file_map. */
711
712 static void
713 dissociate_urls_from_file (const char *file)
714 {
715   hash_table_map (dl_url_file_map, dissociate_urls_from_file_mapper,
716                   (char *)file);
717 }
718
719 /* Register that URL has been successfully downloaded to FILE.  This
720    is used by the link conversion code to convert references to URLs
721    to references to local files.  It is also being used to check if a
722    URL has already been downloaded.  */
723
724 void
725 register_download (const char *url, const char *file)
726 {
727   char *old_file, *old_url;
728
729   ENSURE_TABLES_EXIST;
730
731   /* With some forms of retrieval, it is possible, although not likely
732      or particularly desirable.  If both are downloaded, the second
733      download will override the first one.  When that happens,
734      dissociate the old file name from the URL.  */
735
736   if (hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
737     {
738       if (0 == strcmp (url, old_url))
739         /* We have somehow managed to download the same URL twice.
740            Nothing to do.  */
741         return;
742
743       if (match_except_index (url, old_url)
744           && !hash_table_contains (dl_url_file_map, url))
745         /* The two URLs differ only in the "index.html" ending.  For
746            example, one is "http://www.server.com/", and the other is
747            "http://www.server.com/index.html".  Don't remove the old
748            one, just add the new one as a non-canonical entry.  */
749         goto url_only;
750
751       hash_table_remove (dl_file_url_map, file);
752       xfree (old_file);
753       xfree (old_url);
754
755       /* Remove all the URLs that point to this file.  Yes, there can
756          be more than one such URL, because we store redirections as
757          multiple entries in dl_url_file_map.  For example, if URL1
758          redirects to URL2 which gets downloaded to FILE, we map both
759          URL1 and URL2 to FILE in dl_url_file_map.  (dl_file_url_map
760          only points to URL2.)  When another URL gets loaded to FILE,
761          we want both URL1 and URL2 dissociated from it.
762
763          This is a relatively expensive operation because it performs
764          a linear search of the whole hash table, but it should be
765          called very rarely, only when two URLs resolve to the same
766          file name, *and* the "<file>.1" extensions are turned off.
767          In other words, almost never.  */
768       dissociate_urls_from_file (file);
769     }
770
771   hash_table_put (dl_file_url_map, xstrdup (file), xstrdup (url));
772
773  url_only:
774   /* A URL->FILE mapping is not possible without a FILE->URL mapping.
775      If the latter were present, it should have been removed by the
776      above `if'.  So we could write:
777
778          assert (!hash_table_contains (dl_url_file_map, url));
779
780      The above is correct when running in recursive mode where the
781      same URL always resolves to the same file.  But if you do
782      something like:
783
784          wget URL URL
785
786      then the first URL will resolve to "FILE", and the other to
787      "FILE.1".  In that case, FILE.1 will not be found in
788      dl_file_url_map, but URL will still point to FILE in
789      dl_url_file_map.  */
790   if (hash_table_get_pair (dl_url_file_map, url, &old_url, &old_file))
791     {
792       hash_table_remove (dl_url_file_map, url);
793       xfree (old_url);
794       xfree (old_file);
795     }
796
797   hash_table_put (dl_url_file_map, xstrdup (url), xstrdup (file));
798 }
799
800 /* Register that FROM has been redirected to TO.  This assumes that TO
801    is successfully downloaded and already registered using
802    register_download() above.  */
803
804 void
805 register_redirection (const char *from, const char *to)
806 {
807   char *file;
808
809   ENSURE_TABLES_EXIST;
810
811   file = hash_table_get (dl_url_file_map, to);
812   assert (file != NULL);
813   if (!hash_table_contains (dl_url_file_map, from))
814     hash_table_put (dl_url_file_map, xstrdup (from), xstrdup (file));
815 }
816
817 /* Register that the file has been deleted. */
818
819 void
820 register_delete_file (const char *file)
821 {
822   char *old_url, *old_file;
823
824   ENSURE_TABLES_EXIST;
825
826   if (!hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
827     return;
828
829   hash_table_remove (dl_file_url_map, file);
830   xfree (old_file);
831   xfree (old_url);
832   dissociate_urls_from_file (file);
833 }
834
835 /* Register that FILE is an HTML file that has been downloaded. */
836
837 void
838 register_html (const char *url, const char *file)
839 {
840   if (!downloaded_html_set)
841     downloaded_html_set = make_string_hash_table (0);
842   else if (hash_table_contains (downloaded_html_set, file))
843     return;
844
845   /* The set and the list should use the same copy of FILE, but the
846      slist interface insists on strduping the string it gets.  Oh
847      well. */
848   string_set_add (downloaded_html_set, file);
849   downloaded_html_list = slist_prepend (downloaded_html_list, file);
850 }
851
852 /* Cleanup the data structures associated with recursive retrieving
853    (the variables above).  */
854 void
855 convert_cleanup (void)
856 {
857   if (dl_file_url_map)
858     {
859       free_keys_and_values (dl_file_url_map);
860       hash_table_destroy (dl_file_url_map);
861       dl_file_url_map = NULL;
862     }
863   if (dl_url_file_map)
864     {
865       free_keys_and_values (dl_url_file_map);
866       hash_table_destroy (dl_url_file_map);
867       dl_url_file_map = NULL;
868     }
869   if (downloaded_html_set)
870     string_set_free (downloaded_html_set);
871   slist_free (downloaded_html_list);
872   downloaded_html_list = NULL;
873 }
874 \f
875 /* Book-keeping code for downloaded files that enables extension
876    hacks.  */
877
878 /* This table should really be merged with dl_file_url_map and
879    downloaded_html_files.  This was originally a list, but I changed
880    it to a hash table beause it was actually taking a lot of time to
881    find things in it.  */
882
883 static struct hash_table *downloaded_files_hash;
884
885 /* We're storing "modes" of type downloaded_file_t in the hash table.
886    However, our hash tables only accept pointers for keys and values.
887    So when we need a pointer, we use the address of a
888    downloaded_file_t variable of static storage.  */
889    
890 static downloaded_file_t *
891 downloaded_mode_to_ptr (downloaded_file_t mode)
892 {
893   static downloaded_file_t
894     v1 = FILE_NOT_ALREADY_DOWNLOADED,
895     v2 = FILE_DOWNLOADED_NORMALLY,
896     v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
897     v4 = CHECK_FOR_FILE;
898
899   switch (mode)
900     {
901     case FILE_NOT_ALREADY_DOWNLOADED:
902       return &v1;
903     case FILE_DOWNLOADED_NORMALLY:
904       return &v2;
905     case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
906       return &v3;
907     case CHECK_FOR_FILE:
908       return &v4;
909     }
910   return NULL;
911 }
912
913 /* Remembers which files have been downloaded.  In the standard case,
914    should be called with mode == FILE_DOWNLOADED_NORMALLY for each
915    file we actually download successfully (i.e. not for ones we have
916    failures on or that we skip due to -N).
917
918    When we've downloaded a file and tacked on a ".html" extension due
919    to -E, call this function with
920    FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
921    FILE_DOWNLOADED_NORMALLY.
922
923    If you just want to check if a file has been previously added
924    without adding it, call with mode == CHECK_FOR_FILE.  Please be
925    sure to call this function with local filenames, not remote
926    URLs.  */
927
928 downloaded_file_t
929 downloaded_file (downloaded_file_t mode, const char *file)
930 {
931   downloaded_file_t *ptr;
932
933   if (mode == CHECK_FOR_FILE)
934     {
935       if (!downloaded_files_hash)
936         return FILE_NOT_ALREADY_DOWNLOADED;
937       ptr = hash_table_get (downloaded_files_hash, file);
938       if (!ptr)
939         return FILE_NOT_ALREADY_DOWNLOADED;
940       return *ptr;
941     }
942
943   if (!downloaded_files_hash)
944     downloaded_files_hash = make_string_hash_table (0);
945
946   ptr = hash_table_get (downloaded_files_hash, file);
947   if (ptr)
948     return *ptr;
949
950   ptr = downloaded_mode_to_ptr (mode);
951   hash_table_put (downloaded_files_hash, xstrdup (file), &ptr);
952
953   return FILE_NOT_ALREADY_DOWNLOADED;
954 }
955
956 static int
957 df_free_mapper (void *key, void *value, void *ignored)
958 {
959   xfree (key);
960   return 0;
961 }
962
963 void
964 downloaded_files_free (void)
965 {
966   if (downloaded_files_hash)
967     {
968       hash_table_map (downloaded_files_hash, df_free_mapper, NULL);
969       hash_table_destroy (downloaded_files_hash);
970       downloaded_files_hash = NULL;
971     }
972 }