1 /* Conversion of links to local files.
2 Copyright (C) 2003, 2004, 2005, 2006, 2007,
3 2008 Free Software Foundation, Inc.
5 This file is part of GNU Wget.
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with Wget. If not, see <http://www.gnu.org/licenses/>.
20 Additional permission under GNU GPL version 3 section 7
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work. */
31 #define USE_GNULIB_ALLOC
40 #endif /* HAVE_UNISTD_H */
51 static struct hash_table *dl_file_url_map;
52 struct hash_table *dl_url_file_map;
54 /* Set of HTML files downloaded in this Wget run, used for link
55 conversion after Wget is done. */
56 struct hash_table *downloaded_html_set;
58 static void convert_links (const char *, struct urlpos *);
60 /* This function is called when the retrieval is done to convert the
61 links that have been downloaded. It has to be called at the end of
62 the retrieval, because only then does Wget know conclusively which
63 URLs have been downloaded, and which not, so it can tell which
64 direction to convert to.
66 The "direction" means that the URLs to the files that have been
67 downloaded get converted to the relative URL which will point to
68 that file. And the other URLs get converted to the remote URL on
71 All the downloaded HTMLs are kept in downloaded_html_files, and
72 downloaded URLs in urls_downloaded. All the information is
73 extracted from these two lists. */
76 convert_all_links (void)
82 struct ptimer *timer = ptimer_new ();
88 if (downloaded_html_set)
89 cnt = hash_table_count (downloaded_html_set);
92 file_array = alloca_array (char *, cnt);
93 string_set_to_array (downloaded_html_set, file_array);
95 for (i = 0; i < cnt; i++)
97 struct urlpos *urls, *cur_url;
99 char *file = file_array[i];
101 /* Determine the URL of the HTML file. get_urls_html will need
103 url = hash_table_get (dl_file_url_map, file);
106 DEBUGP (("Apparently %s has been removed.\n", file));
110 DEBUGP (("Scanning %s (from %s)\n", file, url));
112 /* Parse the HTML file... */
113 urls = get_urls_html (file, url, NULL);
115 /* We don't respect meta_disallow_follow here because, even if
116 the file is not followed, we might still want to convert the
117 links that have been followed from other files. */
119 for (cur_url = urls; cur_url; cur_url = cur_url->next)
122 struct url *u = cur_url->url;
124 if (cur_url->link_base_p)
126 /* Base references have been resolved by our parser, so
127 we turn the base URL into an empty string. (Perhaps
128 we should remove the tag entirely?) */
129 cur_url->convert = CO_NULLIFY_BASE;
133 /* We decide the direction of conversion according to whether
134 a URL was downloaded. Downloaded URLs will be converted
135 ABS2REL, whereas non-downloaded will be converted REL2ABS. */
136 local_name = hash_table_get (dl_url_file_map, u->url);
138 /* Decide on the conversion type. */
141 /* We've downloaded this URL. Convert it to relative
142 form. We do this even if the URL already is in
143 relative form, because our directory structure may
144 not be identical to that on the server (think `-nd',
145 `--cut-dirs', etc.) */
146 cur_url->convert = CO_CONVERT_TO_RELATIVE;
147 cur_url->local_name = xstrdup (local_name);
148 DEBUGP (("will convert url %s to local %s\n", u->url, local_name));
152 /* We haven't downloaded this URL. If it's not already
153 complete (including a full host name), convert it to
154 that form, so it can be reached while browsing this
156 if (!cur_url->link_complete_p)
157 cur_url->convert = CO_CONVERT_TO_COMPLETE;
158 cur_url->local_name = NULL;
159 DEBUGP (("will convert url %s to complete\n", u->url));
163 /* Convert the links in the file. */
164 convert_links (file, urls);
171 secs = ptimer_measure (timer);
172 logprintf (LOG_VERBOSE, _("Converted %d files in %s seconds.\n"),
173 file_count, print_decimal (secs));
175 ptimer_destroy (timer);
178 static void write_backup_file (const char *, downloaded_file_t);
179 static const char *replace_attr (const char *, int, FILE *, const char *);
180 static const char *replace_attr_refresh_hack (const char *, int, FILE *,
182 static char *local_quote_string (const char *);
183 static char *construct_relative (const char *, const char *);
185 /* Change the links in one HTML file. LINKS is a list of links in the
186 document, along with their positions and the desired direction of
189 convert_links (const char *file, struct urlpos *links)
191 struct file_memory *fm;
194 downloaded_file_t downloaded_file_return;
197 int to_url_count = 0, to_file_count = 0;
199 logprintf (LOG_VERBOSE, _("Converting %s... "), file);
202 /* First we do a "dry run": go through the list L and see whether
203 any URL needs to be converted in the first place. If not, just
204 leave the file alone. */
207 for (dry = links; dry; dry = dry->next)
208 if (dry->convert != CO_NOCONVERT)
212 logputs (LOG_VERBOSE, _("nothing to do.\n"));
217 fm = read_file (file);
220 logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
221 file, strerror (errno));
225 downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
226 if (opt.backup_converted && downloaded_file_return)
227 write_backup_file (file, downloaded_file_return);
229 /* Before opening the file for writing, unlink the file. This is
230 important if the data in FM is mmaped. In such case, nulling the
231 file, which is what fopen() below does, would make us read all
232 zeroes from the mmaped region. */
233 if (unlink (file) < 0 && errno != ENOENT)
235 logprintf (LOG_NOTQUIET, _("Unable to delete %s: %s\n"),
236 quote (file), strerror (errno));
240 /* Now open the file for writing. */
241 fp = fopen (file, "wb");
244 logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
245 file, strerror (errno));
250 /* Here we loop through all the URLs in file, replacing those of
251 them that are downloaded with relative references. */
253 for (link = links; link; link = link->next)
255 char *url_start = fm->content + link->pos;
257 if (link->pos >= fm->length)
259 DEBUGP (("Something strange is going on. Please investigate."));
262 /* If the URL is not to be converted, skip it. */
263 if (link->convert == CO_NOCONVERT)
265 DEBUGP (("Skipping %s at position %d.\n", link->url->url, link->pos));
269 /* Echo the file contents, up to the offending URL's opening
270 quote, to the outfile. */
271 fwrite (p, 1, url_start - p, fp);
274 switch (link->convert)
276 case CO_CONVERT_TO_RELATIVE:
277 /* Convert absolute URL to relative. */
279 char *newname = construct_relative (file, link->local_name);
280 char *quoted_newname = local_quote_string (newname);
282 if (!link->link_refresh_p)
283 p = replace_attr (p, link->size, fp, quoted_newname);
285 p = replace_attr_refresh_hack (p, link->size, fp, quoted_newname,
286 link->refresh_timeout);
288 DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
289 link->url->url, newname, link->pos, file));
291 xfree (quoted_newname);
295 case CO_CONVERT_TO_COMPLETE:
296 /* Convert the link to absolute URL. */
298 char *newlink = link->url->url;
299 char *quoted_newlink = html_quote_string (newlink);
301 if (!link->link_refresh_p)
302 p = replace_attr (p, link->size, fp, quoted_newlink);
304 p = replace_attr_refresh_hack (p, link->size, fp, quoted_newlink,
305 link->refresh_timeout);
307 DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
308 newlink, link->pos, file));
309 xfree (quoted_newlink);
313 case CO_NULLIFY_BASE:
314 /* Change the base href to "". */
315 p = replace_attr (p, link->size, fp, "");
323 /* Output the rest of the file. */
324 if (p - fm->content < fm->length)
325 fwrite (p, 1, fm->length - (p - fm->content), fp);
329 logprintf (LOG_VERBOSE, "%d-%d\n", to_file_count, to_url_count);
332 /* Construct and return a link that points from BASEFILE to LINKFILE.
333 Both files should be local file names, BASEFILE of the referrering
334 file, and LINKFILE of the referred file.
338 cr("foo", "bar") -> "bar"
339 cr("A/foo", "A/bar") -> "bar"
340 cr("A/foo", "A/B/bar") -> "B/bar"
341 cr("A/X/foo", "A/Y/bar") -> "../Y/bar"
342 cr("X/", "Y/bar") -> "../Y/bar" (trailing slash does matter in BASE)
344 Both files should be absolute or relative, otherwise strange
345 results might ensue. The function makes no special efforts to
346 handle "." and ".." in links, so make sure they're not there
347 (e.g. using path_simplify). */
350 construct_relative (const char *basefile, const char *linkfile)
357 /* First, skip the initial directory components common to both
360 for (b = basefile, l = linkfile; *b == *l && *b != '\0'; ++b, ++l)
363 start = (b - basefile) + 1;
368 /* With common directories out of the way, the situation we have is
370 b - b1/b2/[...]/bfile
371 l - l1/l2/[...]/lfile
373 The link we're constructing needs to be:
374 lnk - ../../l1/l2/[...]/lfile
376 Where the number of ".."'s equals the number of bN directory
379 /* Count the directory components in B. */
381 for (b = basefile; *b; b++)
387 /* Construct LINK as explained above. */
388 link = xmalloc (3 * basedirs + strlen (linkfile) + 1);
389 for (i = 0; i < basedirs; i++)
390 memcpy (link + 3 * i, "../", 3);
391 strcpy (link + 3 * i, linkfile);
395 /* Used by write_backup_file to remember which files have been
397 static struct hash_table *converted_files;
400 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
402 /* Rather than just writing over the original .html file with the
403 converted version, save the former to *.orig. Note we only do
404 this for files we've _successfully_ downloaded, so we don't
405 clobber .orig files sitting around from previous invocations. */
407 /* Construct the backup filename as the original name plus ".orig". */
408 size_t filename_len = strlen (file);
409 char* filename_plus_orig_suffix;
411 if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
413 /* Just write "orig" over "html". We need to do it this way
414 because when we're checking to see if we've downloaded the
415 file before (to see if we can skip downloading it), we don't
416 know if it's a text/html file. Therefore we don't know yet
417 at that stage that -E is going to cause us to tack on
418 ".html", so we need to compare vs. the original URL plus
419 ".orig", not the original URL plus ".html.orig". */
420 filename_plus_orig_suffix = alloca (filename_len + 1);
421 strcpy (filename_plus_orig_suffix, file);
422 strcpy ((filename_plus_orig_suffix + filename_len) - 4, "orig");
424 else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
426 /* Append ".orig" to the name. */
427 filename_plus_orig_suffix = alloca (filename_len + sizeof (".orig"));
428 strcpy (filename_plus_orig_suffix, file);
429 strcpy (filename_plus_orig_suffix + filename_len, ".orig");
432 if (!converted_files)
433 converted_files = make_string_hash_table (0);
435 /* We can get called twice on the same URL thanks to the
436 convert_all_links() call in main(). If we write the .orig file
437 each time in such a case, it'll end up containing the first-pass
438 conversion, not the original file. So, see if we've already been
439 called on this file. */
440 if (!string_set_contains (converted_files, file))
442 /* Rename <file> to <file>.orig before former gets written over. */
443 if (rename (file, filename_plus_orig_suffix) != 0)
444 logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
445 file, filename_plus_orig_suffix, strerror (errno));
447 /* Remember that we've already written a .orig backup for this file.
448 Note that we never free this memory since we need it till the
449 convert_all_links() call, which is one of the last things the
450 program does before terminating. BTW, I'm not sure if it would be
451 safe to just set 'converted_file_ptr->string' to 'file' below,
452 rather than making a copy of the string... Another note is that I
453 thought I could just add a field to the urlpos structure saying
454 that we'd written a .orig file for this URL, but that didn't work,
455 so I had to make this separate list.
456 -- Dan Harkless <wget@harkless.org>
458 This [adding a field to the urlpos structure] didn't work
459 because convert_file() is called from convert_all_links at
460 the end of the retrieval with a freshly built new urlpos
462 -- Hrvoje Niksic <hniksic@xemacs.org>
464 string_set_add (converted_files, file);
468 static bool find_fragment (const char *, int, const char **, const char **);
470 /* Replace an attribute's original text with NEW_TEXT. */
473 replace_attr (const char *p, int size, FILE *fp, const char *new_text)
475 bool quote_flag = false;
476 char quote_char = '\"'; /* use "..." for quoting, unless the
477 original value is quoted, in which
478 case reuse its quoting char. */
479 const char *frag_beg, *frag_end;
481 /* Structure of our string is:
483 <--- size ---> (with quotes)
486 <--- size --> (no quotes) */
488 if (*p == '\"' || *p == '\'')
493 size -= 2; /* disregard opening and closing quote */
495 putc (quote_char, fp);
496 fputs (new_text, fp);
498 /* Look for fragment identifier, if any. */
499 if (find_fragment (p, size, &frag_beg, &frag_end))
500 fwrite (frag_beg, 1, frag_end - frag_beg, fp);
504 putc (quote_char, fp);
509 /* The same as REPLACE_ATTR, but used when replacing
510 <meta http-equiv=refresh content="new_text"> because we need to
511 append "timeout_value; URL=" before the next_text. */
514 replace_attr_refresh_hack (const char *p, int size, FILE *fp,
515 const char *new_text, int timeout)
518 char *new_with_timeout = (char *)alloca (numdigit (timeout)
522 sprintf (new_with_timeout, "%d; URL=%s", timeout, new_text);
524 return replace_attr (p, size, fp, new_with_timeout);
527 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
528 preceded by '&'. If the character is not found, return zero. If
529 the character is found, return true and set BP and EP to point to
530 the beginning and end of the region.
532 This is used for finding the fragment indentifiers in URLs. */
535 find_fragment (const char *beg, int size, const char **bp, const char **ep)
537 const char *end = beg + size;
538 bool saw_amp = false;
539 for (; beg < end; beg++)
561 /* Quote FILE for use as local reference to an HTML file.
563 We quote ? as %3F to avoid passing part of the file name as the
564 parameter when browsing the converted file through HTTP. However,
565 it is safe to do this only when `--html-extension' is turned on.
566 This is because converting "index.html?foo=bar" to
567 "index.html%3Ffoo=bar" would break local browsing, as the latter
568 isn't even recognized as an HTML file! However, converting
569 "index.html?foo=bar.html" to "index.html%3Ffoo=bar.html" should be
570 safe for both local and HTTP-served browsing.
572 We always quote "#" as "%23" and "%" as "%25" because those
573 characters have special meanings in URLs. */
576 local_quote_string (const char *file)
581 char *any = strpbrk (file, "?#%");
583 return html_quote_string (file);
585 /* Allocate space assuming the worst-case scenario, each character
586 having to be quoted. */
587 to = newname = (char *)alloca (3 * strlen (file) + 1);
588 for (from = file; *from; from++)
602 if (opt.html_extension)
615 return html_quote_string (newname);
618 /* Book-keeping code for dl_file_url_map, dl_url_file_map,
619 downloaded_html_list, and downloaded_html_set. Other code calls
620 these functions to let us know that a file has been downloaded. */
622 #define ENSURE_TABLES_EXIST do { \
623 if (!dl_file_url_map) \
624 dl_file_url_map = make_string_hash_table (0); \
625 if (!dl_url_file_map) \
626 dl_url_file_map = make_string_hash_table (0); \
629 /* Return true if S1 and S2 are the same, except for "/index.html".
630 The three cases in which it returns one are (substitute any
631 substring for "foo"):
633 m("foo/index.html", "foo/") ==> 1
634 m("foo/", "foo/index.html") ==> 1
635 m("foo", "foo/index.html") ==> 1
636 m("foo", "foo/" ==> 1
637 m("foo", "foo") ==> 1 */
640 match_except_index (const char *s1, const char *s2)
645 /* Skip common substring. */
646 for (i = 0; *s1 && *s2 && *s1 == *s2; s1++, s2++, i++)
649 /* Strings differ at the very beginning -- bail out. We need to
650 check this explicitly to avoid `lng - 1' reading outside the
655 /* Both strings hit EOF -- strings are equal. */
658 /* Strings are randomly different, e.g. "/foo/bar" and "/foo/qux". */
661 /* S1 is the longer one. */
664 /* S2 is the longer one. */
668 /* foo/index.html */ /* or */ /* foo/index.html */
672 /* The right-hand case. */
675 if (*lng == '/' && *(lng + 1) == '\0')
680 return 0 == strcmp (lng, "/index.html");
684 dissociate_urls_from_file_mapper (void *key, void *value, void *arg)
686 char *mapping_url = (char *)key;
687 char *mapping_file = (char *)value;
688 char *file = (char *)arg;
690 if (0 == strcmp (mapping_file, file))
692 hash_table_remove (dl_url_file_map, mapping_url);
694 xfree (mapping_file);
697 /* Continue mapping. */
701 /* Remove all associations from various URLs to FILE from dl_url_file_map. */
704 dissociate_urls_from_file (const char *file)
706 /* Can't use hash_table_iter_* because the table mutates while mapping. */
707 hash_table_for_each (dl_url_file_map, dissociate_urls_from_file_mapper,
711 /* Register that URL has been successfully downloaded to FILE. This
712 is used by the link conversion code to convert references to URLs
713 to references to local files. It is also being used to check if a
714 URL has already been downloaded. */
717 register_download (const char *url, const char *file)
719 char *old_file, *old_url;
723 /* With some forms of retrieval, it is possible, although not likely
724 or particularly desirable. If both are downloaded, the second
725 download will override the first one. When that happens,
726 dissociate the old file name from the URL. */
728 if (hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
730 if (0 == strcmp (url, old_url))
731 /* We have somehow managed to download the same URL twice.
735 if (match_except_index (url, old_url)
736 && !hash_table_contains (dl_url_file_map, url))
737 /* The two URLs differ only in the "index.html" ending. For
738 example, one is "http://www.server.com/", and the other is
739 "http://www.server.com/index.html". Don't remove the old
740 one, just add the new one as a non-canonical entry. */
743 hash_table_remove (dl_file_url_map, file);
747 /* Remove all the URLs that point to this file. Yes, there can
748 be more than one such URL, because we store redirections as
749 multiple entries in dl_url_file_map. For example, if URL1
750 redirects to URL2 which gets downloaded to FILE, we map both
751 URL1 and URL2 to FILE in dl_url_file_map. (dl_file_url_map
752 only points to URL2.) When another URL gets loaded to FILE,
753 we want both URL1 and URL2 dissociated from it.
755 This is a relatively expensive operation because it performs
756 a linear search of the whole hash table, but it should be
757 called very rarely, only when two URLs resolve to the same
758 file name, *and* the "<file>.1" extensions are turned off.
759 In other words, almost never. */
760 dissociate_urls_from_file (file);
763 hash_table_put (dl_file_url_map, xstrdup (file), xstrdup (url));
766 /* A URL->FILE mapping is not possible without a FILE->URL mapping.
767 If the latter were present, it should have been removed by the
768 above `if'. So we could write:
770 assert (!hash_table_contains (dl_url_file_map, url));
772 The above is correct when running in recursive mode where the
773 same URL always resolves to the same file. But if you do
778 then the first URL will resolve to "FILE", and the other to
779 "FILE.1". In that case, FILE.1 will not be found in
780 dl_file_url_map, but URL will still point to FILE in
782 if (hash_table_get_pair (dl_url_file_map, url, &old_url, &old_file))
784 hash_table_remove (dl_url_file_map, url);
789 hash_table_put (dl_url_file_map, xstrdup (url), xstrdup (file));
792 /* Register that FROM has been redirected to TO. This assumes that TO
793 is successfully downloaded and already registered using
794 register_download() above. */
797 register_redirection (const char *from, const char *to)
803 file = hash_table_get (dl_url_file_map, to);
804 assert (file != NULL);
805 if (!hash_table_contains (dl_url_file_map, from))
806 hash_table_put (dl_url_file_map, xstrdup (from), xstrdup (file));
809 /* Register that the file has been deleted. */
812 register_delete_file (const char *file)
814 char *old_url, *old_file;
818 if (!hash_table_get_pair (dl_file_url_map, file, &old_file, &old_url))
821 hash_table_remove (dl_file_url_map, file);
824 dissociate_urls_from_file (file);
827 /* Register that FILE is an HTML file that has been downloaded. */
830 register_html (const char *url, const char *file)
832 if (!downloaded_html_set)
833 downloaded_html_set = make_string_hash_table (0);
834 string_set_add (downloaded_html_set, file);
837 static void downloaded_files_free (void);
839 /* Cleanup the data structures associated with this file. */
842 convert_cleanup (void)
846 free_keys_and_values (dl_file_url_map);
847 hash_table_destroy (dl_file_url_map);
848 dl_file_url_map = NULL;
852 free_keys_and_values (dl_url_file_map);
853 hash_table_destroy (dl_url_file_map);
854 dl_url_file_map = NULL;
856 if (downloaded_html_set)
857 string_set_free (downloaded_html_set);
858 downloaded_files_free ();
860 string_set_free (converted_files);
863 /* Book-keeping code for downloaded files that enables extension
866 /* This table should really be merged with dl_file_url_map and
867 downloaded_html_files. This was originally a list, but I changed
868 it to a hash table beause it was actually taking a lot of time to
869 find things in it. */
871 static struct hash_table *downloaded_files_hash;
873 /* We're storing "modes" of type downloaded_file_t in the hash table.
874 However, our hash tables only accept pointers for keys and values.
875 So when we need a pointer, we use the address of a
876 downloaded_file_t variable of static storage. */
878 static downloaded_file_t *
879 downloaded_mode_to_ptr (downloaded_file_t mode)
881 static downloaded_file_t
882 v1 = FILE_NOT_ALREADY_DOWNLOADED,
883 v2 = FILE_DOWNLOADED_NORMALLY,
884 v3 = FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED,
889 case FILE_NOT_ALREADY_DOWNLOADED:
891 case FILE_DOWNLOADED_NORMALLY:
893 case FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED:
901 /* Remembers which files have been downloaded. In the standard case,
902 should be called with mode == FILE_DOWNLOADED_NORMALLY for each
903 file we actually download successfully (i.e. not for ones we have
904 failures on or that we skip due to -N).
906 When we've downloaded a file and tacked on a ".html" extension due
907 to -E, call this function with
908 FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
909 FILE_DOWNLOADED_NORMALLY.
911 If you just want to check if a file has been previously added
912 without adding it, call with mode == CHECK_FOR_FILE. Please be
913 sure to call this function with local filenames, not remote
917 downloaded_file (downloaded_file_t mode, const char *file)
919 downloaded_file_t *ptr;
921 if (mode == CHECK_FOR_FILE)
923 if (!downloaded_files_hash)
924 return FILE_NOT_ALREADY_DOWNLOADED;
925 ptr = hash_table_get (downloaded_files_hash, file);
927 return FILE_NOT_ALREADY_DOWNLOADED;
931 if (!downloaded_files_hash)
932 downloaded_files_hash = make_string_hash_table (0);
934 ptr = hash_table_get (downloaded_files_hash, file);
938 ptr = downloaded_mode_to_ptr (mode);
939 hash_table_put (downloaded_files_hash, xstrdup (file), ptr);
941 return FILE_NOT_ALREADY_DOWNLOADED;
945 downloaded_files_free (void)
947 if (downloaded_files_hash)
949 hash_table_iterator iter;
950 for (hash_table_iterate (downloaded_files_hash, &iter);
951 hash_table_iter_next (&iter);
954 hash_table_destroy (downloaded_files_hash);
955 downloaded_files_hash = NULL;
959 /* The function returns the pointer to the malloc-ed quoted version of
960 string s. It will recognize and quote numeric and special graphic
961 entities, as per RFC1866:
969 No other entities are recognized or replaced. */
971 html_quote_string (const char *s)
977 /* Pass through the string, and count the new size. */
978 for (i = 0; *s; s++, i++)
982 else if (*s == '<' || *s == '>')
983 i += 3; /* `lt;' and `gt;' */
985 i += 5; /* `quot;' */
989 res = xmalloc (i + 1);
991 for (p = res; *s; s++)
1004 *p++ = (*s == '<' ? 'l' : 'g');