]> sjero.net Git - wget/blobdiff - src/html-url.c
[svn] Make the first argument to hash_table_new a minimal count of items before
[wget] / src / html-url.c
index 73425c05913b8ebd4c2ee372ff9cdef8dcf3aaca..80f5b96c19ffcec006a0f9d3565868ddd11be95a 100644 (file)
@@ -1,12 +1,12 @@
 /* Collect URLs from HTML source.
-   Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc.
+   Copyright (C) 1998, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
 
 GNU Wget is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+ (at your option) any later version.
 
 GNU Wget is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,7 +15,17 @@ GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with Wget; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+In addition, as a special exception, the Free Software Foundation
+gives permission to link the code of its release of Wget with the
+OpenSSL project's "OpenSSL" library (or with modified versions of it
+that use the same license as the "OpenSSL" library), and distribute
+the linked executables.  You must obey the GNU General Public License
+in all respects for all of the code used other than "OpenSSL".  If you
+modify this file, you may extend this exception to your version of the
+file, but you are not obligated to do so.  If you do not wish to do
+so, delete this exception statement from your version.  */
 
 #include <config.h>
 
@@ -33,6 +43,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include "html-parse.h"
 #include "url.h"
 #include "utils.h"
+#include "hash.h"
+#include "convert.h"
 
 #ifndef errno
 extern int errno;
@@ -48,55 +60,62 @@ typedef void (*tag_handler_t) PARAMS ((int, struct taginfo *,
 
 DECLARE_TAG_HANDLER (tag_find_urls);
 DECLARE_TAG_HANDLER (tag_handle_base);
+DECLARE_TAG_HANDLER (tag_handle_form);
 DECLARE_TAG_HANDLER (tag_handle_link);
 DECLARE_TAG_HANDLER (tag_handle_meta);
 
+enum {
+  TAG_A,
+  TAG_APPLET,
+  TAG_AREA,
+  TAG_BASE,
+  TAG_BGSOUND,
+  TAG_BODY,
+  TAG_EMBED,
+  TAG_FIG,
+  TAG_FORM,
+  TAG_FRAME,
+  TAG_IFRAME,
+  TAG_IMG,
+  TAG_INPUT,
+  TAG_LAYER,
+  TAG_LINK,
+  TAG_META,
+  TAG_OVERLAY,
+  TAG_SCRIPT,
+  TAG_TABLE,
+  TAG_TD,
+  TAG_TH
+};
+
 /* The list of known tags and functions used for handling them.  Most
    tags are simply harvested for URLs. */
-static struct {
+static struct known_tag {
+  int tagid;
   const char *name;
   tag_handler_t handler;
 } known_tags[] = {
-#define TAG_A          0
-  { "a",       tag_find_urls },
-#define TAG_APPLET     1
-  { "applet",  tag_find_urls },
-#define TAG_AREA       2
-  { "area",    tag_find_urls },
-#define TAG_BASE       3
-  { "base",    tag_handle_base },
-#define TAG_BGSOUND    4
-  { "bgsound", tag_find_urls },
-#define TAG_BODY       5
-  { "body",    tag_find_urls },
-#define TAG_EMBED      6
-  { "embed",   tag_find_urls },
-#define TAG_FIG                7
-  { "fig",     tag_find_urls },
-#define TAG_FRAME      8
-  { "frame",   tag_find_urls },
-#define TAG_IFRAME     9
-  { "iframe",  tag_find_urls },
-#define TAG_IMG                10
-  { "img",     tag_find_urls },
-#define TAG_INPUT      11
-  { "input",   tag_find_urls },
-#define TAG_LAYER      12
-  { "layer",   tag_find_urls },
-#define TAG_LINK       13
-  { "link",    tag_handle_link },
-#define TAG_META       14
-  { "meta",    tag_handle_meta },
-#define TAG_OVERLAY    15
-  { "overlay", tag_find_urls },
-#define TAG_SCRIPT     16
-  { "script",  tag_find_urls },
-#define TAG_TABLE      17
-  { "table",   tag_find_urls },
-#define TAG_TD         18
-  { "td",      tag_find_urls },
-#define TAG_TH         19
-  { "th",      tag_find_urls }
+  { TAG_A,      "a",           tag_find_urls },
+  { TAG_APPLET,         "applet",      tag_find_urls },
+  { TAG_AREA,   "area",        tag_find_urls },
+  { TAG_BASE,   "base",        tag_handle_base },
+  { TAG_BGSOUND, "bgsound",    tag_find_urls },
+  { TAG_BODY,   "body",        tag_find_urls },
+  { TAG_EMBED,  "embed",       tag_find_urls },
+  { TAG_FIG,    "fig",         tag_find_urls },
+  { TAG_FORM,   "form",        tag_handle_form },
+  { TAG_FRAME,  "frame",       tag_find_urls },
+  { TAG_IFRAME,         "iframe",      tag_find_urls },
+  { TAG_IMG,    "img",         tag_find_urls },
+  { TAG_INPUT,  "input",       tag_find_urls },
+  { TAG_LAYER,  "layer",       tag_find_urls },
+  { TAG_LINK,   "link",        tag_handle_link },
+  { TAG_META,   "meta",        tag_handle_meta },
+  { TAG_OVERLAY, "overlay",    tag_find_urls },
+  { TAG_SCRIPT,         "script",      tag_find_urls },
+  { TAG_TABLE,  "table",       tag_find_urls },
+  { TAG_TD,     "td",          tag_find_urls },
+  { TAG_TH,     "th",          tag_find_urls }
 };
 
 /* tag_url_attributes documents which attributes of which tags contain
@@ -120,7 +139,7 @@ static struct {
   { TAG_AREA,          "href",         TUA_EXTERNAL },
   { TAG_BGSOUND,       "src",          0 },
   { TAG_BODY,          "background",   0 },
-  { TAG_EMBED,         "href",         0 },
+  { TAG_EMBED,         "href",         TUA_EXTERNAL },
   { TAG_EMBED,         "src",          0 },
   { TAG_FIG,           "src",          0 },
   { TAG_FRAME,         "src",          0 },
@@ -141,14 +160,15 @@ static struct {
    from the information above.  However, some places in the code refer
    to the attributes not mentioned here.  We add them manually.  */
 static const char *additional_attributes[] = {
-  "rel",                       /* for TAG_LINK */
-  "http-equiv",                        /* for TAG_META */
-  "name",                      /* for TAG_META */
-  "content"                    /* for TAG_META */
+  "rel",                       /* used by tag_handle_link */
+  "http-equiv",                        /* used by tag_handle_meta */
+  "name",                      /* used by tag_handle_meta */
+  "content",                   /* used by tag_handle_meta */
+  "action"                     /* used by tag_handle_form */
 };
 
-static const char **interesting_tags;
-static const char **interesting_attributes;
+struct hash_table *interesting_tags;
+struct hash_table *interesting_attributes;
 
 static void
 init_interesting (void)
@@ -160,125 +180,54 @@ init_interesting (void)
 
      Here we also make sure that what we put in interesting_tags
      matches the user's preferences as specified through --ignore-tags
-     and --follow-tags.
-
-     This function is as large as this only because of the glorious
-     expressivity of the C programming language.  */
+     and --follow-tags.  */
 
-  {
-    int i, ind = 0;
-    int size = ARRAY_SIZE (known_tags);
-    interesting_tags = (const char **)xmalloc ((size + 1) * sizeof (char *));
-
-    for (i = 0; i < size; i++)
-      {
-       const char *name = known_tags[i].name;
-
-       /* Normally here we could say:
-          interesting_tags[i] = name;
-          But we need to respect the settings of --ignore-tags and
-          --follow-tags, so the code gets a bit hairier.  */
-
-       if (opt.ignore_tags)
-         {
-           /* --ignore-tags was specified.  Do not match these
-              specific tags.  --ignore-tags takes precedence over
-              --follow-tags, so we process --ignore first and fall
-              through if there's no match. */
-           int j, lose = 0;
-           for (j = 0; opt.ignore_tags[j] != NULL; j++)
-             /* Loop through all the tags this user doesn't care about. */
-             if (strcasecmp(opt.ignore_tags[j], name) == EQ)
-               {
-                 lose = 1;
-                 break;
-               }
-           if (lose)
-             continue;
-         }
-
-       if (opt.follow_tags)
-         {
-           /* --follow-tags was specified.  Only match these specific tags, so
-              continue back to top of for if we don't match one of them. */
-           int j, win = 0;
-           for (j = 0; opt.follow_tags[j] != NULL; j++)
-             /* Loop through all the tags this user cares about. */
-             if (strcasecmp(opt.follow_tags[j], name) == EQ)
-               {
-                 win = 1;
-                 break;
-               }
-           if (!win)
-             continue;  /* wasn't one of the explicitly desired tags */
-         }
-
-       /* If we get to here, --follow-tags isn't being used or the
-          tag is among the ones that are followed, and --ignore-tags,
-          if specified, didn't include this tag, so it's an
-          "interesting" one. */
-       interesting_tags[ind++] = name;
-      }
-    interesting_tags[ind] = NULL;
-  }
-
-  /* The same for attributes, except we loop through tag_url_attributes.
-     Here we also need to make sure that the list of attributes is
-     unique, and to include the attributes from additional_attributes.  */
-  {
-    int i, ind;
-    const char **att = xmalloc ((ARRAY_SIZE (additional_attributes) + 1)
-                               * sizeof (char *));
-    /* First copy the "additional" attributes. */
-    for (i = 0; i < ARRAY_SIZE (additional_attributes); i++)
-      att[i] = additional_attributes[i];
-    ind = i;
-    att[ind] = NULL;
-    for (i = 0; i < ARRAY_SIZE (tag_url_attributes); i++)
-      {
-       int j, seen = 0;
-       const char *look_for = tag_url_attributes[i].attr_name;
-       for (j = 0; j < ind - 1; j++)
-         if (!strcmp (att[j], look_for))
-           {
-             seen = 1;
-             break;
-           }
-       if (!seen)
-         {
-           att = xrealloc (att, (ind + 2) * sizeof (*att));
-           att[ind++] = look_for;
-           att[ind] = NULL;
-         }
-      }
-    interesting_attributes = att;
-  }
-}
-
-static int
-find_tag (const char *tag_name)
-{
   int i;
+  interesting_tags = make_nocase_string_hash_table (countof (known_tags));
 
-  /* This is linear search; if the number of tags grow, we can switch
-     to binary search.  */
+  /* First, add all the tags we know hot to handle, mapped to their
+     respective entries in known_tags.  */
+  for (i = 0; i < countof (known_tags); i++)
+    hash_table_put (interesting_tags, known_tags[i].name, known_tags + i);
 
-  for (i = 0; i < ARRAY_SIZE (known_tags); i++)
+  /* Then remove the tags ignored through --ignore-tags.  */
+  if (opt.ignore_tags)
     {
-      int cmp = strcasecmp (known_tags[i].name, tag_name);
-      /* known_tags are sorted alphabetically, so we can
-         micro-optimize.  */
-      if (cmp > 0)
-       break;
-      else if (cmp == 0)
-       return i;
+      char **ignored;
+      for (ignored = opt.ignore_tags; *ignored; ignored++)
+       hash_table_remove (interesting_tags, *ignored);
+    }
+
+  /* If --follow-tags is specified, use only those tags.  */
+  if (opt.follow_tags)
+    {
+      /* Create a new table intersecting --follow-tags and known_tags,
+        and use it as interesting_tags.  */
+      struct hash_table *intersect = make_nocase_string_hash_table (0);
+      char **followed;
+      for (followed = opt.follow_tags; *followed; followed++)
+       {
+         struct known_tag *t = hash_table_get (interesting_tags, *followed);
+         if (!t)
+           continue;           /* ignore unknown --follow-tags entries. */
+         hash_table_put (intersect, *followed, t);
+       }
+      hash_table_destroy (interesting_tags);
+      interesting_tags = intersect;
     }
-  return -1;
+
+  /* Add the attributes we care about. */
+  interesting_attributes = make_nocase_string_hash_table (10);
+  for (i = 0; i < countof (additional_attributes); i++)
+    string_set_add (interesting_attributes, additional_attributes[i]);
+  for (i = 0; i < countof (tag_url_attributes); i++)
+    string_set_add (interesting_attributes, tag_url_attributes[i].attr_name);
 }
 
 /* Find the value of attribute named NAME in the taginfo TAG.  If the
    attribute is not present, return NULL.  If ATTRIND is non-NULL, the
    index of the attribute in TAG will be stored there.  */
+
 static char *
 find_attr (struct taginfo *tag, const char *name, int *attrind)
 {
@@ -406,10 +355,10 @@ append_one_url (const char *link_uri, int inlinep,
 static void
 tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
 {
-  int i, attrind, first = -1;
-  int size = ARRAY_SIZE (tag_url_attributes);
+  int i, attrind;
+  int first = -1;
 
-  for (i = 0; i < size; i++)
+  for (i = 0; i < countof (tag_url_attributes); i++)
     if (tag_url_attributes[i].tagid == tagid)
       {
        /* We've found the index of tag_url_attributes where the
@@ -433,11 +382,12 @@ tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
       /* Find whether TAG/ATTRIND is a combination that contains a
         URL. */
       char *link = tag->attrs[attrind].value;
+      const int size = countof (tag_url_attributes);
 
       /* If you're cringing at the inefficiency of the nested loops,
-        remember that they both iterate over a laughably small
-        quantity of items.  The worst-case inner loop is for the IMG
-        tag, which has three attributes.  */
+        remember that they both iterate over a very small number of
+        items.  The worst-case inner loop is for the IMG tag, which
+        has three attributes.  */
       for (i = first; i < size && tag_url_attributes[i].tagid == tagid; i++)
        {
          if (0 == strcasecmp (tag->attrs[attrind].name,
@@ -475,6 +425,22 @@ tag_handle_base (int tagid, struct taginfo *tag, struct map_context *ctx)
     ctx->base = xstrdup (newbase);
 }
 
+/* Mark the URL found in <form action=...> for conversion. */
+
+static void
+tag_handle_form (int tagid, struct taginfo *tag, struct map_context *ctx)
+{
+  int attrind;
+  char *action = find_attr (tag, "action", &attrind);
+  if (action)
+    {
+      struct urlpos *action_urlpos = append_one_url (action, 0, tag,
+                                                    attrind, ctx);
+      if (action_urlpos)
+       action_urlpos->ignore_when_downloading = 1;
+    }
+}
+
 /* Handle the LINK tag.  It requires special handling because how its
    links will be followed in -p mode depends on the REL attribute.  */
 
@@ -484,12 +450,18 @@ tag_handle_link (int tagid, struct taginfo *tag, struct map_context *ctx)
   int attrind;
   char *href = find_attr (tag, "href", &attrind);
 
-  /* All <link href="..."> link references are external,
-     except for <link rel="stylesheet" href="...">.  */
+  /* All <link href="..."> link references are external, except those
+     known not to be, such as style sheet and shortcut icon:
+
+       <link rel="stylesheet" href="...">
+       <link rel="shortcut icon" href="...">
+  */
   if (href)
     {
       char *rel  = find_attr (tag, "rel", NULL);
-      int inlinep = (rel && 0 == strcasecmp (rel, "stylesheet"));
+      int inlinep = (rel
+                    && (0 == strcasecmp (rel, "stylesheet")
+                        || 0 == strcasecmp (rel, "shortcut icon")));
       append_one_url (href, inlinep, tag, attrind, ctx);
     }
 }
@@ -515,10 +487,13 @@ tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
         get to the URL.  */
 
       struct urlpos *entry;
-
       int attrind;
-      char *p, *refresh = find_attr (tag, "content", &attrind);
       int timeout = 0;
+      char *p;
+
+      char *refresh = find_attr (tag, "content", &attrind);
+      if (!refresh)
+       return;
 
       for (p = refresh; ISDIGIT (*p); p++)
        timeout = 10 * timeout + *p - '0';
@@ -571,31 +546,32 @@ tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
     }
 }
 
-/* Examine name and attributes of TAG and take appropriate action
-   according to the tag.  */
+/* Dispatch the tag handler appropriate for the tag we're mapping
+   over.  See known_tags[] for definition of tag handlers.  */
 
 static void
 collect_tags_mapper (struct taginfo *tag, void *arg)
 {
   struct map_context *ctx = (struct map_context *)arg;
-  int tagid;
-  tag_handler_t handler;
 
-  tagid = find_tag (tag->name);
-  assert (tagid != -1);
-  handler = known_tags[tagid].handler;
+  /* Find the tag in our table of tags.  This must not fail because
+     map_html_tags only returns tags found in interesting_tags.  */
+  struct known_tag *t = hash_table_get (interesting_tags, tag->name);
+  assert (t != NULL);
 
-  handler (tagid, tag, ctx);
+  t->handler (t->tagid, tag, ctx);
 }
 \f
 /* Analyze HTML tags FILE and construct a list of URLs referenced from
    it.  It merges relative links in FILE with URL.  It is aware of
    <base href=...> and does the right thing.  */
+
 struct urlpos *
 get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
 {
   struct file_memory *fm;
   struct map_context ctx;
+  int flags;
 
   /* Load the file. */
   fm = read_file (file);
@@ -616,8 +592,16 @@ get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
   if (!interesting_tags)
     init_interesting ();
 
-  map_html_tags (fm->content, fm->length, interesting_tags,
-                interesting_attributes, collect_tags_mapper, &ctx);
+  /* Specify MHT_TRIM_VALUES because of buggy HTML generators that
+     generate <a href=" foo"> instead of <a href="foo"> (Netscape
+     ignores spaces as well.)  If you really mean space, use &32; or
+     %20.  */
+  flags = MHT_TRIM_VALUES;
+  if (opt.strict_comments)
+    flags |= MHT_STRICT_COMMENTS;
+
+  map_html_tags (fm->content, fm->length, collect_tags_mapper, &ctx, flags,
+                interesting_tags, interesting_attributes);
 
   DEBUGP (("no-follow in %s: %d\n", file, ctx.nofollow));
   if (meta_disallow_follow)
@@ -628,6 +612,91 @@ get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
   return ctx.head;
 }
 
+/* This doesn't really have anything to do with HTML, but it's similar
+   to get_urls_html, so we put it here.  */
+
+struct urlpos *
+get_urls_file (const char *file)
+{
+  struct file_memory *fm;
+  struct urlpos *head, *tail;
+  const char *text, *text_end;
+
+  /* Load the file.  */
+  fm = read_file (file);
+  if (!fm)
+    {
+      logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
+      return NULL;
+    }
+  DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
+
+  head = tail = NULL;
+  text = fm->content;
+  text_end = fm->content + fm->length;
+  while (text < text_end)
+    {
+      int up_error_code;
+      char *url_text;
+      struct urlpos *entry;
+      struct url *url;
+
+      const char *line_beg = text;
+      const char *line_end = memchr (text, '\n', text_end - text);
+      if (!line_end)
+       line_end = text_end;
+      else
+       ++line_end;
+      text = line_end;
+
+      /* Strip whitespace from the beginning and end of line. */
+      while (line_beg < line_end && ISSPACE (*line_beg))
+       ++line_beg;
+      while (line_end > line_beg && ISSPACE (*(line_end - 1)))
+       --line_end;
+
+      if (line_beg == line_end)
+       continue;
+
+      /* The URL is in the [line_beg, line_end) region. */
+
+      /* We must copy the URL to a zero-terminated string, and we
+        can't use alloca because we're in a loop.  *sigh*.  */
+      url_text = strdupdelim (line_beg, line_end);
+
+      if (opt.base_href)
+       {
+         /* Merge opt.base_href with URL. */
+         char *merged = uri_merge (opt.base_href, url_text);
+         xfree (url_text);
+         url_text = merged;
+       }
+
+      url = url_parse (url_text, &up_error_code);
+      if (!url)
+       {
+         logprintf (LOG_NOTQUIET, "%s: Invalid URL %s: %s\n",
+                    file, url_text, url_error (up_error_code));
+         xfree (url_text);
+         continue;
+       }
+      xfree (url_text);
+
+      entry = (struct urlpos *)xmalloc (sizeof (struct urlpos));
+      memset (entry, 0, sizeof (*entry));
+      entry->next = NULL;
+      entry->url = url;
+
+      if (!head)
+       head = entry;
+      else
+       tail->next = entry;
+      tail = entry;
+    }
+  read_file_free (fm);
+  return head;
+}
+
 void
 cleanup_html_url (void)
 {