summaryrefslogtreecommitdiffstats
path: root/WWW/Library/Implementation/HTAnchor.c
diff options
context:
space:
mode:
Diffstat (limited to 'WWW/Library/Implementation/HTAnchor.c')
-rw-r--r--WWW/Library/Implementation/HTAnchor.c1460
1 files changed, 1460 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTAnchor.c b/WWW/Library/Implementation/HTAnchor.c
new file mode 100644
index 0000000..b5ca251
--- /dev/null
+++ b/WWW/Library/Implementation/HTAnchor.c
@@ -0,0 +1,1460 @@
+/*
+ * $LynxId: HTAnchor.c,v 1.82 2020/01/21 21:58:52 tom Exp $
+ *
+ * Hypertext "Anchor" Object HTAnchor.c
+ * ==========================
+ *
+ * An anchor represents a region of a hypertext document which is linked to
+ * another anchor in the same or a different document.
+ *
+ * History
+ *
+ * Nov 1990 Written in Objective-C for the NeXT browser (TBL)
+ * 24-Oct-1991 (JFG), written in C, browser-independent
+ * 21-Nov-1991 (JFG), first complete version
+ *
+ * (c) Copyright CERN 1991 - See Copyright.html
+ */
+
+#define HASH_SIZE 997 /* Arbitrary prime. Memory/speed tradeoff */
+
+#include <HTUtils.h>
+#include <HTAnchor.h>
+#include <HTParse.h>
+#include <HTString.h>
+#include <UCAux.h>
+#include <UCMap.h>
+
+#include <GridText.h>
+#include <LYUtils.h>
+#include <LYCharSets.h>
+#include <LYUtils.h>
+#include <LYLeaks.h>
+
+#define HASH_OF(h, v) ((HASH_TYPE)((h) * 3 + UCH(v)) % HASH_SIZE)
+
+static HASH_TYPE anchor_hash(const char *cp_address)
+{
+ HASH_TYPE hash;
+ const char *p;
+
+ for (p = cp_address, hash = 0; *p; p++)
+ hash = HASH_OF(hash, *p);
+
+ return (hash);
+}
+
+typedef struct _HyperDoc Hyperdoc;
+
+#ifdef VMS
+struct _HyperDoc {
+ int junk; /* VMS cannot handle pointers to undefined structs */
+};
+#endif /* VMS */
+
+/* Table of lists of all parents */
+static HTList adult_table[HASH_SIZE] =
+{
+ {NULL, NULL}};
+
+/* Creation Methods
+ * ================
+ *
+ * Do not use "new" by itself outside this module. In order to enforce
+ * consistency, we insist that you furnish more information about the
+ * anchor you are creating : use newWithParent or newWithAddress.
+ */
+static HTParentAnchor0 *HTParentAnchor0_new(const char *address,
+ unsigned hash)
+{
+ HTParentAnchor0 *newAnchor = typecalloc(HTParentAnchor0);
+
+ if (newAnchor == NULL)
+ outofmem(__FILE__, "HTParentAnchor0_new");
+
+ newAnchor->parent = newAnchor; /* self */
+ StrAllocCopy(newAnchor->address, address);
+ newAnchor->adult_hash = (HASH_TYPE) hash;
+
+ return (newAnchor);
+}
+
+static HTParentAnchor *HTParentAnchor_new(HTParentAnchor0 *parent)
+{
+ HTParentAnchor *newAnchor = typecalloc(HTParentAnchor);
+
+ if (newAnchor == NULL)
+ outofmem(__FILE__, "HTParentAnchor_new");
+
+ newAnchor->parent = parent; /* cross reference */
+ parent->info = newAnchor; /* cross reference */
+ newAnchor->address = parent->address; /* copy pointer */
+
+ newAnchor->isISMAPScript = FALSE; /* Lynx appends ?0,0 if TRUE. - FM */
+ newAnchor->isHEAD = FALSE; /* HEAD request if TRUE. - FM */
+ newAnchor->safe = FALSE; /* Safe. - FM */
+ newAnchor->no_cache = FALSE; /* no-cache? - FM */
+ newAnchor->inBASE = FALSE; /* duplicated from HTML.c/h */
+ newAnchor->content_length = 0; /* Content-Length. - FM */
+ return (newAnchor);
+}
+
+static HTChildAnchor *HTChildAnchor_new(HTParentAnchor0 *parent)
+{
+ HTChildAnchor *p = typecalloc(HTChildAnchor);
+
+ if (p == NULL)
+ outofmem(__FILE__, "HTChildAnchor_new");
+
+ p->parent = parent; /* parent reference */
+ return p;
+}
+
+static HTChildAnchor *HText_pool_ChildAnchor_new(HTParentAnchor *parent)
+{
+ HTChildAnchor *p = (HTChildAnchor *) HText_pool_calloc((HText *) (parent->document),
+ (unsigned) sizeof(HTChildAnchor));
+
+ if (p == NULL)
+ outofmem(__FILE__, "HText_pool_ChildAnchor_new");
+
+ p->parent = parent->parent; /* parent reference */
+ return p;
+}
+
+#ifdef CASE_INSENSITIVE_ANCHORS
+/* Case insensitive string comparison */
+#define HT_EQUIV(a,b) (TOUPPER(a) == TOUPPER(b))
+#else
+/* Case sensitive string comparison */
+#define HT_EQUIV(a,b) ((a) == (b))
+#endif
+
+/* Null-terminated string comparison
+ * ---------------------------------
+ * On entry,
+ * s Points to one string, null terminated
+ * t points to the other.
+ * On exit,
+ * returns YES if the strings are equivalent
+ * NO if they differ.
+ */
+static BOOL HTSEquivalent(const char *s,
+ const char *t)
+{
+ if (s && t) { /* Make sure they point to something */
+ for (; *s && *t; s++, t++) {
+ if (!HT_EQUIV(*s, *t)) {
+ return (NO);
+ }
+ }
+ return (BOOL) (HT_EQUIV(*s, *t));
+ } else {
+ return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */
+ }
+}
+
+/* Binary string comparison
+ * ------------------------
+ * On entry,
+ * s Points to one bstring
+ * t points to the other.
+ * On exit,
+ * returns YES if the strings are equivalent
+ * NO if they differ.
+ */
+static BOOL HTBEquivalent(const bstring *s,
+ const bstring *t)
+{
+ if (s && t && BStrLen(s) == BStrLen(t)) {
+ int j;
+ int len = BStrLen(s);
+
+ for (j = 0; j < len; ++j) {
+ if (!HT_EQUIV(BStrData(s)[j], BStrData(t)[j])) {
+ return (NO);
+ }
+ }
+ return (YES);
+ } else {
+ return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */
+ }
+}
+
+/*
+ * Three-way compare function
+ */
+static int compare_anchors(void *l,
+ void *r)
+{
+ const char *a = ((HTChildAnchor *) l)->tag;
+ const char *b = ((HTChildAnchor *) r)->tag;
+
+ /* both tags are not NULL */
+
+#ifdef CASE_INSENSITIVE_ANCHORS
+ return strcasecomp(a, b); /* Case insensitive */
+#else
+ return strcmp(a, b); /* Case sensitive - FM */
+#endif /* CASE_INSENSITIVE_ANCHORS */
+}
+
+/* Create new or find old sub-anchor
+ * ---------------------------------
+ *
+ * This one is for a named child.
+ * The parent anchor must already exist.
+ */
+static HTChildAnchor *HTAnchor_findNamedChild(HTParentAnchor0 *parent,
+ const char *tag)
+{
+ HTChildAnchor *child;
+
+ if (parent && tag && *tag) { /* TBL */
+ if (parent->children) {
+ /*
+ * Parent has children. Search them.
+ */
+ HTChildAnchor sample;
+
+ sample.tag = DeConst(tag); /* for compare_anchors() only */
+
+ child = (HTChildAnchor *) HTBTree_search(parent->children, &sample);
+ if (child != NULL) {
+ CTRACE((tfp,
+ "Child anchor %p of parent %p with name `%s' already exists.\n",
+ (void *) child, (void *) parent, tag));
+ return (child);
+ }
+ } else { /* parent doesn't have any children yet : create family */
+ parent->children = HTBTree_new(compare_anchors);
+ }
+
+ child = HTChildAnchor_new(parent);
+ CTRACE((tfp, "HTAnchor: New Anchor %p named `%s' is child of %p\n",
+ (void *) child,
+ NonNull(tag),
+ (void *) child->parent));
+
+ StrAllocCopy(child->tag, tag); /* should be set before HTBTree_add */
+ HTBTree_add(parent->children, child);
+ return (child);
+
+ } else {
+ CTRACE((tfp, "HTAnchor_findNamedChild called with NULL parent.\n"));
+ return (NULL);
+ }
+
+}
+
+/*
+ * This one is for a new unnamed child being edited into an existing
+ * document. The parent anchor and the document must already exist.
+ * (Just add new unnamed child).
+ */
+static HTChildAnchor *HTAnchor_addChild(HTParentAnchor *parent)
+{
+ HTChildAnchor *child;
+
+ if (!parent) {
+ CTRACE((tfp, "HTAnchor_addChild called with NULL parent.\n"));
+ return (NULL);
+ }
+
+ child = HText_pool_ChildAnchor_new(parent);
+ CTRACE((tfp, "HTAnchor: New unnamed Anchor %p is child of %p\n",
+ (void *) child,
+ (void *) child->parent));
+
+ child->tag = 0;
+ HTList_linkObject(&parent->children_notag, child, &child->_add_children_notag);
+
+ return (child);
+}
+
+static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc);
+
+static BOOL HTAnchor_link(HTChildAnchor *child,
+ HTAnchor * destination,
+ HTLinkType *type);
+
+/* Create or find a child anchor with a possible link
+ * --------------------------------------------------
+ *
+ * Create new anchor with a given parent and possibly
+ * a name, and possibly a link to a _relatively_ named anchor.
+ * (Code originally in ParseHTML.h)
+ */
+HTChildAnchor *HTAnchor_findChildAndLink(HTParentAnchor *parent, /* May not be 0 */
+ const char *tag, /* May be "" or 0 */
+ const char *href, /* May be "" or 0 */
+ HTLinkType *ltype) /* May be 0 */
+{
+ HTChildAnchor *child;
+
+ CTRACE((tfp, "Entered HTAnchor_findChildAndLink: tag=`%s',%s href=`%s'\n",
+ NonNull(tag),
+ (ltype == HTInternalLink) ? " (internal link)" : "",
+ NonNull(href)));
+
+ if (parent == 0) {
+ child = 0;
+ } else {
+ if (non_empty(tag)) {
+ child = HTAnchor_findNamedChild(parent->parent, tag);
+ } else {
+ child = HTAnchor_addChild(parent);
+ }
+
+ if (non_empty(href)) {
+ const char *fragment = NULL;
+ HTParentAnchor0 *dest;
+
+ if (ltype == HTInternalLink && *href == '#') {
+ dest = parent->parent;
+ } else {
+ const char *relative_to = ((parent->inBASE && *href != '#')
+ ? parent->content_base
+ : parent->address);
+ DocAddress parsed_doc;
+
+ parsed_doc.address = HTParse(href, relative_to,
+ PARSE_ALL_WITHOUT_ANCHOR);
+
+ parsed_doc.post_data = NULL;
+ parsed_doc.post_content_type = NULL;
+ if (ltype && parent->post_data && ltype == HTInternalLink) {
+ /* for internal links, find a destination with the same
+ post data if the source of the link has post data. - kw
+ Example: LYNXIMGMAP: */
+ parsed_doc.post_data = parent->post_data;
+ parsed_doc.post_content_type = parent->post_content_type;
+ }
+ parsed_doc.bookmark = NULL;
+ parsed_doc.isHEAD = FALSE;
+ parsed_doc.safe = FALSE;
+
+ dest = HTAnchor_findAddress_in_adult_table(&parsed_doc);
+ FREE(parsed_doc.address);
+ }
+
+ /*
+ * [from HTAnchor_findAddress()]
+ * If the address represents a sub-anchor, we load its parent (above),
+ * then we create a named child anchor within that parent.
+ */
+ fragment = (*href == '#') ? href + 1 : HTParseAnchor(href);
+
+ if (*fragment)
+ dest = (HTParentAnchor0 *) HTAnchor_findNamedChild(dest, fragment);
+
+ if (tag && *tag) {
+ if (child->dest) { /* DUPLICATE_ANCHOR_NAME_WORKAROUND - kw */
+ CTRACE((tfp,
+ "*** Duplicate ChildAnchor %p named `%s'",
+ (void *) child, tag));
+ if ((HTAnchor *) dest != child->dest || ltype != child->type) {
+ CTRACE((tfp,
+ ", different dest %p or type, creating unnamed child\n",
+ (void *) child->dest));
+ child = HTAnchor_addChild(parent);
+ }
+ }
+ }
+ HTAnchor_link(child, (HTAnchor *) dest, ltype);
+ }
+ }
+ return child;
+}
+
+/* Create new or find old parent anchor
+ * ------------------------------------
+ *
+ * Me one is for a reference which is found in a document, and might
+ * not be already loaded.
+ * Note: You are not guaranteed a new anchor -- you might get an old one,
+ * like with fonts.
+ */
+HTParentAnchor *HTAnchor_findAddress(const DocAddress *newdoc)
+{
+ /* Anchor tag specified ? */
+ const char *tag = HTParseAnchor(newdoc->address);
+
+ CTRACE((tfp, "Entered HTAnchor_findAddress\n"));
+
+ /*
+ * If the address represents a sub-anchor, we load its parent, then we
+ * create a named child anchor within that parent.
+ */
+ if (*tag) {
+ DocAddress parsed_doc;
+ HTParentAnchor0 *foundParent;
+
+ parsed_doc.address = HTParse(newdoc->address, "",
+ PARSE_ALL_WITHOUT_ANCHOR);
+ parsed_doc.post_data = newdoc->post_data;
+ parsed_doc.post_content_type = newdoc->post_content_type;
+ parsed_doc.bookmark = newdoc->bookmark;
+ parsed_doc.isHEAD = newdoc->isHEAD;
+ parsed_doc.safe = newdoc->safe;
+
+ foundParent = HTAnchor_findAddress_in_adult_table(&parsed_doc);
+ (void) HTAnchor_findNamedChild(foundParent, tag);
+ FREE(parsed_doc.address);
+ return HTAnchor_parent((HTAnchor *) foundParent);
+ }
+ return HTAnchor_parent((HTAnchor *) HTAnchor_findAddress_in_adult_table(newdoc));
+}
+
+/* The address has no anchor tag, for sure.
+ */
+static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc)
+{
+ /*
+ * Check whether we have this node.
+ */
+ HASH_TYPE hash;
+ HTList *adults;
+ HTList *grownups;
+ HTParentAnchor0 *foundAnchor;
+ BOOL need_extra_info = (BOOL) (newdoc->post_data ||
+ newdoc->post_content_type ||
+ newdoc->bookmark ||
+ newdoc->isHEAD ||
+ newdoc->safe);
+
+ /*
+ * We need not free adult_table[] atexit - it should be perfectly empty
+ * after free'ing all HText's. (There is an error if it is not empty at
+ * exit). -LP
+ */
+
+ /*
+ * Select list from hash table,
+ */
+ hash = anchor_hash(newdoc->address);
+ adults = &(adult_table[hash]);
+
+ /*
+ * Search list for anchor.
+ */
+ grownups = adults;
+ while (NULL != (foundAnchor =
+ (HTParentAnchor0 *) HTList_nextObject(grownups))) {
+ if (HTSEquivalent(foundAnchor->address, newdoc->address) &&
+
+ ((!foundAnchor->info && !need_extra_info) ||
+ (foundAnchor->info &&
+ HTBEquivalent(foundAnchor->info->post_data, newdoc->post_data) &&
+ foundAnchor->info->isHEAD == newdoc->isHEAD))) {
+ CTRACE((tfp, "Anchor %p with address `%s' already exists.\n",
+ (void *) foundAnchor, newdoc->address));
+ return foundAnchor;
+ }
+ }
+
+ /*
+ * Node not found: create new anchor.
+ */
+ foundAnchor = HTParentAnchor0_new(newdoc->address, hash);
+ CTRACE((tfp, "New anchor %p has hash %d and address `%s'\n",
+ (void *) foundAnchor, hash, newdoc->address));
+
+ if (need_extra_info) {
+ /* rare case, create a big structure */
+ HTParentAnchor *p = HTParentAnchor_new(foundAnchor);
+
+ if (newdoc->post_data)
+ BStrCopy(p->post_data, newdoc->post_data);
+ if (newdoc->post_content_type)
+ StrAllocCopy(p->post_content_type,
+ newdoc->post_content_type);
+ if (newdoc->bookmark)
+ StrAllocCopy(p->bookmark, newdoc->bookmark);
+ p->isHEAD = newdoc->isHEAD;
+ p->safe = newdoc->safe;
+ }
+ HTList_linkObject(adults, foundAnchor, &foundAnchor->_add_adult);
+
+ return foundAnchor;
+}
+
+/* Create new or find old named anchor - simple form
+ * -------------------------------------------------
+ *
+ * Like HTAnchor_findAddress, but simpler to use for simple cases.
+ * No post data etc. can be supplied. - kw
+ */
+HTParentAnchor *HTAnchor_findSimpleAddress(const char *url)
+{
+ DocAddress urldoc;
+
+ urldoc.address = DeConst(url); /* ignore warning, it IS treated like const - kw */
+ urldoc.post_data = NULL;
+ urldoc.post_content_type = NULL;
+ urldoc.bookmark = NULL;
+ urldoc.isHEAD = FALSE;
+ urldoc.safe = FALSE;
+ return HTAnchor_findAddress(&urldoc);
+}
+
+/* Link me Anchor to another given one
+ * -------------------------------------
+ */
+static BOOL HTAnchor_link(HTChildAnchor *child,
+ HTAnchor * destination,
+ HTLinkType *type)
+{
+ if (!(child && destination))
+ return (NO); /* Can't link to/from non-existing anchor */
+
+ CTRACE((tfp, "Linking child %p to anchor %p\n", (void *) child, (void *) destination));
+ if (child->dest) {
+ CTRACE((tfp, "*** child anchor already has destination, exiting!\n"));
+ return (NO);
+ }
+
+ child->dest = destination;
+ child->type = type;
+
+ if (child->parent != destination->parent)
+ /* link only foreign children */
+ HTList_linkObject(&destination->parent->sources, child, &child->_add_sources);
+
+ return (YES); /* Success */
+}
+
+/* Delete an anchor and possibly related things (auto garbage collection)
+ * --------------------------------------------
+ *
+ * The anchor is only deleted if the corresponding document is not loaded.
+ * All outgoing links from children are deleted, and children are
+ * removed from the sources lists of theirs targets.
+ * We also try to delete the targets whose documents are not loaded.
+ * If this anchor's sources list is empty, we delete it and its children.
+ */
+
+/*
+ * Recursively try to delete destination anchor of this child.
+ * In any event, this will tell destination anchor that we
+ * no longer consider it a destination.
+ */
+static void deleteLinks(HTChildAnchor *me)
+{
+ /*
+ * Unregister me with our destination anchor's parent.
+ */
+ if (me->dest) {
+ HTParentAnchor0 *parent = me->dest->parent;
+
+ /*
+ * Start. Set the dest pointer to zero.
+ */
+ me->dest = NULL;
+
+ /*
+ * Remove me from the parent's sources so that the parent knows one
+ * less anchor is its dest.
+ */
+ if ((me->parent != parent) && !HTList_isEmpty(&parent->sources)) {
+ /*
+ * Really should only need to deregister once.
+ */
+ HTList_unlinkObject(&parent->sources, (void *) me);
+ }
+
+ /*
+ * Recursive call. Test here to avoid calling overhead. Don't delete
+ * if document is loaded or being loaded.
+ */
+ if ((me->parent != parent) &&
+ parent != NULL &&
+ !parent->underway &&
+ (!parent->info || !parent->info->document)) {
+ HTAnchor_delete(parent);
+ }
+
+ /*
+ * At this point, we haven't a destination. Set it to be so. Leave
+ * the HTAtom pointed to by type up to other code to handle (reusable,
+ * near static).
+ */
+ me->type = NULL;
+ }
+}
+
+static void HTParentAnchor_free(HTParentAnchor *me);
+
+BOOL HTAnchor_delete(HTParentAnchor0 *me)
+{
+ /*
+ * Memory leaks fixed.
+ * 05-27-94 Lynx 2-3-1 Garrett Arch Blythe
+ */
+ HTBTElement *ele;
+ HTChildAnchor *child;
+
+ /*
+ * Do nothing if nothing to do.
+ */
+ if (!me) {
+ return (NO);
+ }
+
+ /*
+ * Don't delete if document is loaded or being loaded.
+ */
+ if (me->underway || (me->info && me->info->document)) {
+ return (NO);
+ }
+
+ /*
+ * Mark ourselves busy, so that recursive calls of this function on this
+ * HTParentAnchor0 will not free it from under our feet. - kw
+ */
+ me->underway = TRUE;
+
+ {
+ /*
+ * Delete all outgoing links from named children. Do not delete named
+ * children itself (may have incoming links).
+ */
+ if (me->children) {
+ ele = HTBTree_next(me->children, NULL);
+ while (ele != NULL) {
+ child = (HTChildAnchor *) HTBTree_object(ele);
+ if (child->dest)
+ deleteLinks(child);
+ ele = HTBTree_next(me->children, ele);
+ }
+ }
+ }
+ me->underway = FALSE;
+
+ /*
+ * There are still incoming links to this one (we are the
+ * destination of another anchor).
+ */
+ if (!HTList_isEmpty(&me->sources)) {
+ /*
+ * Can't delete parent, still have sources.
+ */
+ return (NO);
+ }
+
+ /*
+ * No more incoming and outgoing links : kill everything First, delete
+ * named children.
+ */
+ if (me->children) {
+ ele = HTBTree_next(me->children, NULL);
+ while (ele != NULL) {
+ child = (HTChildAnchor *) HTBTree_object(ele);
+ FREE(child->tag);
+ FREE(child);
+ ele = HTBTree_next(me->children, ele);
+ }
+ HTBTree_free(me->children);
+ }
+
+ /*
+ * Delete the ParentAnchor, if any. (Document was already deleted).
+ */
+ if (me->info) {
+ HTParentAnchor_free(me->info);
+ FREE(me->info);
+ }
+
+ /*
+ * Remove ourselves from the hash table's list.
+ */
+ HTList_unlinkObject(&(adult_table[me->adult_hash]), (void *) me);
+
+ /*
+ * Free the address.
+ */
+ FREE(me->address);
+
+ /*
+ * Finally, kill the parent anchor passed in.
+ */
+ FREE(me);
+
+ return (YES);
+}
+
+/*
+ * Unnamed children (children_notag) have no sense without HText - delete them
+ * and their links if we are about to free HText. Document currently exists.
+ * Called within HText_free().
+ */
+void HTAnchor_delete_links(HTParentAnchor *me)
+{
+ HTList *cur;
+ HTChildAnchor *child;
+
+ /*
+ * Do nothing if nothing to do.
+ */
+ if (!me || !me->document) {
+ return;
+ }
+
+ /*
+ * Mark ourselves busy, so that recursive calls on this HTParentAnchor0
+ * will not free it from under our feet. - kw
+ */
+ me->parent->underway = TRUE;
+
+ /*
+ * Delete all outgoing links from unnamed children.
+ */
+ if (!HTList_isEmpty(&me->children_notag)) {
+ cur = &me->children_notag;
+ while ((child =
+ (HTChildAnchor *) HTList_unlinkLastObject(cur)) != 0) {
+ deleteLinks(child);
+ /* child allocated in HText pool, HText_free() will free it later */
+ }
+ }
+ me->parent->underway = FALSE;
+}
+
+static void HTParentAnchor_free(HTParentAnchor *me)
+{
+ /*
+ * Delete the methods list.
+ */
+ if (me->methods) {
+ /*
+ * Leave what the methods point to up in memory for other code (near
+ * static stuff).
+ */
+ HTList_delete(me->methods);
+ me->methods = NULL;
+ }
+
+ /*
+ * Free up all allocated members.
+ */
+ FREE(me->charset);
+ FREE(me->isIndexAction);
+ FREE(me->isIndexPrompt);
+ FREE(me->title);
+ FREE(me->physical);
+ BStrFree(me->post_data);
+ FREE(me->post_content_type);
+ FREE(me->bookmark);
+ FREE(me->owner);
+ FREE(me->RevTitle);
+ FREE(me->citehost);
+#ifdef USE_SOURCE_CACHE
+ HTAnchor_clearSourceCache(me);
+#endif
+ if (me->FileCache) {
+ FILE *fd;
+
+ if ((fd = fopen(me->FileCache, "r")) != NULL) {
+ fclose(fd);
+ (void) remove(me->FileCache);
+ }
+ FREE(me->FileCache);
+ }
+ FREE(me->SugFname);
+ FREE(me->cache_control);
+ HTChunkClear(&(me->http_headers));
+ FREE(me->content_type_params);
+ FREE(me->content_type);
+ FREE(me->content_language);
+ FREE(me->content_encoding);
+ FREE(me->content_base);
+ FREE(me->content_disposition);
+ FREE(me->content_location);
+ FREE(me->content_md5);
+ FREE(me->message_id);
+ FREE(me->subject);
+ FREE(me->date);
+ FREE(me->expires);
+
+ FREE(me->last_modified);
+ FREE(me->ETag);
+ FREE(me->server);
+#ifdef USE_COLOR_STYLE
+ FREE(me->style);
+#endif
+
+ /*
+ * Original code wanted a way to clean out the HTFormat if no longer needed
+ * (ref count?). I'll leave it alone since those HTAtom objects are a
+ * little harder to know where they are being referenced all at one time.
+ * (near static)
+ */
+
+ FREE(me->UCStages);
+ ImageMapList_free(me->imaps);
+}
+
+#ifdef USE_SOURCE_CACHE
+void HTAnchor_clearSourceCache(HTParentAnchor *me)
+{
+ /*
+ * Clean up the source cache, if any.
+ */
+ if (me->source_cache_file) {
+ CTRACE((tfp, "SourceCache: Removing file %s\n",
+ me->source_cache_file));
+ (void) LYRemoveTemp(me->source_cache_file);
+ FREE(me->source_cache_file);
+ }
+ if (me->source_cache_chunk) {
+ CTRACE((tfp, "SourceCache: Removing memory chunk %p\n",
+ (void *) me->source_cache_chunk));
+ HTChunkFree(me->source_cache_chunk);
+ me->source_cache_chunk = NULL;
+ }
+}
+#endif /* USE_SOURCE_CACHE */
+
+/* Data access functions
+ * ---------------------
+ */
+HTParentAnchor *HTAnchor_parent(HTAnchor * me)
+{
+ if (!me)
+ return NULL;
+
+ if (me->parent->info)
+ return me->parent->info;
+
+ /* else: create a new structure */
+ return HTParentAnchor_new(me->parent);
+}
+
+void HTAnchor_setDocument(HTParentAnchor *me,
+ HyperDoc *doc)
+{
+ if (me)
+ me->document = doc;
+}
+
+HyperDoc *HTAnchor_document(HTParentAnchor *me)
+{
+ return (me ? me->document : NULL);
+}
+
+char *HTAnchor_address(HTAnchor * me)
+{
+ char *addr = NULL;
+
+ if (me) {
+ if (((HTParentAnchor0 *) me == me->parent) ||
+ ((HTParentAnchor *) me == me->parent->info) ||
+ !((HTChildAnchor *) me)->tag) { /* it's an adult or no tag */
+ StrAllocCopy(addr, me->parent->address);
+ } else { /* it's a named child */
+ HTSprintf0(&addr, "%s#%s",
+ me->parent->address, ((HTChildAnchor *) me)->tag);
+ }
+ }
+ return (addr);
+}
+
+char *HTAnchor_short_address(HTAnchor * me)
+{
+ const char chop[] = "file://localhost/";
+ char *addr = HTAnchor_address(me);
+
+ if (!strncmp(addr, chop, sizeof(chop) - 1)) {
+ char *a = addr + 7;
+ char *b = addr + sizeof(chop) - 2;
+
+ while ((*a++ = *b++) != '\0') {
+ ;
+ }
+ }
+ return addr;
+}
+
+void HTAnchor_setFormat(HTParentAnchor *me,
+ HTFormat form)
+{
+ if (me)
+ me->format = form;
+}
+
+HTFormat HTAnchor_format(HTParentAnchor *me)
+{
+ return (me ? me->format : NULL);
+}
+
+void HTAnchor_setIndex(HTParentAnchor *me,
+ const char *address)
+{
+ if (me) {
+ me->isIndex = YES;
+ StrAllocCopy(me->isIndexAction, address);
+ }
+}
+
+void HTAnchor_setPrompt(HTParentAnchor *me,
+ const char *prompt)
+{
+ if (me) {
+ StrAllocCopy(me->isIndexPrompt, prompt);
+ }
+}
+
+BOOL HTAnchor_isIndex(HTParentAnchor *me)
+{
+ return (BOOL) (me
+ ? me->isIndex
+ : NO);
+}
+
+/* Whether Anchor has been designated as an ISMAP link
+ * (normally by presence of an ISMAP attribute on A or IMG) - KW
+ */
+BOOL HTAnchor_isISMAPScript(HTAnchor * me)
+{
+ return (BOOL) ((me && me->parent->info)
+ ? me->parent->info->isISMAPScript
+ : NO);
+}
+
+#if defined(USE_COLOR_STYLE)
+/* Style handling.
+*/
+const char *HTAnchor_style(HTParentAnchor *me)
+{
+ return (me ? me->style : NULL);
+}
+
+void HTAnchor_setStyle(HTParentAnchor *me,
+ const char *style)
+{
+ if (me) {
+ StrAllocCopy(me->style, style);
+ }
+}
+#endif
+
+/* Title handling.
+*/
+const char *HTAnchor_title(HTParentAnchor *me)
+{
+ return (me ? me->title : NULL);
+}
+
+void HTAnchor_setTitle(HTParentAnchor *me,
+ const char *title)
+{
+ int i;
+
+ if (me) {
+ if (title) {
+ StrAllocCopy(me->title, title);
+ for (i = 0; me->title[i]; i++) {
+ if (UCH(me->title[i]) == 1 ||
+ UCH(me->title[i]) == 2) {
+ me->title[i] = ' ';
+ }
+ }
+ } else {
+ CTRACE((tfp, "HTAnchor_setTitle: New title is NULL! "));
+ if (me->title) {
+ CTRACE((tfp, "Old title was \"%s\".\n", me->title));
+ FREE(me->title);
+ } else {
+ CTRACE((tfp, "Old title was NULL.\n"));
+ }
+ }
+ }
+}
+
+void HTAnchor_appendTitle(HTParentAnchor *me,
+ const char *title)
+{
+ int i;
+
+ if (me) {
+ StrAllocCat(me->title, title);
+ for (i = 0; me->title[i]; i++) {
+ if (UCH(me->title[i]) == 1 ||
+ UCH(me->title[i]) == 2) {
+ me->title[i] = ' ';
+ }
+ }
+ }
+}
+
+/* Bookmark handling.
+*/
+const char *HTAnchor_bookmark(HTParentAnchor *me)
+{
+ return (me ? me->bookmark : NULL);
+}
+
+void HTAnchor_setBookmark(HTParentAnchor *me,
+ const char *bookmark)
+{
+ if (me)
+ StrAllocCopy(me->bookmark, bookmark);
+}
+
+/* Owner handling.
+*/
+const char *HTAnchor_owner(HTParentAnchor *me)
+{
+ return (me ? me->owner : NULL);
+}
+
+void HTAnchor_setOwner(HTParentAnchor *me,
+ const char *owner)
+{
+ if (me) {
+ StrAllocCopy(me->owner, owner);
+ }
+}
+
+/* TITLE handling in LINKs with REV="made" or REV="owner". - FM
+*/
+const char *HTAnchor_RevTitle(HTParentAnchor *me)
+{
+ return (me ? me->RevTitle : NULL);
+}
+
+void HTAnchor_setRevTitle(HTParentAnchor *me,
+ const char *title)
+{
+ int i;
+
+ if (me) {
+ StrAllocCopy(me->RevTitle, title);
+ for (i = 0; me->RevTitle[i]; i++) {
+ if (UCH(me->RevTitle[i]) == 1 ||
+ UCH(me->RevTitle[i]) == 2) {
+ me->RevTitle[i] = ' ';
+ }
+ }
+ }
+}
+
+#ifndef DISABLE_BIBP
+/* Citehost for bibp links from LINKs with REL="citehost". - RDC
+*/
+const char *HTAnchor_citehost(HTParentAnchor *me)
+{
+ return (me ? me->citehost : NULL);
+}
+
+void HTAnchor_setCitehost(HTParentAnchor *me,
+ const char *citehost)
+{
+ if (me) {
+ StrAllocCopy(me->citehost, citehost);
+ }
+}
+#endif /* !DISABLE_BIBP */
+
+/* Suggested filename handling. - FM
+ * (will be loaded if we had a Content-Disposition
+ * header or META element with filename=name.suffix)
+ */
+const char *HTAnchor_SugFname(HTParentAnchor *me)
+{
+ return (me ? me->SugFname : NULL);
+}
+
+/* HTTP Headers.
+*/
+const char *HTAnchor_http_headers(HTParentAnchor *me)
+{
+ return (me ? me->http_headers.data : NULL);
+}
+
+/* Content-Type handling (parameter list).
+*/
+const char *HTAnchor_content_type_params(HTParentAnchor *me)
+{
+ return (me ? me->content_type_params : NULL);
+}
+
+/* Content-Encoding handling. - FM
+ * (will be loaded if we had a Content-Encoding
+ * header.)
+ */
+const char *HTAnchor_content_encoding(HTParentAnchor *me)
+{
+ return (me ? me->content_encoding : NULL);
+}
+
+/* Content-Type handling. - FM
+*/
+const char *HTAnchor_content_type(HTParentAnchor *me)
+{
+ return (me ? me->content_type : NULL);
+}
+
+/* Last-Modified header handling. - FM
+*/
+const char *HTAnchor_last_modified(HTParentAnchor *me)
+{
+ return (me ? me->last_modified : NULL);
+}
+
+/* Date header handling. - FM
+*/
+const char *HTAnchor_date(HTParentAnchor *me)
+{
+ return (me ? me->date : NULL);
+}
+
+/* Server header handling. - FM
+*/
+const char *HTAnchor_server(HTParentAnchor *me)
+{
+ return (me ? me->server : NULL);
+}
+
+/* Safe header handling. - FM
+*/
+BOOL HTAnchor_safe(HTParentAnchor *me)
+{
+ return (BOOL) (me ? me->safe : FALSE);
+}
+
+/* Content-Base header handling. - FM
+*/
+const char *HTAnchor_content_base(HTParentAnchor *me)
+{
+ return (me ? me->content_base : NULL);
+}
+
+/* Content-Location header handling. - FM
+*/
+const char *HTAnchor_content_location(HTParentAnchor *me)
+{
+ return (me ? me->content_location : NULL);
+}
+
+/* Message-ID, used for mail replies - kw
+*/
+const char *HTAnchor_messageID(HTParentAnchor *me)
+{
+ return (me ? me->message_id : NULL);
+}
+
+BOOL HTAnchor_setMessageID(HTParentAnchor *me,
+ const char *messageid)
+{
+ if (!(me && messageid && *messageid)) {
+ return FALSE;
+ }
+ StrAllocCopy(me->message_id, messageid);
+ return TRUE;
+}
+
+/* Subject, used for mail replies - kw
+*/
+const char *HTAnchor_subject(HTParentAnchor *me)
+{
+ return (me ? me->subject : NULL);
+}
+
+BOOL HTAnchor_setSubject(HTParentAnchor *me,
+ const char *subject)
+{
+ if (!(me && subject && *subject)) {
+ return FALSE;
+ }
+ StrAllocCopy(me->subject, subject);
+ return TRUE;
+}
+
+/* Manipulation of links
+ * ---------------------
+ */
+HTAnchor *HTAnchor_followLink(HTChildAnchor *me)
+{
+ return (me->dest);
+}
+
+HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me,
+ HTLinkType *type)
+{
+ if (me->type == type)
+ return (me->dest);
+ return (NULL); /* No link of me type */
+}
+
+/* Methods List
+ * ------------
+ */
+HTList *HTAnchor_methods(HTParentAnchor *me)
+{
+ if (!me->methods) {
+ me->methods = HTList_new();
+ }
+ return (me->methods);
+}
+
+/* Protocol
+ * --------
+ */
+void *HTAnchor_protocol(HTParentAnchor *me)
+{
+ return (me->protocol);
+}
+
+void HTAnchor_setProtocol(HTParentAnchor *me,
+ void *protocol)
+{
+ me->protocol = protocol;
+}
+
+/* Physical Address
+ * ----------------
+ */
+char *HTAnchor_physical(HTParentAnchor *me)
+{
+ return (me->physical);
+}
+
+void HTAnchor_setPhysical(HTParentAnchor *me,
+ char *physical)
+{
+ if (me) {
+ StrAllocCopy(me->physical, physical);
+ }
+}
+
+#ifdef DEBUG
+static void show_stages(HTParentAnchor *me, const char *tag, int which_stage)
+{
+ int j;
+
+ CTRACE((tfp, "Stages %s*%s", NonNull(me->charset), tag));
+ for (j = 0; j < UCT_STAGEMAX; j++) {
+ CTRACE((tfp, " "));
+ if (j == which_stage)
+ CTRACE((tfp, "("));
+ CTRACE((tfp, "%d:%d:%s",
+ j,
+ me->UCStages->s[j].LYhndl,
+ NonNull(me->UCStages->s[j].C.MIMEname)));
+ if (j == which_stage)
+ CTRACE((tfp, ")"));
+ }
+ CTRACE((tfp, "\n"));
+}
+#else
+#define show_stages(me,tag,which_stage) /* nothing */
+#endif
+
+/*
+ * We store charset info in the HTParentAnchor object, for several
+ * "stages". (See UCDefs.h)
+ * A stream method is supposed to know what stage in the model it is.
+ *
+ * General model MIME -> parser -> structured -> HText
+ * e.g., text/html
+ * from HTTP: HTMIME.c -> SGML.c -> HTML.c -> GridText.c
+ * text/plain
+ * from file: HTFile.c -> HTPlain.c -> GridText.c
+ *
+ * The lock/set_by is used to lock e.g. a charset set by an explicit
+ * HTTP MIME header against overriding by a HTML META tag - the MIME
+ * header has higher priority. Defaults (from -assume_.. options etc.)
+ * will not override charset explicitly given by server.
+ *
+ * Some advantages of keeping this in the HTAnchor:
+ * - Global variables are bad.
+ * - Can remember a charset given by META tag when toggling to SOURCE view.
+ * - Can remember a charset given by <A CHARSET=...> href in another doc.
+ *
+ * We don't modify the HTParentAnchor's charset element
+ * here, that one will only be set when explicitly given.
+ */
+LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me,
+ int which_stage)
+{
+ LYUCcharset *result = NULL;
+
+ if (me) {
+ if (!me->UCStages) {
+ int i;
+ int chndl = UCLYhndl_for_unspec; /* always >= 0 */
+ UCAnchorInfo *stages = typecalloc(UCAnchorInfo);
+
+ if (stages == NULL)
+ outofmem(__FILE__, "HTAnchor_getUCInfoStage");
+
+ for (i = 0; i < UCT_STAGEMAX; i++) {
+ stages->s[i].C.MIMEname = "";
+ stages->s[i].LYhndl = -1;
+ }
+ if (me->charset) {
+ chndl = UCGetLYhndl_byMIME(me->charset);
+ if (chndl < 0)
+ chndl = UCLYhndl_for_unrec;
+ if (chndl < 0)
+ /*
+ * UCLYhndl_for_unrec not defined :-(
+ * fallback to UCLYhndl_for_unspec which always valid.
+ */
+ chndl = UCLYhndl_for_unspec; /* always >= 0 */
+ }
+ MemCpy(&stages->s[UCT_STAGE_MIME].C, &LYCharSet_UC[chndl],
+ sizeof(LYUCcharset));
+
+ stages->s[UCT_STAGE_MIME].lock = UCT_SETBY_DEFAULT;
+ stages->s[UCT_STAGE_MIME].LYhndl = chndl;
+ me->UCStages = stages;
+ }
+ result = (&me->UCStages->s[which_stage].C);
+ show_stages(me, "_getUCInfoStage", which_stage);
+ }
+ return (result);
+}
+
+int HTAnchor_getUCLYhndl(HTParentAnchor *me,
+ int which_stage)
+{
+ if (me) {
+ if (!me->UCStages) {
+ /*
+ * This will allocate and initialize, if not yet done.
+ */
+ (void) HTAnchor_getUCInfoStage(me, which_stage);
+ }
+ if (me->UCStages->s[which_stage].lock > UCT_SETBY_NONE) {
+ return (me->UCStages->s[which_stage].LYhndl);
+ }
+ }
+ return (-1);
+}
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+static void setup_switch_display_charset(HTParentAnchor *me, int h)
+{
+ if (!Switch_Display_Charset(h, SWITCH_DISPLAY_CHARSET_MAYBE))
+ return;
+ HTAnchor_setUCInfoStage(me, current_char_set,
+ UCT_STAGE_HTEXT, UCT_SETBY_MIME); /* highest priority! */
+ HTAnchor_setUCInfoStage(me, current_char_set,
+ UCT_STAGE_STRUCTURED, UCT_SETBY_MIME); /* highest priority! */
+ CTRACE((tfp,
+ "changing UCInfoStage: HTEXT/STRUCTURED stages charset='%s'.\n",
+ LYCharSet_UC[current_char_set].MIMEname));
+}
+#endif
+
+LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me,
+ int LYhndl,
+ int which_stage,
+ int set_by)
+{
+ if (me) {
+ /*
+ * This will allocate and initialize, if not yet done.
+ */
+ LYUCcharset *p = HTAnchor_getUCInfoStage(me, which_stage);
+
+ /*
+ * Can we override?
+ */
+ if (set_by >= me->UCStages->s[which_stage].lock) {
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ int ohandle = me->UCStages->s[which_stage].LYhndl;
+#endif
+ me->UCStages->s[which_stage].lock = set_by;
+ me->UCStages->s[which_stage].LYhndl = LYhndl;
+ if (LYhndl >= 0) {
+ MemCpy(p, &LYCharSet_UC[LYhndl], sizeof(LYUCcharset));
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ /* Allow a switch to a more suitable display charset */
+ if (LYhndl != ohandle && which_stage == UCT_STAGE_PARSER)
+ setup_switch_display_charset(me, LYhndl);
+#endif
+ } else {
+ p->UChndl = -1;
+ }
+ show_stages(me, "_setUCInfoStage", which_stage);
+ return (p);
+ }
+ }
+ return (NULL);
+}
+
+LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me,
+ int LYhndl,
+ int which_stage,
+ int set_by)
+{
+ LYUCcharset *result = NULL;
+ int ohandle;
+
+ if (me && me->UCStages) {
+ me->UCStages->s[which_stage].lock = set_by;
+ ohandle = me->UCStages->s[which_stage].LYhndl;
+ me->UCStages->s[which_stage].LYhndl = LYhndl;
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ /* Allow a switch to a more suitable display charset */
+ if (LYhndl >= 0 && LYhndl != ohandle
+ && which_stage == UCT_STAGE_PARSER)
+ setup_switch_display_charset(me, LYhndl);
+#else
+ (void) ohandle;
+#endif
+ show_stages(me, "_resetUCInfoStage", which_stage);
+ result = (&me->UCStages->s[which_stage].C);
+ }
+ return result;
+}
+
+/*
+ * A set_by of (-1) means use the lock value from the from_stage.
+ */
+LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me,
+ int to_stage,
+ int from_stage,
+ int set_by)
+{
+ if (me) {
+ /*
+ * This will allocate and initialize, if not yet done.
+ */
+ LYUCcharset *p_from = HTAnchor_getUCInfoStage(me, from_stage);
+ LYUCcharset *p_to = HTAnchor_getUCInfoStage(me, to_stage);
+
+ /*
+ * Can we override?
+ */
+ if (set_by == -1)
+ set_by = me->UCStages->s[from_stage].lock;
+ if (set_by == UCT_SETBY_NONE)
+ set_by = UCT_SETBY_DEFAULT;
+ if (set_by >= me->UCStages->s[to_stage].lock) {
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ int ohandle = me->UCStages->s[to_stage].LYhndl;
+#endif
+ me->UCStages->s[to_stage].lock = set_by;
+ me->UCStages->s[to_stage].LYhndl =
+ me->UCStages->s[from_stage].LYhndl;
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ /* Allow a switch to a more suitable display charset */
+ if (me->UCStages->s[to_stage].LYhndl >= 0
+ && me->UCStages->s[to_stage].LYhndl != ohandle
+ && to_stage == UCT_STAGE_PARSER)
+ setup_switch_display_charset(me,
+ me->UCStages->s[to_stage].LYhndl);
+#endif
+ if (p_to != p_from)
+ MemCpy(p_to, p_from, sizeof(LYUCcharset));
+
+ return (p_to);
+ }
+ }
+ return (NULL);
+}