Blob Blame History Raw
/*
 * $LynxId: HTAnchor.c,v 1.81 2018/03/11 18:34:50 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 sence 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 priorty! */
    HTAnchor_setUCInfoStage(me, current_char_set,
			    UCT_STAGE_STRUCTURED, UCT_SETBY_MIME);	/* highest priorty! */
    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);
}