2000-11-19 15:50:10 -05:00
|
|
|
|
/* Collect URLs from HTML source.
|
2003-10-09 11:01:58 -04:00
|
|
|
|
Copyright (C) 1998, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-05-27 15:35:15 -04:00
|
|
|
|
This file is part of GNU Wget.
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-05-27 15:35:15 -04:00
|
|
|
|
GNU Wget is free software; you can redistribute it and/or modify
|
2000-11-19 15:50:10 -05:00
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
2003-10-09 11:01:58 -04:00
|
|
|
|
(at your option) any later version.
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-05-27 15:35:15 -04:00
|
|
|
|
GNU Wget is distributed in the hope that it will be useful,
|
2000-11-19 15:50:10 -05:00
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
2001-05-27 15:35:15 -04:00
|
|
|
|
along with Wget; if not, write to the Free Software
|
2002-05-17 22:16:36 -04:00
|
|
|
|
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. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2005-06-19 18:34:58 -04:00
|
|
|
|
#include <string.h>
|
2000-11-19 15:50:10 -05:00
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
#include "wget.h"
|
|
|
|
|
#include "html-parse.h"
|
|
|
|
|
#include "url.h"
|
|
|
|
|
#include "utils.h"
|
2003-10-09 11:01:58 -04:00
|
|
|
|
#include "hash.h"
|
2003-09-21 18:47:14 -04:00
|
|
|
|
#include "convert.h"
|
2005-05-08 05:58:39 -04:00
|
|
|
|
#include "recur.h" /* declaration of get_urls_html */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
struct map_context;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2005-06-19 18:34:58 -04:00
|
|
|
|
typedef void (*tag_handler_t) (int, struct taginfo *, struct map_context *);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
2005-06-19 18:34:58 -04:00
|
|
|
|
#define DECLARE_TAG_HANDLER(fun) \
|
|
|
|
|
static void fun (int, struct taginfo *, struct map_context *)
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
DECLARE_TAG_HANDLER (tag_find_urls);
|
|
|
|
|
DECLARE_TAG_HANDLER (tag_handle_base);
|
2002-04-11 13:51:45 -04:00
|
|
|
|
DECLARE_TAG_HANDLER (tag_handle_form);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
DECLARE_TAG_HANDLER (tag_handle_link);
|
|
|
|
|
DECLARE_TAG_HANDLER (tag_handle_meta);
|
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
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,
|
2003-11-08 20:33:33 -05:00
|
|
|
|
TAG_OBJECT,
|
2003-10-09 11:01:58 -04:00
|
|
|
|
TAG_OVERLAY,
|
|
|
|
|
TAG_SCRIPT,
|
|
|
|
|
TAG_TABLE,
|
|
|
|
|
TAG_TD,
|
|
|
|
|
TAG_TH
|
|
|
|
|
};
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
/* The list of known tags and functions used for handling them. Most
|
|
|
|
|
tags are simply harvested for URLs. */
|
2003-10-09 11:01:58 -04:00
|
|
|
|
static struct known_tag {
|
|
|
|
|
int tagid;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
const char *name;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
tag_handler_t handler;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
} known_tags[] = {
|
2003-10-09 11:01:58 -04:00
|
|
|
|
{ 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 },
|
2003-11-08 20:33:33 -05:00
|
|
|
|
{ TAG_OBJECT, "object", tag_find_urls },
|
2003-10-09 11:01:58 -04:00
|
|
|
|
{ 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 }
|
2000-11-19 15:50:10 -05:00
|
|
|
|
};
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
/* tag_url_attributes documents which attributes of which tags contain
|
|
|
|
|
URLs to harvest. It is used by tag_find_urls. */
|
2001-01-09 21:28:24 -05:00
|
|
|
|
|
2003-10-10 10:25:10 -04:00
|
|
|
|
/* Defines for the FLAGS. */
|
2001-01-09 21:28:24 -05:00
|
|
|
|
|
2003-10-10 10:25:10 -04:00
|
|
|
|
/* The link is "inline", i.e. needs to be retrieved for this document
|
|
|
|
|
to be correctly rendered. Inline links include inlined images,
|
|
|
|
|
stylesheets, children frames, etc. */
|
|
|
|
|
#define ATTR_INLINE 1
|
|
|
|
|
|
|
|
|
|
/* The link is expected to yield HTML contents. It's important not to
|
|
|
|
|
try to follow HTML obtained by following e.g. <img src="...">
|
|
|
|
|
regardless of content-type. Doing this causes infinite loops for
|
|
|
|
|
"images" that return non-404 error pages with links to the same
|
|
|
|
|
image. */
|
|
|
|
|
#define ATTR_HTML 2
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
/* For tags handled by tag_find_urls: attributes that contain URLs to
|
2000-11-19 15:50:10 -05:00
|
|
|
|
download. */
|
|
|
|
|
static struct {
|
|
|
|
|
int tagid;
|
|
|
|
|
const char *attr_name;
|
|
|
|
|
int flags;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
} tag_url_attributes[] = {
|
2003-10-10 10:25:10 -04:00
|
|
|
|
{ TAG_A, "href", ATTR_HTML },
|
|
|
|
|
{ TAG_APPLET, "code", ATTR_INLINE },
|
|
|
|
|
{ TAG_AREA, "href", ATTR_HTML },
|
|
|
|
|
{ TAG_BGSOUND, "src", ATTR_INLINE },
|
|
|
|
|
{ TAG_BODY, "background", ATTR_INLINE },
|
|
|
|
|
{ TAG_EMBED, "href", ATTR_HTML },
|
|
|
|
|
{ TAG_EMBED, "src", ATTR_INLINE | ATTR_HTML },
|
|
|
|
|
{ TAG_FIG, "src", ATTR_INLINE },
|
|
|
|
|
{ TAG_FRAME, "src", ATTR_INLINE | ATTR_HTML },
|
|
|
|
|
{ TAG_IFRAME, "src", ATTR_INLINE | ATTR_HTML },
|
|
|
|
|
{ TAG_IMG, "href", ATTR_INLINE },
|
|
|
|
|
{ TAG_IMG, "lowsrc", ATTR_INLINE },
|
|
|
|
|
{ TAG_IMG, "src", ATTR_INLINE },
|
|
|
|
|
{ TAG_INPUT, "src", ATTR_INLINE },
|
|
|
|
|
{ TAG_LAYER, "src", ATTR_INLINE | ATTR_HTML },
|
2003-11-08 20:33:33 -05:00
|
|
|
|
{ TAG_OBJECT, "data", ATTR_INLINE },
|
2003-10-10 10:25:10 -04:00
|
|
|
|
{ TAG_OVERLAY, "src", ATTR_INLINE | ATTR_HTML },
|
|
|
|
|
{ TAG_SCRIPT, "src", ATTR_INLINE },
|
|
|
|
|
{ TAG_TABLE, "background", ATTR_INLINE },
|
|
|
|
|
{ TAG_TD, "background", ATTR_INLINE },
|
|
|
|
|
{ TAG_TH, "background", ATTR_INLINE }
|
2000-11-19 15:50:10 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* The lists of interesting tags and attributes are built dynamically,
|
|
|
|
|
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[] = {
|
2002-04-11 13:51:45 -04:00
|
|
|
|
"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 */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
};
|
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
struct hash_table *interesting_tags;
|
|
|
|
|
struct hash_table *interesting_attributes;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-08 20:24:41 -05:00
|
|
|
|
static void
|
2000-11-19 15:50:10 -05:00
|
|
|
|
init_interesting (void)
|
|
|
|
|
{
|
|
|
|
|
/* Init the variables interesting_tags and interesting_attributes
|
|
|
|
|
that are used by the HTML parser to know which tags and
|
|
|
|
|
attributes we're interested in. We initialize this only once,
|
|
|
|
|
for performance reasons.
|
|
|
|
|
|
|
|
|
|
Here we also make sure that what we put in interesting_tags
|
|
|
|
|
matches the user's preferences as specified through --ignore-tags
|
2003-10-09 11:01:58 -04:00
|
|
|
|
and --follow-tags. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
int i;
|
|
|
|
|
interesting_tags = make_nocase_string_hash_table (countof (known_tags));
|
2003-10-08 12:00:10 -04:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* 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);
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* Then remove the tags ignored through --ignore-tags. */
|
|
|
|
|
if (opt.ignore_tags)
|
|
|
|
|
{
|
|
|
|
|
char **ignored;
|
|
|
|
|
for (ignored = opt.ignore_tags; *ignored; ignored++)
|
|
|
|
|
hash_table_remove (interesting_tags, *ignored);
|
|
|
|
|
}
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* If --follow-tags is specified, use only those tags. */
|
|
|
|
|
if (opt.follow_tags)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2003-10-09 22:46:09 -04:00
|
|
|
|
/* Create a new table intersecting --follow-tags and known_tags,
|
|
|
|
|
and use it as interesting_tags. */
|
2003-10-09 11:01:58 -04:00
|
|
|
|
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)
|
2003-10-09 22:46:09 -04:00
|
|
|
|
continue; /* ignore unknown --follow-tags entries. */
|
2003-10-09 11:01:58 -04:00
|
|
|
|
hash_table_put (intersect, *followed, t);
|
|
|
|
|
}
|
|
|
|
|
hash_table_destroy (interesting_tags);
|
|
|
|
|
interesting_tags = intersect;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
2003-10-08 12:00:10 -04:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* Add the attributes we care about. */
|
2003-10-09 22:46:09 -04:00
|
|
|
|
interesting_attributes = make_nocase_string_hash_table (10);
|
2003-10-09 11:01:58 -04:00
|
|
|
|
for (i = 0; i < countof (additional_attributes); i++)
|
2003-11-02 16:12:49 -05:00
|
|
|
|
hash_table_put (interesting_attributes, additional_attributes[i], "1");
|
2003-10-09 11:01:58 -04:00
|
|
|
|
for (i = 0; i < countof (tag_url_attributes); i++)
|
2003-11-02 16:12:49 -05:00
|
|
|
|
hash_table_put (interesting_attributes,
|
|
|
|
|
tag_url_attributes[i].attr_name, "1");
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find the value of attribute named NAME in the taginfo TAG. If the
|
2001-12-12 10:43:01 -05:00
|
|
|
|
attribute is not present, return NULL. If ATTRIND is non-NULL, the
|
|
|
|
|
index of the attribute in TAG will be stored there. */
|
2003-10-08 12:00:10 -04:00
|
|
|
|
|
2000-11-19 15:50:10 -05:00
|
|
|
|
static char *
|
2001-12-12 10:43:01 -05:00
|
|
|
|
find_attr (struct taginfo *tag, const char *name, int *attrind)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < tag->nattrs; i++)
|
|
|
|
|
if (!strcasecmp (tag->attrs[i].name, name))
|
|
|
|
|
{
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (attrind)
|
|
|
|
|
*attrind = i;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
return tag->attrs[i].value;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
struct map_context {
|
2000-11-19 15:50:10 -05:00
|
|
|
|
char *text; /* HTML text. */
|
|
|
|
|
char *base; /* Base URI of the document, possibly
|
|
|
|
|
changed through <base href=...>. */
|
|
|
|
|
const char *parent_base; /* Base of the current document. */
|
|
|
|
|
const char *document_file; /* File name of this document. */
|
|
|
|
|
int nofollow; /* whether NOFOLLOW was specified in a
|
|
|
|
|
<meta name=robots> tag. */
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
struct urlpos *head, *tail; /* List of URLs that is being
|
|
|
|
|
built. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
};
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
/* Append LINK_URI to the urlpos structure that is being built.
|
|
|
|
|
|
|
|
|
|
LINK_URI will be merged with the current document base. TAG and
|
|
|
|
|
ATTRIND are the necessary context to store the position and
|
|
|
|
|
size. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-11-25 13:40:55 -05:00
|
|
|
|
static struct urlpos *
|
2003-10-13 10:32:25 -04:00
|
|
|
|
append_url (const char *link_uri,
|
|
|
|
|
struct taginfo *tag, int attrind, struct map_context *ctx)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2001-11-24 22:10:34 -05:00
|
|
|
|
int link_has_scheme = url_has_scheme (link_uri);
|
|
|
|
|
struct urlpos *newel;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
const char *base = ctx->base ? ctx->base : ctx->parent_base;
|
2001-11-24 22:10:34 -05:00
|
|
|
|
struct url *url;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
|
|
|
|
if (!base)
|
|
|
|
|
{
|
2001-11-24 22:10:34 -05:00
|
|
|
|
DEBUGP (("%s: no base, merge will use \"%s\".\n",
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->document_file, link_uri));
|
2001-11-24 22:10:34 -05:00
|
|
|
|
|
|
|
|
|
if (!link_has_scheme)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Base URL is unavailable, and the link does not have a
|
|
|
|
|
location attached to it -- we have to give up. Since
|
|
|
|
|
this can only happen when using `--force-html -i', print
|
|
|
|
|
a warning. */
|
|
|
|
|
logprintf (LOG_NOTQUIET,
|
2001-12-12 13:32:17 -05:00
|
|
|
|
_("%s: Cannot resolve incomplete link %s.\n"),
|
2001-12-12 12:01:26 -05:00
|
|
|
|
ctx->document_file, link_uri);
|
2001-11-25 13:40:55 -05:00
|
|
|
|
return NULL;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
2001-11-24 22:10:34 -05:00
|
|
|
|
|
|
|
|
|
url = url_parse (link_uri, NULL);
|
|
|
|
|
if (!url)
|
|
|
|
|
{
|
|
|
|
|
DEBUGP (("%s: link \"%s\" doesn't parse.\n",
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->document_file, link_uri));
|
2001-11-25 13:40:55 -05:00
|
|
|
|
return NULL;
|
2001-11-24 22:10:34 -05:00
|
|
|
|
}
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
|
|
|
|
else
|
2001-11-24 22:10:34 -05:00
|
|
|
|
{
|
|
|
|
|
/* Merge BASE with LINK_URI, but also make sure the result is
|
|
|
|
|
canonicalized, i.e. that "../" have been resolved.
|
|
|
|
|
(parse_url will do that for us.) */
|
|
|
|
|
|
|
|
|
|
char *complete_uri = uri_merge (base, link_uri);
|
|
|
|
|
|
|
|
|
|
DEBUGP (("%s: merge(\"%s\", \"%s\") -> %s\n",
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->document_file, base, link_uri, complete_uri));
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-11-24 22:10:34 -05:00
|
|
|
|
url = url_parse (complete_uri, NULL);
|
|
|
|
|
if (!url)
|
|
|
|
|
{
|
|
|
|
|
DEBUGP (("%s: merged link \"%s\" doesn't parse.\n",
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->document_file, complete_uri));
|
2001-11-24 22:10:34 -05:00
|
|
|
|
xfree (complete_uri);
|
2001-11-25 13:40:55 -05:00
|
|
|
|
return NULL;
|
2001-11-24 22:10:34 -05:00
|
|
|
|
}
|
|
|
|
|
xfree (complete_uri);
|
|
|
|
|
}
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 12:01:26 -05:00
|
|
|
|
DEBUGP (("appending \"%s\" to urlpos.\n", url->url));
|
|
|
|
|
|
2003-10-31 09:55:50 -05:00
|
|
|
|
newel = xnew0 (struct urlpos);
|
2001-11-24 22:10:34 -05:00
|
|
|
|
newel->url = url;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
newel->pos = tag->attrs[attrind].value_raw_beginning - ctx->text;
|
|
|
|
|
newel->size = tag->attrs[attrind].value_raw_size;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-11-18 19:12:05 -05:00
|
|
|
|
/* A URL is relative if the host is not named, and the name does not
|
|
|
|
|
start with `/'. */
|
2001-11-24 22:10:34 -05:00
|
|
|
|
if (!link_has_scheme && *link_uri != '/')
|
2000-11-20 21:06:36 -05:00
|
|
|
|
newel->link_relative_p = 1;
|
2001-11-24 22:10:34 -05:00
|
|
|
|
else if (link_has_scheme)
|
2000-11-20 21:06:36 -05:00
|
|
|
|
newel->link_complete_p = 1;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (ctx->tail)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->tail->next = newel;
|
|
|
|
|
ctx->tail = newel;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
|
|
|
|
else
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx->tail = ctx->head = newel;
|
2001-11-25 13:40:55 -05:00
|
|
|
|
|
|
|
|
|
return newel;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
/* All the tag_* functions are called from collect_tags_mapper, as
|
|
|
|
|
specified by KNOWN_TAGS. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Default tag handler: collect URLs from attributes specified for
|
|
|
|
|
this tag by tag_url_attributes. */
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
|
|
|
|
static void
|
2001-12-12 10:43:01 -05:00
|
|
|
|
tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2003-10-09 11:01:58 -04:00
|
|
|
|
int i, attrind;
|
|
|
|
|
int first = -1;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
for (i = 0; i < countof (tag_url_attributes); i++)
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (tag_url_attributes[i].tagid == tagid)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2001-12-12 10:43:01 -05:00
|
|
|
|
/* We've found the index of tag_url_attributes where the
|
2001-12-12 12:01:26 -05:00
|
|
|
|
attributes of our tag begin. */
|
2001-11-16 14:44:42 -05:00
|
|
|
|
first = i;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
break;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
2001-12-12 10:43:01 -05:00
|
|
|
|
assert (first != -1);
|
|
|
|
|
|
|
|
|
|
/* Loop over the "interesting" attributes of this tag. In this
|
|
|
|
|
example, it will loop over "src" and "lowsrc".
|
|
|
|
|
|
|
|
|
|
<img src="foo.png" lowsrc="bar.png">
|
|
|
|
|
|
|
|
|
|
This has to be done in the outer loop so that the attributes are
|
|
|
|
|
processed in the same order in which they appear in the page.
|
|
|
|
|
This is required when converting links. */
|
|
|
|
|
|
|
|
|
|
for (attrind = 0; attrind < tag->nattrs; attrind++)
|
|
|
|
|
{
|
|
|
|
|
/* Find whether TAG/ATTRIND is a combination that contains a
|
|
|
|
|
URL. */
|
2001-12-12 12:01:26 -05:00
|
|
|
|
char *link = tag->attrs[attrind].value;
|
2003-10-09 11:01:58 -04:00
|
|
|
|
const int size = countof (tag_url_attributes);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
/* If you're cringing at the inefficiency of the nested loops,
|
2003-10-09 11:01:58 -04:00
|
|
|
|
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. */
|
2001-12-12 10:43:01 -05:00
|
|
|
|
for (i = first; i < size && tag_url_attributes[i].tagid == tagid; i++)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (0 == strcasecmp (tag->attrs[attrind].name,
|
|
|
|
|
tag_url_attributes[i].attr_name))
|
|
|
|
|
{
|
2003-10-13 10:32:25 -04:00
|
|
|
|
struct urlpos *up = append_url (link, tag, attrind, ctx);
|
2003-10-10 10:25:10 -04:00
|
|
|
|
if (up)
|
|
|
|
|
{
|
|
|
|
|
int flags = tag_url_attributes[i].flags;
|
|
|
|
|
if (flags & ATTR_INLINE)
|
|
|
|
|
up->link_inline_p = 1;
|
|
|
|
|
if (flags & ATTR_HTML)
|
|
|
|
|
up->link_expect_html = 1;
|
|
|
|
|
}
|
2001-12-12 10:43:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2001-11-25 13:40:55 -05:00
|
|
|
|
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Handle the BASE tag, for <base href=...>. */
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
static void
|
|
|
|
|
tag_handle_base (int tagid, struct taginfo *tag, struct map_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
struct urlpos *base_urlpos;
|
|
|
|
|
int attrind;
|
|
|
|
|
char *newbase = find_attr (tag, "href", &attrind);
|
|
|
|
|
if (!newbase)
|
|
|
|
|
return;
|
|
|
|
|
|
2003-10-13 10:32:25 -04:00
|
|
|
|
base_urlpos = append_url (newbase, tag, attrind, ctx);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (!base_urlpos)
|
|
|
|
|
return;
|
|
|
|
|
base_urlpos->ignore_when_downloading = 1;
|
|
|
|
|
base_urlpos->link_base_p = 1;
|
|
|
|
|
|
|
|
|
|
if (ctx->base)
|
|
|
|
|
xfree (ctx->base);
|
|
|
|
|
if (ctx->parent_base)
|
|
|
|
|
ctx->base = uri_merge (ctx->parent_base, newbase);
|
|
|
|
|
else
|
|
|
|
|
ctx->base = xstrdup (newbase);
|
|
|
|
|
}
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2002-04-11 13:51:45 -04:00
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2003-10-13 10:32:25 -04:00
|
|
|
|
struct urlpos *up = append_url (action, tag, attrind, ctx);
|
2003-10-10 10:25:10 -04:00
|
|
|
|
if (up)
|
|
|
|
|
up->ignore_when_downloading = 1;
|
2002-04-11 13:51:45 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Handle the LINK tag. It requires special handling because how its
|
|
|
|
|
links will be followed in -p mode depends on the REL attribute. */
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
static void
|
|
|
|
|
tag_handle_link (int tagid, struct taginfo *tag, struct map_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
int attrind;
|
|
|
|
|
char *href = find_attr (tag, "href", &attrind);
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-18 20:15:34 -05:00
|
|
|
|
/* 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="...">
|
|
|
|
|
*/
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (href)
|
|
|
|
|
{
|
2003-10-13 10:32:25 -04:00
|
|
|
|
struct urlpos *up = append_url (href, tag, attrind, ctx);
|
2003-10-10 10:25:10 -04:00
|
|
|
|
if (up)
|
|
|
|
|
{
|
|
|
|
|
char *rel = find_attr (tag, "rel", NULL);
|
|
|
|
|
if (rel
|
|
|
|
|
&& (0 == strcasecmp (rel, "stylesheet")
|
|
|
|
|
|| 0 == strcasecmp (rel, "shortcut icon")))
|
|
|
|
|
up->link_inline_p = 1;
|
2005-05-27 09:53:47 -04:00
|
|
|
|
else
|
|
|
|
|
/* The external ones usually point to HTML pages, such as
|
|
|
|
|
<link rel="next" href="..."> */
|
|
|
|
|
up->link_expect_html = 1;
|
2003-10-10 10:25:10 -04:00
|
|
|
|
}
|
2001-12-12 10:43:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Handle the META tag. This requires special handling because of the
|
|
|
|
|
refresh feature and because of robot exclusion. */
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
char *name = find_attr (tag, "name", NULL);
|
|
|
|
|
char *http_equiv = find_attr (tag, "http-equiv", NULL);
|
|
|
|
|
|
|
|
|
|
if (http_equiv && 0 == strcasecmp (http_equiv, "refresh"))
|
|
|
|
|
{
|
2001-12-12 12:01:26 -05:00
|
|
|
|
/* Some pages use a META tag to specify that the page be
|
|
|
|
|
refreshed by a new page after a given number of seconds. The
|
|
|
|
|
general format for this is:
|
|
|
|
|
|
|
|
|
|
<meta http-equiv=Refresh content="NUMBER; URL=index2.html">
|
|
|
|
|
|
|
|
|
|
So we just need to skip past the "NUMBER; URL=" garbage to
|
|
|
|
|
get to the URL. */
|
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
struct urlpos *entry;
|
|
|
|
|
int attrind;
|
|
|
|
|
int timeout = 0;
|
2002-01-31 22:34:31 -05:00
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
|
|
char *refresh = find_attr (tag, "content", &attrind);
|
|
|
|
|
if (!refresh)
|
|
|
|
|
return;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
for (p = refresh; ISDIGIT (*p); p++)
|
|
|
|
|
timeout = 10 * timeout + *p - '0';
|
|
|
|
|
if (*p++ != ';')
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
while (ISSPACE (*p))
|
|
|
|
|
++p;
|
|
|
|
|
if (!( TOUPPER (*p) == 'U'
|
|
|
|
|
&& TOUPPER (*(p + 1)) == 'R'
|
|
|
|
|
&& TOUPPER (*(p + 2)) == 'L'
|
|
|
|
|
&& *(p + 3) == '='))
|
|
|
|
|
return;
|
|
|
|
|
p += 4;
|
|
|
|
|
while (ISSPACE (*p))
|
|
|
|
|
++p;
|
|
|
|
|
|
2003-10-13 10:32:25 -04:00
|
|
|
|
entry = append_url (p, tag, attrind, ctx);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
if (entry)
|
|
|
|
|
{
|
|
|
|
|
entry->link_refresh_p = 1;
|
|
|
|
|
entry->refresh_timeout = timeout;
|
2003-10-13 10:32:25 -04:00
|
|
|
|
entry->link_expect_html = 1;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (name && 0 == strcasecmp (name, "robots"))
|
|
|
|
|
{
|
|
|
|
|
/* Handle stuff like:
|
|
|
|
|
<meta name="robots" content="index,nofollow"> */
|
|
|
|
|
char *content = find_attr (tag, "content", NULL);
|
|
|
|
|
if (!content)
|
|
|
|
|
return;
|
|
|
|
|
if (!strcasecmp (content, "none"))
|
|
|
|
|
ctx->nofollow = 1;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while (*content)
|
|
|
|
|
{
|
|
|
|
|
/* Find the next occurrence of ',' or the end of
|
|
|
|
|
the string. */
|
|
|
|
|
char *end = strchr (content, ',');
|
|
|
|
|
if (end)
|
|
|
|
|
++end;
|
|
|
|
|
else
|
|
|
|
|
end = content + strlen (content);
|
|
|
|
|
if (!strncasecmp (content, "nofollow", end - content))
|
|
|
|
|
ctx->nofollow = 1;
|
|
|
|
|
content = end;
|
|
|
|
|
}
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* Dispatch the tag handler appropriate for the tag we're mapping
|
|
|
|
|
over. See known_tags[] for definition of tag handlers. */
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
collect_tags_mapper (struct taginfo *tag, void *arg)
|
|
|
|
|
{
|
|
|
|
|
struct map_context *ctx = (struct map_context *)arg;
|
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
/* 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);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
|
2003-10-09 11:01:58 -04:00
|
|
|
|
t->handler (t->tagid, tag, ctx);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
}
|
|
|
|
|
|
2001-04-24 20:50:22 -04:00
|
|
|
|
/* Analyze HTML tags FILE and construct a list of URLs referenced from
|
2001-11-25 13:40:55 -05:00
|
|
|
|
it. It merges relative links in FILE with URL. It is aware of
|
2001-11-30 16:17:53 -05:00
|
|
|
|
<base href=...> and does the right thing. */
|
2003-09-21 18:47:14 -04:00
|
|
|
|
|
2001-11-24 22:10:34 -05:00
|
|
|
|
struct urlpos *
|
2001-11-30 16:17:53 -05:00
|
|
|
|
get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
|
2000-11-19 15:50:10 -05:00
|
|
|
|
{
|
|
|
|
|
struct file_memory *fm;
|
2001-12-12 10:43:01 -05:00
|
|
|
|
struct map_context ctx;
|
2003-10-08 12:17:33 -04:00
|
|
|
|
int flags;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
|
|
|
|
/* Load the file. */
|
|
|
|
|
fm = read_file (file);
|
|
|
|
|
if (!fm)
|
|
|
|
|
{
|
|
|
|
|
logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2005-02-23 17:21:04 -05:00
|
|
|
|
DEBUGP (("Loaded %s (size %s).\n", file, number_to_static_string (fm->length)));
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
ctx.text = fm->content;
|
|
|
|
|
ctx.head = ctx.tail = NULL;
|
|
|
|
|
ctx.base = NULL;
|
|
|
|
|
ctx.parent_base = url ? url : opt.base_href;
|
|
|
|
|
ctx.document_file = file;
|
|
|
|
|
ctx.nofollow = 0;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
|
|
|
|
if (!interesting_tags)
|
|
|
|
|
init_interesting ();
|
|
|
|
|
|
2003-10-08 12:17:33 -04:00
|
|
|
|
/* Specify MHT_TRIM_VALUES because of buggy HTML generators that
|
2003-11-26 11:37:04 -05:00
|
|
|
|
generate <a href=" foo"> instead of <a href="foo"> (browsers
|
|
|
|
|
ignore spaces as well.) If you really mean space, use &32; or
|
|
|
|
|
%20. MHT_TRIM_VALUES also causes squashing of embedded newlines,
|
|
|
|
|
e.g. in <img src="foo.[newline]html">. Such newlines are also
|
|
|
|
|
ignored by IE and Mozilla and are presumably introduced by
|
|
|
|
|
writing HTML with editors that force word wrap. */
|
2003-10-08 12:17:33 -04:00
|
|
|
|
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);
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2001-12-12 10:43:01 -05:00
|
|
|
|
DEBUGP (("no-follow in %s: %d\n", file, ctx.nofollow));
|
2000-11-19 15:50:10 -05:00
|
|
|
|
if (meta_disallow_follow)
|
2001-12-12 10:43:01 -05:00
|
|
|
|
*meta_disallow_follow = ctx.nofollow;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
|
2003-11-02 14:56:37 -05:00
|
|
|
|
xfree_null (ctx.base);
|
2000-11-19 15:50:10 -05:00
|
|
|
|
read_file_free (fm);
|
2001-12-12 10:43:01 -05:00
|
|
|
|
return ctx.head;
|
2000-11-19 15:50:10 -05:00
|
|
|
|
}
|
2000-11-22 17:15:45 -05:00
|
|
|
|
|
2003-09-21 18:47:14 -04:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2005-02-23 17:21:04 -05:00
|
|
|
|
DEBUGP (("Loaded %s (size %s).\n", file, number_to_static_string (fm->length)));
|
2003-09-21 18:47:14 -04:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2005-06-15 16:26:37 -04:00
|
|
|
|
logprintf (LOG_NOTQUIET, _("%s: Invalid URL %s: %s\n"),
|
2003-09-21 18:47:14 -04:00
|
|
|
|
file, url_text, url_error (up_error_code));
|
|
|
|
|
xfree (url_text);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
xfree (url_text);
|
|
|
|
|
|
2003-10-31 09:55:50 -05:00
|
|
|
|
entry = xnew0 (struct urlpos);
|
2003-09-21 18:47:14 -04:00
|
|
|
|
entry->next = NULL;
|
|
|
|
|
entry->url = url;
|
|
|
|
|
|
|
|
|
|
if (!head)
|
|
|
|
|
head = entry;
|
|
|
|
|
else
|
|
|
|
|
tail->next = entry;
|
|
|
|
|
tail = entry;
|
|
|
|
|
}
|
|
|
|
|
read_file_free (fm);
|
|
|
|
|
return head;
|
|
|
|
|
}
|
|
|
|
|
|
2000-11-22 17:15:45 -05:00
|
|
|
|
void
|
|
|
|
|
cleanup_html_url (void)
|
|
|
|
|
{
|
2003-11-02 16:12:49 -05:00
|
|
|
|
/* Destroy the hash tables. The hash table keys and values are not
|
|
|
|
|
allocated by this code, so we don't need to free them here. */
|
|
|
|
|
if (interesting_tags)
|
|
|
|
|
hash_table_destroy (interesting_tags);
|
|
|
|
|
if (interesting_attributes)
|
|
|
|
|
hash_table_destroy (interesting_attributes);
|
2000-11-22 17:15:45 -05:00
|
|
|
|
}
|