/*	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 101		/* Arbitrary prime.  Memory/speed tradeoff */

#include <HTUtils.h>
#include <HTAnchor.h>
#include <HTParse.h>
#include <UCAux.h>
#include <UCMap.h>

#include <LYUtils.h>
#include <LYCharSets.h>
#include <LYLeaks.h>

#ifdef NOT_DEFINED
/*
 *	This is the hashing function used to determine which list in the
 *		adult_table a parent anchor should be put in.  This is a
 *		much simpler function than the original used.
 */
#define HASH_FUNCTION(cp_address) ((unsigned short int)strlen(cp_address) *\
	(unsigned short int)TOUPPER(*cp_address) % HASH_SIZE)
#endif /* NOT_DEFINED */
/*
 *	This is the original function.	We'll use it again. - FM
 */
PRIVATE int HASH_FUNCTION ARGS1(
	CONST char *,	cp_address)
{
    int hash;
    CONST unsigned char *p;

    for (p = (CONST unsigned char *)cp_address, hash = 0; *p; p++)
	hash = (int) (hash * 3 + (*(CONST unsigned char *)p)) % HASH_SIZE;

    return(hash);
}

typedef struct _HyperDoc Hyperdoc;
#ifdef VMS
struct _HyperDoc {
	int junk;	/* VMS cannot handle pointers to undefined structs */
};
#endif /* VMS */

PRIVATE HTList **adult_table = 0;  /* Point to table of lists of all parents */

/*				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.
*/
PRIVATE HTParentAnchor * HTParentAnchor_new NOARGS
{
    HTParentAnchor *newAnchor =
       (HTParentAnchor *)calloc(1, sizeof(HTParentAnchor));  /* zero-filled */
    if (newAnchor == NULL)
	outofmem(__FILE__, "HTParentAnchor_new");
    newAnchor->parent = newAnchor;
    newAnchor->bookmark = NULL; 	/* Bookmark filename. - FM */
    newAnchor->isISMAPScript = FALSE;	/* Lynx appends ?0,0 if TRUE. - FM */
    newAnchor->isHEAD = FALSE;		/* HEAD request if TRUE. - FM */
    newAnchor->safe = FALSE;		/* Safe. - FM */
#ifdef SOURCE_CACHE
    newAnchor->source_cache_file = NULL;
    newAnchor->source_cache_chunk = NULL;
#endif
    newAnchor->FileCache = NULL;	/* Path to a disk-cached copy. - FM */
    newAnchor->SugFname = NULL; 	/* Suggested filename. - FM */
    newAnchor->RevTitle = NULL; 	/* TITLE for a LINK with REV. - FM */
    newAnchor->cache_control = NULL;	/* Cache-Control. - FM */
    newAnchor->no_cache = FALSE;	/* no-cache? - FM */
    newAnchor->content_type = NULL;	/* Content-Type. - FM */
    newAnchor->content_language = NULL; /* Content-Language. - FM */
    newAnchor->content_encoding = NULL; /* Compression algorithm. - FM */
    newAnchor->content_base = NULL;	/* Content-Base. - FM */
    newAnchor->content_disposition = NULL; /* Content-Disposition. - FM */
    newAnchor->content_location = NULL; /* Content-Location. - FM */
    newAnchor->content_md5 = NULL;	/* Content-MD5. - FM */
    newAnchor->content_length = 0;	/* Content-Length. - FM */
    newAnchor->date = NULL;		/* Date. - FM */
    newAnchor->expires = NULL;		/* Expires. - FM */
    newAnchor->last_modified = NULL;	/* Last-Modified. - FM */
    newAnchor->ETag = NULL;		/* ETag (HTTP/1.1 cache validator) */
    newAnchor->server = NULL;		/* Server. - FM */
    return(newAnchor);
}

PRIVATE HTChildAnchor * HTChildAnchor_new NOARGS
{
    HTChildAnchor *p;

    p = (HTChildAnchor *)calloc(1, sizeof(HTChildAnchor)); /* zero-filled */
    if (p == NULL)
	outofmem(__FILE__, "HTChildAnchor_new");
    return p;
}


#ifdef CASE_INSENSITIVE_ANCHORS
/*	Case insensitive 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 ignoring case
**		NO if they differ in more than	their case.
*/
PRIVATE BOOL HTEquivalent ARGS2(
	CONST char *,	s,
	CONST char *,	t)
{
    if (s && t) {  /* Make sure they point to something */
	for (; *s && *t; s++, t++) {
	    if (TOUPPER(*s) != TOUPPER(*t)) {
		return(NO);
	    }
	}
	return( TOUPPER(*s) == TOUPPER(*t));
    } else {
	return(s == t); 	/* Two NULLs are equivalent, aren't they ? */
    }
}

#else

/*	Case sensitive string comparison
**	----------------------------------
** On entry,
**	s	Points to one string, null terminated
**	t	points to the other.
** On exit,
**	returns YES if the strings are identical or both NULL
**		NO if they differ.
*/
PRIVATE BOOL HTIdentical ARGS2(
	CONST char *,	s,
	CONST char *,	t)
{
    if (s && t) {  /* Make sure they point to something */
#ifdef SH_EX	/* 1998/04/28 (Tue) 22:02:58 */
	if (*s == 'P' || *t == 'P') {
	    if (strcmp(s + 1, "Name") == 0 || strcmp(t + 1, "Name") == 0)
		return NO;
	}
#endif
	for (; *s && *t; s++, t++) {
	    if (*s != *t) {
		return(NO);
	    }
	}
	return (BOOL) (*s == *t);
    } else {
	return (BOOL) (s == t);  /* Two NULLs are identical, aren't they ? */
    }
}
#endif /* CASE_INSENSITIVE_ANCHORS */


/*	Create new or find old sub-anchor
**	---------------------------------
**
**	Me one is for a new anchor being edited into an existing
**	document.  The parent anchor must already exist.
*/
PUBLIC HTChildAnchor * HTAnchor_findChild ARGS2(
	HTParentAnchor *,	parent,
	CONST char *,		tag)
{
    HTChildAnchor *child;
    HTList *kids;

    if (!parent) {
	CTRACE((tfp, "HTAnchor_findChild called with NULL parent.\n"));
	return(NULL);
    }
    if ((kids = parent->children) != 0) {
	/*
	**  Parent has children.  Search them.
	*/
	if (tag && *tag) {		/* TBL */
	    while (NULL != (child=(HTChildAnchor *)HTList_nextObject(kids))) {
#ifdef CASE_INSENSITIVE_ANCHORS
		if (HTEquivalent(child->tag, tag)) /* Case insensitive */
#else
		if (HTIdentical(child->tag, tag)) /* Case sensitive - FM */
#endif /* CASE_INSENSITIVE_ANCHORS */
		{
		    CTRACE((tfp, "Child anchor %p of parent %p with name `%s' already exists.\n",
				(void *)child, (void *)parent, tag));
		    return(child);
		}
	    }
	}  /*  end if tag is void */
    } else {  /* parent doesn't have any children yet : create family */
	parent->children = HTList_new();
    }

    child = HTChildAnchor_new();
    CTRACE((tfp, "HTAnchor: New Anchor %p named `%s' is child of %p\n",
		(void *)child,
		tag ? tag : (CONST char *)"",
		(void *)parent)); /* int for apollo */
    HTList_addObject (parent->children, child);
    child->parent = parent;
    StrAllocCopy(child->tag, tag);
    return(child);
}


/*	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)
*/
PUBLIC HTChildAnchor * HTAnchor_findChildAndLink ARGS4(
	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 = HTAnchor_findChild(parent, tag);

    CTRACE((tfp,"Entered HTAnchor_findChildAndLink\n"));

    if (href && *href) {
	char *relative_to = HTAnchor_address((HTAnchor *)parent);
	DocAddress parsed_doc;
	HTAnchor * dest;

	parsed_doc.address = HTParse(href, relative_to, PARSE_ALL);
#ifndef DONT_TRACK_INTERNAL_LINKS
	if (ltype && parent->post_data && ltype == LINK_INTERNAL) {
	    /* for internal links, find a destination with the same
	       post data if the source of the link has post data. - kw */
	    parsed_doc.post_data = parent->post_data;
	    parsed_doc.post_content_type = parent->post_content_type;
	} else
#endif
	{
	    parsed_doc.post_data = NULL;
	    parsed_doc.post_content_type = NULL;
	}
	parsed_doc.bookmark = NULL;
	parsed_doc.isHEAD = FALSE;
	parsed_doc.safe = FALSE;
	dest = HTAnchor_findAddress(&parsed_doc);

#define DUPLICATE_ANCHOR_NAME_WORKAROUND

#ifdef DUPLICATE_ANCHOR_NAME_WORKAROUND
	if (tag && *tag) {
	    HTAnchor *testdest1;
	    int nlinks;
	    testdest1 = child->mainLink.dest;
	    if (testdest1) {
		nlinks = 1 + HTList_count(child->links);
		CTRACE((tfp,
		       "*** Duplicate ChildAnchor %p named `%s' with %d links",
		       child, tag, nlinks));
		if (dest == testdest1 && ltype == child->mainLink.type) {
		    CTRACE((tfp,", same dest %p and type, keeping it\n",
			   testdest1));
		} else {
		    CTRACE((tfp,", different dest %p, creating unnamed child\n",
			   testdest1));
		    child = HTAnchor_findChild(parent, 0);
		}
	    }
	}
#endif
	HTAnchor_link((HTAnchor *)child, dest, ltype);
	FREE(parsed_doc.address);
	FREE(relative_to);
    }
    return(child);
}

#ifdef LY_FIND_LEAKS
/*
**  Function for freeing the adult hash table. - FM
*/
PRIVATE void free_adult_table NOARGS
{
    int i_counter;
    HTList * HTAp_freeme;
    HTParentAnchor * parent;
    /*
     *	Loop through all lists.
     */
    for (i_counter = 0; i_counter < HASH_SIZE; i_counter++) {
	/*
	**  Loop through the list.
	*/
	while (adult_table[i_counter] != NULL) {
	    /*
	    **	Free off items - FM
	    */
	    HTAp_freeme = adult_table[i_counter];
	    adult_table[i_counter] = HTAp_freeme->next;
	    if (HTAp_freeme->object) {
		parent = (HTParentAnchor *)HTAp_freeme->object;
		CTRACE((tfp, "delete anchor:%d/%d,%d,%d %s\n",
		       i_counter, HTList_count(HTAp_freeme) + 1,
		       (parent->physical ? 1 : 0),
		       (int)parent->underway,
		       (parent->address ? parent->address : "(no address)")));
		parent->underway = FALSE;
		HTAnchor_delete(parent);
	    }
	    FREE(HTAp_freeme);
	}
    }
    FREE(adult_table);
}
#endif /* LY_FIND_LEAKS */

/*	Create new or find old named 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.
*/
PUBLIC HTAnchor * HTAnchor_findAddress ARGS1(
	CONST DocAddress *,	newdoc)
{
    /* Anchor tag specified ? */
    char *tag = HTParse(newdoc->address, "", PARSE_ANCHOR);

    CTRACE((tfp,"Entered HTAnchor_findAddress\n"));

    /*
    **	If the address represents a sub-anchor, we recursively load its
    **	parent, then we create a child anchor within that document.
    */
    if (*tag) {
	DocAddress parsed_doc;
	HTParentAnchor * foundParent;
	HTChildAnchor * foundAnchor;

	parsed_doc.address = HTParse(newdoc->address, "",
		PARSE_ACCESS | PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
	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 = (HTParentAnchor *)HTAnchor_findAddress(&parsed_doc);
	foundAnchor = HTAnchor_findChild (foundParent, tag);
	FREE(parsed_doc.address);
	FREE(tag);
	return (HTAnchor *)foundAnchor;
    } else {
	/*
	**  If the address has no anchor tag,
	**  check whether we have this node.
	*/
	int hash;
	HTList * adults;
	HTList *grownups;
	HTParentAnchor * foundAnchor;

	FREE(tag);

	/*
	**  Select list from hash table,
	*/
	hash = HASH_FUNCTION(newdoc->address);
	if (!adult_table) {
	    adult_table = (HTList **)calloc(HASH_SIZE, sizeof(HTList *));
	    if (!adult_table)
		outofmem(__FILE__, "HTAnchor_findAddress");
#ifdef LY_FIND_LEAKS
	    atexit(free_adult_table);
#endif
	}
	if (!adult_table[hash])
	    adult_table[hash] = HTList_new();
	adults = adult_table[hash];

	/*
	**  Search list for anchor.
	*/
	grownups = adults;
	while (NULL != (foundAnchor =
			(HTParentAnchor *)HTList_nextObject(grownups))) {
#ifdef CASE_INSENSITIVE_ANCHORS
	    if (HTEquivalent(foundAnchor->address, newdoc->address) &&
		HTEquivalent(foundAnchor->post_data, newdoc->post_data) &&
		foundAnchor->isHEAD == newdoc->isHEAD)
#else
	    if (HTIdentical(foundAnchor->address, newdoc->address) &&
		HTIdentical(foundAnchor->post_data, newdoc->post_data) &&
		foundAnchor->isHEAD == newdoc->isHEAD)
#endif /* CASE_INSENSITIVE_ANCHORS */
	    {
		CTRACE((tfp, "Anchor %p with address `%s' already exists.\n",
			    (void *)foundAnchor, newdoc->address));
		 return (HTAnchor *)foundAnchor;
	     }
	}

	/*
	**  Node not found: create new anchor.
	*/
	foundAnchor = HTParentAnchor_new();
	CTRACE((tfp, "New anchor %p has hash %d and address `%s'\n",
		    (void *)foundAnchor, hash, newdoc->address));
	StrAllocCopy(foundAnchor->address, newdoc->address);
	if (newdoc->post_data)
	    StrAllocCopy(foundAnchor->post_data, newdoc->post_data);
	if (newdoc->post_content_type)
	    StrAllocCopy(foundAnchor->post_content_type,
			 newdoc->post_content_type);
	if (newdoc->bookmark)
	    StrAllocCopy(foundAnchor->bookmark, newdoc->bookmark);
	foundAnchor->isHEAD = newdoc->isHEAD;
	foundAnchor->safe = newdoc->safe;
	HTList_addObject (adults, foundAnchor);
	return (HTAnchor *)foundAnchor;
    }
}
/*	Create new or find old named anchor - simple form
**	-------------------------------------------------
**
**	Like the previous one, but simpler to use for simple cases.
**	No post data etc. can be supplied. - kw
*/
PUBLIC HTAnchor * HTAnchor_findSimpleAddress ARGS1(
	CONST char *,	url)
{
    DocAddress urldoc;

    urldoc.address = (char *)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);
}

/*	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 parent and children are deleted, and this anchor
**	is removed from the sources list of all its targets.
**	We also try to delete the targets whose documents are not loaded.
**	If this anchor's source list is empty, we delete it and its children.
*/
PRIVATE void deleteLinks ARGS1(
	HTAnchor *,	me)
{
    /*
     *	Memory leaks fixed.
     *	05-27-94 Lynx 2-3-1 Garrett Arch Blythe
     */

    /*
     *	Anchor is NULL, do nothing.
     */
    if (!me) {
	return;
    }

    /*
     *	Unregister me with our mainLink destination anchor's parent.
     */
    if (me->mainLink.dest) {
	HTParentAnchor *parent = me->mainLink.dest->parent;

	/*
	 *  Set the mainLink pointer to zero NOW.  If we don't,
	 *  and we get somehow called recursively again for this
	 *  same old me during the HTAnchor_delete below, weird
	 *  things can occasionally happen. - kw
	 */
	 me->mainLink.dest = NULL;

	/*
	 *  Remove me from the parent's sources so that the
	 *  parent knows one less anchor is it's dest.
	 */
	if (!HTList_isEmpty(parent->sources)) {
	    /*
	     *	Really should only need to deregister once.
	     */
	    HTList_removeObject(parent->sources, (void *)me);
	}

	/*
	 *  Test here to avoid calling overhead.
	 *  If the parent has no loaded document, then we should
	 *  tell it to attempt to delete itself.
	 *  Don't do this jazz if the anchor passed in is the same
	 *  as the anchor to delete.
	 *  Also, don't do this if the destination parent is our
	 *  parent.
	 */
	if (!parent->document &&
	    parent != (HTParentAnchor *)me &&
	    me->parent != parent) {
	    HTAnchor_delete(parent);
	}

	/*
	 *  At this point, we haven't a mainLink.  Set it to be
	 *  so.
	 *  Leave the HTAtom pointed to by type up to other code to
	 *  handle (reusable, near static).
	 */
	me->mainLink.type = NULL;
    }

    /*
     *	Check for extra destinations in our links list.
     */
    if (!HTList_isEmpty(me->links)) {
	HTLink *target;
	HTParentAnchor *parent;

	/*
	 *  Take out our extra non mainLinks one by one, calling
	 *  their parents to know that they are no longer
	 *  the destination of me's anchor.
	 */
	while ((target = (HTLink *)HTList_removeLastObject(me->links)) != 0) {
	    parent = target->dest->parent;
	    if (!HTList_isEmpty(parent->sources)) {
		/*
		 *  Only need to tell destination parent
		 *  anchor once.
		 */
		HTList_removeObject(parent->sources, (void *)me);
	    }

	    /*
	     *	Avoid calling overhead.
	     *	If the parent hasn't a loaded document, then
	     *	   we will attempt to have the parent
	     *	   delete itself.
	     *	Don't call twice if this is the same anchor
	     *	   that we are trying to delete.
	     *	Also, don't do this if we are trying to delete
	     *	   our parent.
	     */
	    if (!parent->document &&
		(HTParentAnchor *)me != parent &&
		me->parent != parent) {
		HTAnchor_delete(parent);
	    }
	    /*
	     *	The link structure has to be deleted, too!
	     *	That was missing, but this code probably never
	     *	got exercised by Lynx.	- KW
	     */
	    FREE(target);
	}

	/*
	 *  At this point, me no longer has any destination in
	 *  the links list.  Get rid of it.
	 */
	if (me->links) {
	    HTList_delete(me->links);
	    me->links = NULL;
	}
    }

    /*
     *	Catch in case links list exists but nothing in it.
     */
    if (me->links) {
	HTList_delete(me->links);
	me->links = NULL;
    }
}

#ifdef SOURCE_CACHE
PUBLIC void HTAnchor_clearSourceCache ARGS1(
	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));
	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 /* SOURCE_CACHE */

PUBLIC BOOL HTAnchor_delete ARGS1(
	HTParentAnchor *,	me)
{
    /*
     *	Memory leaks fixed.
     * 05-27-94 Lynx 2-3-1 Garrett Arch Blythe
     */
    HTList *cur;
    HTChildAnchor *child;

    /*
     *	Do nothing if nothing to do.
     */
    if (!me) {
	return(NO);
    }

    /*
     *	Don't delete if document is loaded or being loaded.
     */
    if (me->document || me->underway) {
	return(NO);
    }

    /*
     *  Mark ourselves busy, so that recursive calls of this function
     *  on this HTParentAnchor will not free it from under our feet. - kw
     */
    me->underway = TRUE;

    /*
     *	Recursively try to delete destination anchors of this parent.
     *	In any event, this will tell all destination anchors that we
     *	no longer consider them a destination.
     */
    deleteLinks((HTAnchor *)me);

    /*
     *	There are still incoming links to this one (we are the
     *	destination of another anchor).
     *	Don't actually delete this anchor, but children are OK to
     *	delete their links.
     */
    if (!HTList_isEmpty(me->sources)) {
	/*
	 *  Delete all outgoing links from children, do not
	 *  delete the children, though.
	 */
	if (!HTList_isEmpty(me->children)) {
	    cur = me->children;
	    while ((child = (HTChildAnchor *)HTList_nextObject(cur)) != 0) {
		if (child != NULL) {
		    deleteLinks((HTAnchor *)child);
		}
	    }
	}
	me->underway = FALSE;

	/*
	 *  Can't delete parent, still have sources.
	 */
	return(NO);
    }

    /*
     *	No more incoming links : kill everything
     *	First, recursively delete children and their links.
     */
    if (!HTList_isEmpty(me->children)) {
	while ((child = (HTChildAnchor *)HTList_removeLastObject(
							me->children)) != 0) {
	    if (child) {
		deleteLinks((HTAnchor *)child);
		if (child->tag) {
		    FREE(child->tag);
		}
		FREE(child);
	    }
	}
    }
    me->underway = FALSE;

    /*
     *	Delete our empty list of children.
     */
    if (me->children) {
	HTList_delete(me->children);
	me->children = NULL;
    }

    /*
     * Delete our empty list of sources.
     */
    if (me->sources) {
	HTList_delete(me->sources);
	me->sources = NULL;
    }

    /*
     *	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);
    FREE(me->post_data);
    FREE(me->post_content_type);
    FREE(me->bookmark);
    FREE(me->owner);
    FREE(me->RevTitle);
#ifdef SOURCE_CACHE
    HTAnchor_clearSourceCache(me);
#endif
    if (me->FileCache) {
	FILE *fd;
	if ((fd = fopen(me->FileCache, "r")) != NULL) {
	    fclose(fd);
	    remove(me->FileCache);
	}
	FREE(me->FileCache);
    }
    FREE(me->SugFname);
    FREE(me->cache_control);
    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_HASH
    FREE(me->style);
#endif

    /*
     *	Remove ourselves from the hash table's list.
     */
    if (adult_table) {
	unsigned short int usi_hash = HASH_FUNCTION(me->address);

	if (adult_table[usi_hash])  {
	    HTList_removeObject(adult_table[usi_hash], (void *)me);
	}
    }

    /*
     *	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 the address.
     */
    FREE(me->address);

    FREE (me->UCStages);
    ImageMapList_free(me->imaps);


    /*
     *	Finally, kill the parent anchor passed in.
     */
    FREE(me);

    return(YES);
}


/*		Move an anchor to the head of the list of its siblings
**		------------------------------------------------------
**
**	This is to ensure that an anchor which might have already existed
**	is put in the correct order as we load the document.
*/
PUBLIC void HTAnchor_makeLastChild ARGS1(
	HTChildAnchor *,	me)
{
    if (me->parent != (HTParentAnchor *)me) {  /* Make sure it's a child */
	HTList * siblings = me->parent->children;
	HTList_removeObject (siblings, me);
	HTList_addObject (siblings, me);
    }
}

/*	Data access functions
**	---------------------
*/
PUBLIC HTParentAnchor * HTAnchor_parent ARGS1(
	HTAnchor *,	me)
{
    return( me ? me->parent : NULL);
}

PUBLIC void HTAnchor_setDocument ARGS2(
	HTParentAnchor *,	me,
	HyperDoc *,		doc)
{
    if (me)
	me->document = doc;
}

PUBLIC HyperDoc * HTAnchor_document ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->document : NULL);
}


PUBLIC char * HTAnchor_address ARGS1(
	HTAnchor *,	me)
{
    char *addr = NULL;

    if (me) {
	if (((HTParentAnchor *)me == me->parent) ||
	    !((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);
}

PUBLIC void HTAnchor_setFormat ARGS2(
	HTParentAnchor *,	me,
	HTFormat,		form)
{
    if (me)
	me->format = form;
}

PUBLIC HTFormat HTAnchor_format ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->format : NULL);
}

PUBLIC void HTAnchor_setIndex ARGS2(
	HTParentAnchor *,	me,
	char *, 		address)
{
    if (me) {
	me->isIndex = YES;
	StrAllocCopy(me->isIndexAction, address);
    }
}

PUBLIC void HTAnchor_setPrompt ARGS2(
	HTParentAnchor *,	me,
	char *, 		prompt)
{
    if (me) {
	StrAllocCopy(me->isIndexPrompt, prompt);
    }
}

PUBLIC BOOL HTAnchor_isIndex ARGS1(
	HTParentAnchor *,	me)
{
    return( 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
*/
PUBLIC BOOL HTAnchor_isISMAPScript ARGS1(
	HTAnchor *,	me)
{
    return( me ? me->parent->isISMAPScript : NO);
}

PUBLIC BOOL HTAnchor_hasChildren ARGS1(
	HTParentAnchor *,	me)
{
    return (BOOL) ( me ? ! HTList_isEmpty(me->children) : NO);
}

#if defined(USE_HASH)
/*	Style handling.
*/
PUBLIC CONST char * HTAnchor_style ARGS1(
	HTParentAnchor *,	me)
{
	return( me ? me->style : NULL);
}

PUBLIC void HTAnchor_setStyle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		style)
{
    if (me) {
	StrAllocCopy(me->style, style);
    }
}
#endif


/*	Title handling.
*/
PUBLIC CONST char * HTAnchor_title ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->title : NULL);
}

PUBLIC void HTAnchor_setTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
	if (title) {
	    StrAllocCopy(me->title, title);
	    for (i = 0; me->title[i]; i++) {
		if ((unsigned char)me->title[i] == 1 ||
		    (unsigned char)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"));
	    }
	}
    }
}

PUBLIC void HTAnchor_appendTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
	StrAllocCat(me->title, title);
	for (i = 0; me->title[i]; i++) {
	    if ((unsigned char)me->title[i] == 1 ||
		(unsigned char)me->title[i] == 2) {
		me->title[i] = ' ';
	    }
	}
    }
}

/*	Bookmark handling.
*/
PUBLIC CONST char * HTAnchor_bookmark ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->bookmark : NULL);
}

PUBLIC void HTAnchor_setBookmark ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		bookmark)
{
    if (me)
	StrAllocCopy(me->bookmark, bookmark);
}

/*	Owner handling.
*/
PUBLIC CONST char * HTAnchor_owner ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->owner : NULL);
}

PUBLIC void HTAnchor_setOwner ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		owner)
{
    if (me) {
	StrAllocCopy(me->owner, owner);
    }
}

/*	TITLE handling in LINKs with REV="made" or REV="owner". - FM
*/
PUBLIC CONST char * HTAnchor_RevTitle ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->RevTitle : NULL);
}

PUBLIC void HTAnchor_setRevTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
	StrAllocCopy(me->RevTitle, title);
	for (i = 0; me->RevTitle[i]; i++) {
	    if ((unsigned char)me->RevTitle[i] == 1 ||
		(unsigned char)me->RevTitle[i] == 2) {
		me->RevTitle[i] = ' ';
	    }
	}
    }
}

/*	Suggested filename handling. - FM
**	(will be loaded if we had a Content-Disposition
**	 header or META element with filename=name.suffix)
*/
PUBLIC CONST char * HTAnchor_SugFname ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->SugFname : NULL);
}

/*	Content-Encoding handling. - FM
**	(will be loaded if we had a Content-Encoding
**	 header.)
*/
PUBLIC CONST char * HTAnchor_content_encoding ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->content_encoding : NULL);
}

/*	Content-Type handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_type ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->content_type : NULL);
}

/*	Last-Modified header handling. - FM
*/
PUBLIC CONST char * HTAnchor_last_modified ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->last_modified : NULL);
}

/*	Date header handling. - FM
*/
PUBLIC CONST char * HTAnchor_date ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->date : NULL);
}

/*	Server header handling. - FM
*/
PUBLIC CONST char * HTAnchor_server ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->server : NULL);
}

/*	Safe header handling. - FM
*/
PUBLIC BOOL HTAnchor_safe ARGS1(
	HTParentAnchor *,	me)
{
    return (BOOL) ( me ? me->safe : FALSE);
}

/*	Content-Base header handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_base ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->content_base : NULL);
}

/*	Content-Location header handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_location ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->content_location : NULL);
}

/*	Message-ID, used for mail replies - kw
*/
PUBLIC CONST char * HTAnchor_messageID ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->message_id : NULL);
}

PUBLIC BOOL HTAnchor_setMessageID ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		messageid)
{
    if (!(me && messageid && *messageid)) {
	return FALSE;
    }
    StrAllocCopy(me->message_id, messageid);
    return TRUE;
}

/*	Subject, used for mail replies - kw
*/
PUBLIC CONST char * HTAnchor_subject ARGS1(
	HTParentAnchor *,	me)
{
    return( me ? me->subject : NULL);
}

PUBLIC BOOL HTAnchor_setSubject ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		subject)
{
    if (!(me && subject && *subject)) {
	return FALSE;
    }
    StrAllocCopy(me->subject, subject);
    return TRUE;
}

/*	Link me Anchor to another given one
**	-------------------------------------
*/
PUBLIC BOOL HTAnchor_link ARGS3(
	HTAnchor *,	source,
	HTAnchor *,	destination,
	HTLinkType *,	type)
{
    if (!(source && destination))
	return(NO);  /* Can't link to/from non-existing anchor */
    CTRACE((tfp, "Linking anchor %p to anchor %p\n", source, destination));
    if (!source->mainLink.dest) {
	source->mainLink.dest = destination;
	source->mainLink.type = type;
    } else {
	HTLink * newLink = (HTLink *)calloc (1, sizeof (HTLink));
	if (newLink == NULL)
	    outofmem(__FILE__, "HTAnchor_link");
	newLink->dest = destination;
	newLink->type = type;
	if (!source->links)
	    source->links = HTList_new();
	HTList_addObject (source->links, newLink);
    }
    if (!destination->parent->sources)
	destination->parent->sources = HTList_new();
    HTList_addObject (destination->parent->sources, source);
    return(YES);  /* Success */
}


/*	Manipulation of links
**	---------------------
*/
PUBLIC HTAnchor * HTAnchor_followMainLink ARGS1(
	HTAnchor *,	me)
{
    return( me->mainLink.dest);
}

PUBLIC HTAnchor * HTAnchor_followTypedLink ARGS2(
	HTAnchor *,	me,
	HTLinkType *,	type)
{
    if (me->mainLink.type == type)
	return( me->mainLink.dest);
    if (me->links) {
	HTList *links = me->links;
	HTLink *the_link;
	while (NULL != (the_link=(HTLink *)HTList_nextObject(links))) {
	    if (the_link->type == type) {
		return( the_link->dest);
	    }
	}
    }
    return(NULL);  /* No link of me type */
}


/*	Make main link
*/
PUBLIC BOOL HTAnchor_makeMainLink ARGS2(
	HTAnchor *,	me,
	HTLink *,	movingLink)
{
    /* Check that everything's OK */
    if (!(me && HTList_removeObject (me->links, movingLink))) {
	return(NO);  /* link not found or NULL anchor */
    } else {
	/* First push current main link onto top of links list */
	HTLink *newLink = (HTLink *)calloc (1, sizeof (HTLink));
	if (newLink == NULL)
	    outofmem(__FILE__, "HTAnchor_makeMainLink");
	memcpy((void *)newLink,
	       (CONST char *)&me->mainLink, sizeof (HTLink));
	HTList_addObject (me->links, newLink);

	/* Now make movingLink the new main link, and free it */
	memcpy((void *)&me->mainLink,
	       (CONST void *)movingLink, sizeof (HTLink));
	FREE(movingLink);
	return(YES);
    }
}


/*	Methods List
**	------------
*/
PUBLIC HTList * HTAnchor_methods ARGS1(
	HTParentAnchor *,	me)
{
    if (!me->methods) {
	me->methods = HTList_new();
    }
    return( me->methods);
}

/*	Protocol
**	--------
*/
PUBLIC void * HTAnchor_protocol ARGS1(
	HTParentAnchor *,	me)
{
    return( me->protocol);
}

PUBLIC void HTAnchor_setProtocol ARGS2(
	HTParentAnchor *,	me,
	void*,			protocol)
{
    me->protocol = protocol;
}

/*	Physical Address
**	----------------
*/
PUBLIC char * HTAnchor_physical ARGS1(
	HTParentAnchor *,	me)
{
    return( me->physical);
}

PUBLIC void HTAnchor_setPhysical ARGS2(
	HTParentAnchor *,	me,
	char *, 		physical)
{
    if (me) {
	StrAllocCopy(me->physical, physical);
    }
}

/*
**  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.
*/
PUBLIC LYUCcharset * HTAnchor_getUCInfoStage ARGS2(
	HTParentAnchor *,	me,
	int,			which_stage)
{
    if (me && !me->UCStages) {
	int i;
	int chndl = UCLYhndl_for_unspec;  /* always >= 0 */
	UCAnchorInfo * stages = (UCAnchorInfo*)calloc(1,
						      sizeof(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;
    }
    if (me) {
	return( &me->UCStages->s[which_stage].C);
    }
    return(NULL);
}

PUBLIC int HTAnchor_getUCLYhndl ARGS2(
	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);
}

PUBLIC LYUCcharset * HTAnchor_setUCInfoStage ARGS4(
	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) {
	    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));
	    }
	    else {
		p->UChndl = -1;
	    }
	    return(p);
	}
    }
    return(NULL);
}

PUBLIC LYUCcharset * HTAnchor_resetUCInfoStage ARGS4(
	HTParentAnchor *,	me,
	int,			LYhndl,
	int,			which_stage,
	int,			set_by)
{
    if (!me || !me->UCStages)
	return(NULL);
    me->UCStages->s[which_stage].lock = set_by;
    me->UCStages->s[which_stage].LYhndl = LYhndl;
    return( &me->UCStages->s[which_stage].C);
}

/*
**  A set_by of (-1) means use the lock value from the from_stage.
*/
PUBLIC LYUCcharset * HTAnchor_copyUCInfoStage ARGS4(
	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) {
	    me->UCStages->s[to_stage].lock = set_by;
	    me->UCStages->s[to_stage].LYhndl =
		me->UCStages->s[from_stage].LYhndl;
	    if (p_to != p_from)
		memcpy(p_to, p_from, sizeof(LYUCcharset));
	    return(p_to);
	}
    }
    return(NULL);
}
