From ae5d181b854d3ccb373b6bc01b4869e44ff4d87a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:37:15 +0200 Subject: Adding upstream version 2.9.0dev.12. Signed-off-by: Daniel Baumann --- WWW/Library/Implementation/HTAABrow.c | 1354 +++++++ WWW/Library/Implementation/HTAABrow.h | 142 + WWW/Library/Implementation/HTAAProt.c | 738 ++++ WWW/Library/Implementation/HTAAProt.h | 226 ++ WWW/Library/Implementation/HTAAUtil.c | 605 ++++ WWW/Library/Implementation/HTAAUtil.h | 318 ++ WWW/Library/Implementation/HTAccess.c | 1460 ++++++++ WWW/Library/Implementation/HTAccess.h | 268 ++ WWW/Library/Implementation/HTAnchor.c | 1460 ++++++++ WWW/Library/Implementation/HTAnchor.h | 412 +++ WWW/Library/Implementation/HTAssoc.c | 82 + WWW/Library/Implementation/HTAssoc.h | 35 + WWW/Library/Implementation/HTAtom.c | 107 + WWW/Library/Implementation/HTAtom.h | 53 + WWW/Library/Implementation/HTBTree.c | 680 ++++ WWW/Library/Implementation/HTBTree.h | 104 + WWW/Library/Implementation/HTCJK.h | 121 + WWW/Library/Implementation/HTChunk.c | 332 ++ WWW/Library/Implementation/HTChunk.h | 228 ++ WWW/Library/Implementation/HTDOS.c | 241 ++ WWW/Library/Implementation/HTDOS.h | 56 + WWW/Library/Implementation/HTFTP.c | 4177 ++++++++++++++++++++++ WWW/Library/Implementation/HTFTP.h | 70 + WWW/Library/Implementation/HTFWriter.h | 30 + WWW/Library/Implementation/HTFile.c | 3395 ++++++++++++++++++ WWW/Library/Implementation/HTFile.h | 368 ++ WWW/Library/Implementation/HTFinger.c | 418 +++ WWW/Library/Implementation/HTFinger.h | 30 + WWW/Library/Implementation/HTFormat.c | 2181 +++++++++++ WWW/Library/Implementation/HTFormat.h | 588 +++ WWW/Library/Implementation/HTGopher.c | 2071 +++++++++++ WWW/Library/Implementation/HTGopher.h | 29 + WWW/Library/Implementation/HTGroup.c | 758 ++++ WWW/Library/Implementation/HTGroup.h | 182 + WWW/Library/Implementation/HTInit.h | 34 + WWW/Library/Implementation/HTLex.c | 142 + WWW/Library/Implementation/HTLex.h | 64 + WWW/Library/Implementation/HTList.c | 402 +++ WWW/Library/Implementation/HTList.h | 142 + WWW/Library/Implementation/HTMIME.c | 2594 ++++++++++++++ WWW/Library/Implementation/HTMIME.h | 84 + WWW/Library/Implementation/HTMLDTD.c | 334 ++ WWW/Library/Implementation/HTMLDTD.h | 97 + WWW/Library/Implementation/HTMLGen.c | 738 ++++ WWW/Library/Implementation/HTMLGen.h | 26 + WWW/Library/Implementation/HTNews.c | 3147 ++++++++++++++++ WWW/Library/Implementation/HTNews.h | 60 + WWW/Library/Implementation/HTParse.c | 1383 +++++++ WWW/Library/Implementation/HTParse.h | 212 ++ WWW/Library/Implementation/HTPlain.c | 601 ++++ WWW/Library/Implementation/HTPlain.h | 21 + WWW/Library/Implementation/HTRules.c | 704 ++++ WWW/Library/Implementation/HTRules.h | 169 + WWW/Library/Implementation/HTStream.h | 69 + WWW/Library/Implementation/HTString.c | 1464 ++++++++ WWW/Library/Implementation/HTString.h | 167 + WWW/Library/Implementation/HTStyle.c | 378 ++ WWW/Library/Implementation/HTStyle.h | 241 ++ WWW/Library/Implementation/HTTCP.c | 2623 ++++++++++++++ WWW/Library/Implementation/HTTCP.h | 111 + WWW/Library/Implementation/HTTP.c | 2838 +++++++++++++++ WWW/Library/Implementation/HTTP.h | 47 + WWW/Library/Implementation/HTTelnet.c | 553 +++ WWW/Library/Implementation/HTTelnet.h | 28 + WWW/Library/Implementation/HTUU.c | 210 ++ WWW/Library/Implementation/HTUU.h | 36 + WWW/Library/Implementation/HTUtils.h | 847 +++++ WWW/Library/Implementation/HTVMSUtils.c | 1131 ++++++ WWW/Library/Implementation/HTVMSUtils.h | 101 + WWW/Library/Implementation/HTVMS_WaisProt.c | 2469 +++++++++++++ WWW/Library/Implementation/HTVMS_WaisProt.h | 425 +++ WWW/Library/Implementation/HTVMS_WaisUI.c | 2279 ++++++++++++ WWW/Library/Implementation/HTVMS_WaisUI.h | 664 ++++ WWW/Library/Implementation/HTWAIS.c | 1078 ++++++ WWW/Library/Implementation/HTWAIS.h | 43 + WWW/Library/Implementation/HTWSRC.c | 486 +++ WWW/Library/Implementation/HTWSRC.h | 43 + WWW/Library/Implementation/HText.h | 219 ++ WWW/Library/Implementation/HTioctl.h | 11 + WWW/Library/Implementation/LYLeaks.h | 309 ++ WWW/Library/Implementation/LYexit.h | 67 + WWW/Library/Implementation/SGML.c | 5159 +++++++++++++++++++++++++++ WWW/Library/Implementation/SGML.h | 287 ++ WWW/Library/Implementation/UCAux.h | 92 + WWW/Library/Implementation/UCDefs.h | 106 + WWW/Library/Implementation/UCMap.h | 114 + WWW/Library/Implementation/Version.make | 1 + WWW/Library/Implementation/dtd_util.c | 1722 +++++++++ WWW/Library/Implementation/hdr_HTMLDTD.h | 1000 ++++++ WWW/Library/Implementation/makefile.in | 384 ++ WWW/Library/Implementation/src0_HTMLDTD.h | 2478 +++++++++++++ WWW/Library/Implementation/src0_HTMLDTD.txt | 3901 ++++++++++++++++++++ WWW/Library/Implementation/src1_HTMLDTD.h | 2478 +++++++++++++ WWW/Library/Implementation/src1_HTMLDTD.txt | 3901 ++++++++++++++++++++ WWW/Library/Implementation/tidy_tls.h | 147 + WWW/Library/Implementation/www_tcp.h | 1017 ++++++ WWW/Library/Implementation/www_wait.h | 34 + WWW/Library/djgpp/CommonMakefile | 334 ++ WWW/Library/djgpp/makefile | 59 + WWW/Library/djgpp/makefile.sla | 40 + WWW/Library/vms/COPYING.LIB | 481 +++ WWW/Library/vms/descrip.mms | 206 ++ WWW/Library/vms/libmake.com | 233 ++ 103 files changed, 77384 insertions(+) create mode 100644 WWW/Library/Implementation/HTAABrow.c create mode 100644 WWW/Library/Implementation/HTAABrow.h create mode 100644 WWW/Library/Implementation/HTAAProt.c create mode 100644 WWW/Library/Implementation/HTAAProt.h create mode 100644 WWW/Library/Implementation/HTAAUtil.c create mode 100644 WWW/Library/Implementation/HTAAUtil.h create mode 100644 WWW/Library/Implementation/HTAccess.c create mode 100644 WWW/Library/Implementation/HTAccess.h create mode 100644 WWW/Library/Implementation/HTAnchor.c create mode 100644 WWW/Library/Implementation/HTAnchor.h create mode 100644 WWW/Library/Implementation/HTAssoc.c create mode 100644 WWW/Library/Implementation/HTAssoc.h create mode 100644 WWW/Library/Implementation/HTAtom.c create mode 100644 WWW/Library/Implementation/HTAtom.h create mode 100644 WWW/Library/Implementation/HTBTree.c create mode 100644 WWW/Library/Implementation/HTBTree.h create mode 100644 WWW/Library/Implementation/HTCJK.h create mode 100644 WWW/Library/Implementation/HTChunk.c create mode 100644 WWW/Library/Implementation/HTChunk.h create mode 100644 WWW/Library/Implementation/HTDOS.c create mode 100644 WWW/Library/Implementation/HTDOS.h create mode 100644 WWW/Library/Implementation/HTFTP.c create mode 100644 WWW/Library/Implementation/HTFTP.h create mode 100644 WWW/Library/Implementation/HTFWriter.h create mode 100644 WWW/Library/Implementation/HTFile.c create mode 100644 WWW/Library/Implementation/HTFile.h create mode 100644 WWW/Library/Implementation/HTFinger.c create mode 100644 WWW/Library/Implementation/HTFinger.h create mode 100644 WWW/Library/Implementation/HTFormat.c create mode 100644 WWW/Library/Implementation/HTFormat.h create mode 100644 WWW/Library/Implementation/HTGopher.c create mode 100644 WWW/Library/Implementation/HTGopher.h create mode 100644 WWW/Library/Implementation/HTGroup.c create mode 100644 WWW/Library/Implementation/HTGroup.h create mode 100644 WWW/Library/Implementation/HTInit.h create mode 100644 WWW/Library/Implementation/HTLex.c create mode 100644 WWW/Library/Implementation/HTLex.h create mode 100644 WWW/Library/Implementation/HTList.c create mode 100644 WWW/Library/Implementation/HTList.h create mode 100644 WWW/Library/Implementation/HTMIME.c create mode 100644 WWW/Library/Implementation/HTMIME.h create mode 100644 WWW/Library/Implementation/HTMLDTD.c create mode 100644 WWW/Library/Implementation/HTMLDTD.h create mode 100644 WWW/Library/Implementation/HTMLGen.c create mode 100644 WWW/Library/Implementation/HTMLGen.h create mode 100644 WWW/Library/Implementation/HTNews.c create mode 100644 WWW/Library/Implementation/HTNews.h create mode 100644 WWW/Library/Implementation/HTParse.c create mode 100644 WWW/Library/Implementation/HTParse.h create mode 100644 WWW/Library/Implementation/HTPlain.c create mode 100644 WWW/Library/Implementation/HTPlain.h create mode 100644 WWW/Library/Implementation/HTRules.c create mode 100644 WWW/Library/Implementation/HTRules.h create mode 100644 WWW/Library/Implementation/HTStream.h create mode 100644 WWW/Library/Implementation/HTString.c create mode 100644 WWW/Library/Implementation/HTString.h create mode 100644 WWW/Library/Implementation/HTStyle.c create mode 100644 WWW/Library/Implementation/HTStyle.h create mode 100644 WWW/Library/Implementation/HTTCP.c create mode 100644 WWW/Library/Implementation/HTTCP.h create mode 100644 WWW/Library/Implementation/HTTP.c create mode 100644 WWW/Library/Implementation/HTTP.h create mode 100644 WWW/Library/Implementation/HTTelnet.c create mode 100644 WWW/Library/Implementation/HTTelnet.h create mode 100644 WWW/Library/Implementation/HTUU.c create mode 100644 WWW/Library/Implementation/HTUU.h create mode 100644 WWW/Library/Implementation/HTUtils.h create mode 100644 WWW/Library/Implementation/HTVMSUtils.c create mode 100644 WWW/Library/Implementation/HTVMSUtils.h create mode 100644 WWW/Library/Implementation/HTVMS_WaisProt.c create mode 100644 WWW/Library/Implementation/HTVMS_WaisProt.h create mode 100644 WWW/Library/Implementation/HTVMS_WaisUI.c create mode 100644 WWW/Library/Implementation/HTVMS_WaisUI.h create mode 100644 WWW/Library/Implementation/HTWAIS.c create mode 100644 WWW/Library/Implementation/HTWAIS.h create mode 100644 WWW/Library/Implementation/HTWSRC.c create mode 100644 WWW/Library/Implementation/HTWSRC.h create mode 100644 WWW/Library/Implementation/HText.h create mode 100644 WWW/Library/Implementation/HTioctl.h create mode 100644 WWW/Library/Implementation/LYLeaks.h create mode 100644 WWW/Library/Implementation/LYexit.h create mode 100644 WWW/Library/Implementation/SGML.c create mode 100644 WWW/Library/Implementation/SGML.h create mode 100644 WWW/Library/Implementation/UCAux.h create mode 100644 WWW/Library/Implementation/UCDefs.h create mode 100644 WWW/Library/Implementation/UCMap.h create mode 100644 WWW/Library/Implementation/Version.make create mode 100644 WWW/Library/Implementation/dtd_util.c create mode 100644 WWW/Library/Implementation/hdr_HTMLDTD.h create mode 100644 WWW/Library/Implementation/makefile.in create mode 100644 WWW/Library/Implementation/src0_HTMLDTD.h create mode 100644 WWW/Library/Implementation/src0_HTMLDTD.txt create mode 100644 WWW/Library/Implementation/src1_HTMLDTD.h create mode 100644 WWW/Library/Implementation/src1_HTMLDTD.txt create mode 100644 WWW/Library/Implementation/tidy_tls.h create mode 100644 WWW/Library/Implementation/www_tcp.h create mode 100644 WWW/Library/Implementation/www_wait.h create mode 100644 WWW/Library/djgpp/CommonMakefile create mode 100644 WWW/Library/djgpp/makefile create mode 100644 WWW/Library/djgpp/makefile.sla create mode 100644 WWW/Library/vms/COPYING.LIB create mode 100644 WWW/Library/vms/descrip.mms create mode 100644 WWW/Library/vms/libmake.com (limited to 'WWW/Library') diff --git a/WWW/Library/Implementation/HTAABrow.c b/WWW/Library/Implementation/HTAABrow.c new file mode 100644 index 0000000..c963acd --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.c @@ -0,0 +1,1354 @@ +/* + * $LynxId: HTAABrow.c,v 1.43 2018/05/11 22:54:19 tom Exp $ + * + * MODULE HTAABrow.c + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + * + * Contains the code for keeping track on server hostnames, + * port numbers, scheme names, usernames, passwords + * (and servers' public keys). + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * Oct 17 AL Made corrections suggested by marca: + * Added if (!realm->username) return NULL; + * Changed some ""s to NULLs. + * Now doing calloc() to init uuencode source; + * otherwise HTUU_encode() reads uninitialized memory + * every now and then (not a real bug but not pretty). + * Corrected the formula for uuencode destination size. + * + * 28 Apr 1997 AJL Do Proxy Authorisation. + * + * BUGS: + * + * + */ + +#include +#include +#include /* URL parsing function */ +#include /* HTList object */ +#include /* HTConfirm(), HTPrompt() */ +#include /* AA common to both sides */ +#include /* Assoc list */ +#include /* Are we using an HTTP gateway? */ +#include /* Implemented here */ +#include /* Uuencoding and uudecoding */ + +#include + +/* + * Local datatype definitions + * + * HTAAServer contains all the information about one server. + */ +typedef struct { + + char *hostname; /* Host's name */ + int portnumber; /* Port number */ + BOOL IsProxy; /* Is it a proxy? */ + HTList *setups; /* List of protection setups + on this server; i.e., valid + authentication schemes and + templates when to use them. + This is actually a list of + HTAASetup objects. */ + HTList *realms; /* Information about passwords */ +} HTAAServer; + +/* + * HTAASetup contains information about one server's one + * protected tree of documents. + */ +typedef struct { + HTAAServer *server; /* Which server serves this tree */ + char *ctemplate; /* Template for this tree */ + HTList *valid_schemes; /* Valid authentic.schemes */ + HTAssocList **scheme_specifics; /* Scheme specific params */ + BOOL retry; /* Failed last time -- reprompt (or whatever) */ +} HTAASetup; + +/* + * Information about usernames and passwords in + * Basic and Pubkey authentication schemes; + */ +typedef struct { + char *realmname; /* Password domain name */ + char *username; /* Username in that domain */ + char *password; /* Corresponding password */ +} HTAARealm; + +/* + * To free off all globals. - FM + */ +static void free_HTAAGlobals(void); +static BOOL free_HTAAGlobalsSet = FALSE; +static char *HTAA_composeAuthResult = NULL; +static char *compose_auth_stringResult = NULL; /* Uuencoded presentation */ + +/* + * Module-wide global variables + */ +static HTList *server_table = NULL; /* Browser's info about servers */ +static char *secret_key = NULL; /* Browser's latest secret key */ +static HTAASetup *current_setup = NULL; /* The server setup we are currently */ + + /* talking to */ +static char *current_hostname = NULL; /* The server's name and portnumber */ +static int current_portnumber = 80; /* where we are currently trying to */ + + /* connect. */ +static char *current_docname = NULL; /* The document's name we are */ + + /* trying to access. */ +static char *HTAAForwardAuth = NULL; /* Authorization: line to forward */ + + /* (used by gateway httpds) */ +static HTAASetup *proxy_setup = NULL; /* Same as above, but for Proxy -AJL */ +static char *proxy_hostname = NULL; +static char *proxy_docname = NULL; +static int proxy_portnumber = 80; + +/*** HTAAForwardAuth for enabling gateway-httpds to forward Authorization ***/ + +void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics) +{ + size_t len = (20 + + (scheme_name ? strlen(scheme_name) : 0) + + (scheme_specifics ? strlen(scheme_specifics) : 0)); + + FREE(HTAAForwardAuth); + if ((HTAAForwardAuth = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAAForwardAuth_set"); + + strcpy(HTAAForwardAuth, "Authorization: "); + if (scheme_name) { + strcat(HTAAForwardAuth, scheme_name); + strcat(HTAAForwardAuth, " "); + if (scheme_specifics) { + strcat(HTAAForwardAuth, scheme_specifics); + } + } +} + +void HTAAForwardAuth_reset(void) +{ + FREE(HTAAForwardAuth); +} + +/**************************** HTAAServer ***********************************/ + +static void HTAASetup_delete(HTAASetup * killme); /* Forward */ + +/* static HTAAServer_new() + * ALLOCATE A NEW NODE TO HOLD SERVER INFO + * AND ADD IT TO THE LIST OF SERVERS + * ON ENTRY: + * hostname is the name of the host that the server + * is running in. + * portnumber is the portnumber which the server listens. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns the newly-allocated node with all the strings + * duplicated. + * Strings will be automatically freed by + * the function HTAAServer_delete(), which also + * frees the node itself. + */ +static HTAAServer *HTAAServer_new(const char *hostname, + int portnumber, + int IsProxy) +{ + HTAAServer *server; + + if ((server = typecalloc(HTAAServer)) == 0) + outofmem(__FILE__, "HTAAServer_new"); + + server->hostname = NULL; + server->portnumber = (portnumber > 0 ? portnumber : 80); + server->IsProxy = (BOOLEAN) IsProxy; + server->setups = HTList_new(); + server->realms = HTList_new(); + + if (hostname) + StrAllocCopy(server->hostname, hostname); + + if (!server_table) + server_table = HTList_new(); + + HTList_addObject(server_table, (void *) server); + + return server; +} + +/* static HTAAServer_delete() + * + * DELETE THE ENTRY FOR THE SERVER FROM THE HOST TABLE, + * AND FREE THE MEMORY USED BY IT. + * + * ON ENTRY: + * killme points to the HTAAServer to be freed. + * + * ON EXIT: + * returns nothing. + */ +static void HTAAServer_delete(HTAAServer *killme) +{ + int n, i; + HTAASetup *setup; + HTAARealm *realm; + HTList *cur; + + if (killme) { + if (killme->setups != NULL) { + n = HTList_count(killme->setups); + for (i = (n - 1); i >= 0; i--) { + if ((setup = (HTAASetup *) HTList_objectAt(killme->setups, + i)) != NULL) { + HTAASetup_delete(setup); + setup = NULL; + } + } + HTList_delete(killme->setups); + killme->setups = NULL; + } + + cur = killme->realms; + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + FREE(realm->realmname); + FREE(realm->username); + FREE(realm->password); + FREE(realm); + } + HTList_delete(killme->realms); + killme->realms = NULL; + + FREE(killme->hostname); + + HTList_removeObject(server_table, (void *) killme); + FREE(killme); + } +} + +/* static HTAAServer_lookup() + * LOOK UP SERVER BY HOSTNAME AND PORTNUMBER + * ON ENTRY: + * hostname obvious. + * portnumber if non-positive defaults to 80. + * IsProxy should be TRUE if this is a proxy. + * + * Looks up the server in the module-global server_table. + * + * ON EXIT: + * returns pointer to a HTAAServer structure + * representing the looked-up server. + * NULL, if not found. + */ +static HTAAServer *HTAAServer_lookup(const char *hostname, + int portnumber, + int IsProxy) +{ + if (hostname) { + HTList *cur = server_table; + HTAAServer *server; + + if (portnumber <= 0) + portnumber = 80; + + while (NULL != (server = (HTAAServer *) HTList_nextObject(cur))) { + if (server->portnumber == portnumber && + 0 == strcmp(server->hostname, hostname) && + server->IsProxy == IsProxy) + return server; + } + } + return NULL; /* NULL parameter, or not found */ +} + +/*************************** HTAASetup *******************************/ + +/* static HTAASetup_lookup() + * FIGURE OUT WHICH AUTHENTICATION SETUP THE SERVER + * IS USING FOR A GIVEN FILE ON A GIVEN HOST AND PORT + * + * ON ENTRY: + * hostname is the name of the server host machine. + * portnumber is the port that the server is running in. + * docname is the (URL-)pathname of the document we + * are trying to access. + * IsProxy should be TRUE if this is a proxy. + * + * This function goes through the information known about + * all the setups of the server, and finds out if the given + * filename resides in one of the protected directories. + * + * ON EXIT: + * returns NULL if no match. + * Otherwise, a HTAASetup structure representing + * the protected server setup on the corresponding + * document tree. + * + */ +static HTAASetup *HTAASetup_lookup(const char *hostname, + int portnumber, + const char *docname, + int IsProxy) +{ + HTAAServer *server; + HTAASetup *setup; + + if (portnumber <= 0) + portnumber = 80; + + if (hostname && docname && *hostname && *docname && + NULL != (server = HTAAServer_lookup(hostname, + portnumber, + IsProxy))) { + + HTList *cur = server->setups; + + CTRACE((tfp, "%s %s (%s:%d:%s)\n", + "HTAASetup_lookup: resolving setup for", + (IsProxy ? "proxy" : "server"), + hostname, portnumber, docname)); + + while (NULL != (setup = (HTAASetup *) HTList_nextObject(cur))) { + if (HTAA_templateMatch(setup->ctemplate, docname)) { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "matched template", setup->ctemplate)); + return setup; + } else { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "did NOT match template", setup->ctemplate)); + } + } /* while setups remain */ + } + /* if valid parameters and server found */ + CTRACE((tfp, "%s `%s' %s\n", + "HTAASetup_lookup: No template matched", + NONNULL(docname), + "(so probably not protected)")); + + return NULL; /* NULL in parameters, or not found */ +} + +/* static HTAASetup_new() + * CREATE A NEW SETUP NODE + * ON ENTRY: + * server is a pointer to a HTAAServer structure + * to which this setup belongs. + * ctemplate documents matching this template + * are protected according to this setup. + * valid_schemes a list containing all valid authentication + * schemes for this setup. + * If NULL, all schemes are disallowed. + * scheme_specifics is an array of assoc lists, which + * contain scheme specific parameters given + * by server in Authenticate: fields. + * If NULL, all scheme specifics are + * set to NULL. + * ON EXIT: + * returns a new HTAASetup node, and also adds it as + * part of the HTAAServer given as parameter. + */ +static HTAASetup *HTAASetup_new(HTAAServer *server, char *ctemplate, + HTList *valid_schemes, + HTAssocList **scheme_specifics) +{ + HTAASetup *setup; + + if (!server || isEmpty(ctemplate)) + return NULL; + + if ((setup = typecalloc(HTAASetup)) == 0) + outofmem(__FILE__, "HTAASetup_new"); + + setup->retry = NO; + setup->server = server; + setup->ctemplate = NULL; + if (ctemplate) + StrAllocCopy(setup->ctemplate, ctemplate); + setup->valid_schemes = valid_schemes; + setup->scheme_specifics = scheme_specifics; + + HTList_addObject(server->setups, (void *) setup); + + return setup; +} + +/* static HTAASetup_delete() + * FREE A HTAASetup STRUCTURE + * ON ENTRY: + * killme is a pointer to the structure to free(). + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_delete(HTAASetup * killme) +{ + int scheme; + + if (killme) { + FREE(killme->ctemplate); + if (killme->valid_schemes) { + HTList_delete(killme->valid_schemes); + killme->valid_schemes = NULL; + } + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) + if (killme->scheme_specifics[scheme]) + HTAssocList_delete(killme->scheme_specifics[scheme]); + FREE(killme->scheme_specifics); + FREE(killme); + } +} + +/* static HTAASetup_updateSpecifics() + * COPY SCHEME SPECIFIC PARAMETERS + * TO HTAASetup STRUCTURE + * ON ENTRY: + * setup destination setup structure. + * specifics string array containing scheme + * specific parameters for each scheme. + * If NULL, all the scheme specific + * parameters are set to NULL. + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_updateSpecifics(HTAASetup * setup, HTAssocList **specifics) +{ + int scheme; + + if (setup) { + if (setup->scheme_specifics) { + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) { + if (setup->scheme_specifics[scheme]) + HTAssocList_delete(setup->scheme_specifics[scheme]); + } + FREE(setup->scheme_specifics); + } + setup->scheme_specifics = specifics; + } +} + +/*************************** HTAARealm **********************************/ + +/* static HTAARealm_lookup() + * LOOKUP HTAARealm STRUCTURE BY REALM NAME + * ON ENTRY: + * realm_table a list of realm objects. + * realmname is the name of realm to look for. + * + * ON EXIT: + * returns the realm. NULL, if not found. + */ +static HTAARealm *HTAARealm_lookup(HTList *realm_table, + const char *realmname) +{ + if (realm_table && realmname) { + HTList *cur = realm_table; + HTAARealm *realm; + + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + if (0 == strcmp(realm->realmname, realmname)) + return realm; + } + } + return NULL; /* No table, NULL param, or not found */ +} + +/* static HTAARealm_new() + * CREATE A NODE CONTAINING USERNAME AND + * PASSWORD USED FOR THE GIVEN REALM. + * IF REALM ALREADY EXISTS, CHANGE + * USERNAME/PASSWORD. + * ON ENTRY: + * realm_table a list of realms to where to add + * the new one, too. + * realmname is the name of the password domain. + * username and + * password are what you can expect them to be. + * + * ON EXIT: + * returns the created realm. + */ +static HTAARealm *HTAARealm_new(HTList *realm_table, + const char *realmname, + const char *username, + const char *password) +{ + HTAARealm *realm; + + realm = HTAARealm_lookup(realm_table, realmname); + + if (!realm) { + if ((realm = typecalloc(HTAARealm)) == 0) + outofmem(__FILE__, "HTAARealm_new"); + + realm->realmname = NULL; + realm->username = NULL; + realm->password = NULL; + StrAllocCopy(realm->realmname, realmname); + if (realm_table) + HTList_addObject(realm_table, (void *) realm); + } + if (username) + StrAllocCopy(realm->username, username); + if (password) + StrAllocCopy(realm->password, password); + + return realm; +} + +BOOL HTAA_HaveUserinfo(const char *hostname) +{ + int gen_delims = 0; + BOOL result = FALSE; + char *my_info = NULL; + + if (StrAllocCopy(my_info, hostname) != NULL) { + char *at_sign = HTSkipToAt(my_info, &gen_delims); + + free(my_info); + if (at_sign != NULL && gen_delims == 0) + result = TRUE; + } + return result; +} + +/* + * If there is userinfo in the hostname string, update the realm to use that + * information. The command-line "-auth" option will override this. + */ +static void fill_in_userinfo(HTAARealm *realm, const char *hostname) +{ + int gen_delims = 0; + char *my_info = NULL; + char *at_sign = HTSkipToAt(StrAllocCopy(my_info, hostname), &gen_delims); + + if (at_sign != NULL && gen_delims == 0) { + char *colon; + + *at_sign = '\0'; + if ((colon = StrChr(my_info, ':')) != 0) { + *colon++ = '\0'; + } + if (non_empty(my_info)) { + char *msg; + BOOL prior = non_empty(realm->username); + + if (prior && strcmp(realm->username, my_info)) { + msg = 0; + HTSprintf0(&msg, + gettext("username for realm %s changed from %s to %s"), + realm->realmname, + realm->username, + my_info); + HTAlert(msg); + free(msg); + FREE(realm->username); + StrAllocCopy(realm->username, my_info); + } else if (!prior) { + StrAllocCopy(realm->username, my_info); + } + if (non_empty(colon)) { + prior = non_empty(realm->password); + if (prior && strcmp(realm->password, colon)) { + msg = 0; + HTSprintf0(&msg, + gettext("password for realm %s user %s changed"), + realm->realmname, + realm->username); + HTAlert(msg); + free(msg); + FREE(realm->password); + StrAllocCopy(realm->password, colon); + } else if (!prior) { + StrAllocCopy(realm->password, colon); + } + } + } + } + free(my_info); +} + +/***************** Basic and Pubkey Authentication ************************/ + +/* static compose_auth_string() + * + * COMPOSE Basic OR Pubkey AUTHENTICATION STRING; + * PROMPTS FOR USERNAME AND PASSWORD IF NEEDED + * + * ON ENTRY: + * hostname may include user- and password information + * scheme is either HTAA_BASIC or HTAA_PUBKEY. + * setup is the current server setup. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns a newly composed authorization string, + * (with, of course, a newly generated secret + * key and fresh timestamp, if Pubkey-scheme + * is being used). + * NULL, if something fails. + * NOTE: + * Like throughout the entire AA package, no string or structure + * returned by AA package needs to (or should) be freed. + * + */ +static char *compose_auth_string(const char *hostname, + HTAAScheme scheme, + HTAASetup * setup, + int IsProxy) +{ + char *cleartext = NULL; /* Cleartext presentation */ + char *ciphertext = NULL; /* Encrypted presentation */ + size_t len; + char *msg = NULL; + char *username = NULL; + char *password = NULL; + char *realmname = NULL; + char *theHost = NULL; + char *proxiedHost = NULL; + char *thePort = NULL; + HTAARealm *realm; + const char *i_net_addr = "0.0.0.0"; /* Change... @@@@ */ + const char *timestamp = "42"; /* ... these @@@@ */ + + FREE(compose_auth_stringResult); /* From previous call */ + + if ((scheme != HTAA_BASIC && scheme != HTAA_PUBKEY) || + !(setup && + setup->scheme_specifics && + setup->scheme_specifics[scheme] && + setup->server && + setup->server->realms)) + return NULL; + + realmname = HTAssocList_lookup(setup->scheme_specifics[scheme], "realm"); + if (!realmname) + return NULL; + + realm = HTAARealm_lookup(setup->server->realms, realmname); + setup->retry |= HTAA_HaveUserinfo(hostname); + + if (!(realm && + non_empty(realm->username) && + non_empty(realm->password)) || setup->retry) { + if (!realm) { + CTRACE((tfp, "%s `%s' %s\n", + "compose_auth_string: realm:", realmname, + "not found -- creating")); + realm = HTAARealm_new(setup->server->realms, + realmname, NULL, NULL); + } + fill_in_userinfo(realm, hostname); + /* + * The template should be either the '*' global for everything on the + * server (always true for proxy authorization setups), or a path for + * the start of a protected limb, with no host field, but we'll check + * for a host anyway in case a WWW-Protection-Template header set an + * absolute URL instead of a path. If we do get a host from this, it + * will include the port. - FM + */ + if ((!IsProxy) && using_proxy && setup->ctemplate) { + proxiedHost = HTParse(setup->ctemplate, "", PARSE_HOST); + if (proxiedHost && *proxiedHost != '\0') { + theHost = proxiedHost; + } + } + /* + * If we didn't get a host field from the template, set up the host + * name and port from the setup->server elements. - FM + */ + if (!theHost) + theHost = setup->server->hostname; + if (setup->server->portnumber > 0 && + setup->server->portnumber != 80) { + HTSprintf0(&thePort, ":%d", setup->server->portnumber); + } + + HTSprintf0(&msg, gettext("Username for '%s' at %s '%s%s':"), + realm->realmname, + (IsProxy ? "proxy" : "server"), + (theHost ? theHost : "??"), + NonNull(thePort)); + FREE(proxiedHost); + FREE(thePort); + if (non_empty(realm->username)) { + StrAllocCopy(username, realm->username); + } + if (non_empty(realm->password)) { + StrAllocCopy(password, realm->password); + } + HTPromptUsernameAndPassword(msg, &username, &password, IsProxy); + + FREE(msg); + FREE(realm->username); + FREE(realm->password); + + realm->username = username; + realm->password = password; + + if (!realm->username || !realm->password) { + /* + * Signals to retry. - FM + */ + return NULL; + } else if (*realm->username == '\0') { + /* + * Signals to abort. - FM + */ + StrAllocCopy(compose_auth_stringResult, ""); + return compose_auth_stringResult; + } + } + + len = (strlen(NonNull(realm->username)) + + strlen(NonNull(realm->password)) + 3); + + if (scheme == HTAA_PUBKEY) { +#ifdef PUBKEY + /* Generate new secret key */ + StrAllocCopy(secret_key, HTAA_generateRandomKey()); +#endif /* PUBKEY */ + /* Room for secret key, timestamp and inet address */ + len += strlen(NonNull(secret_key)) + 30; + } else { + FREE(secret_key); + } + + if ((cleartext = typecallocn(char, len)) == 0) + outofmem(__FILE__, "compose_auth_string"); + + if (realm->username) + strcpy(cleartext, realm->username); + else + *cleartext = '\0'; + + strcat(cleartext, ":"); + + if (realm->password) + strcat(cleartext, realm->password); + + if (scheme == HTAA_PUBKEY) { + strcat(cleartext, ":"); + strcat(cleartext, i_net_addr); + strcat(cleartext, ":"); + strcat(cleartext, timestamp); + strcat(cleartext, ":"); + if (secret_key) + strcat(cleartext, secret_key); + + if (!((ciphertext = typecallocn(char, 2 * len)) && + (compose_auth_stringResult = typecallocn(char, 3 * len)))) + outofmem(__FILE__, "compose_auth_string"); + +#ifdef PUBKEY + HTPK_encrypt(cleartext, ciphertext, server->public_key); + HTUU_encode((unsigned char *) ciphertext, strlen(ciphertext), + compose_auth_stringResult); +#endif /* PUBKEY */ + FREE(cleartext); + FREE(ciphertext); + } else { /* scheme == HTAA_BASIC */ + if (!(compose_auth_stringResult = + typecallocn(char, (4 * ((len + 2) / 3)) + 1))) + outofmem(__FILE__, "compose_auth_string"); + + HTUU_encode((unsigned char *) cleartext, strlen(cleartext), + compose_auth_stringResult); + FREE(cleartext); + } + return compose_auth_stringResult; +} + +/* BROWSER static HTAA_selectScheme() + * SELECT THE AUTHENTICATION SCHEME TO USE + * ON ENTRY: + * setup is the server setup structure which can + * be used to make the decision about the + * used scheme. + * + * When new authentication methods are added to library + * this function makes the decision about which one to + * use at a given time. This can be done by inspecting + * environment variables etc. + * + * Currently only searches for the first valid scheme, + * and if nothing found suggests Basic scheme; + * + * ON EXIT: + * returns the authentication scheme to use. + */ +static HTAAScheme HTAA_selectScheme(HTAASetup * setup) +{ + int scheme; + + if (setup && setup->valid_schemes) { + for (scheme = HTAA_BASIC; scheme < HTAA_MAX_SCHEMES; scheme++) { + void *object = (void *) (intptr_t) scheme; + + if (-1 < HTList_indexOf(setup->valid_schemes, object)) + return (HTAAScheme) scheme; + } + } + return HTAA_BASIC; +} + +/* + * Purpose: Free off all module globals. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 06-19-96 created - FM + */ +static void free_HTAAGlobals(void) +{ + HTAAServer *server; + int n, i; + + if (server_table != NULL) { + n = HTList_count(server_table); + for (i = (n - 1); i >= 0; i--) { + if ((server = (HTAAServer *) HTList_objectAt(server_table, + i)) != NULL) { + HTAAServer_delete(server); + server = NULL; + } + } + HTList_delete(server_table); + server_table = NULL; + } + + HTAAForwardAuth_reset(); + FREE(HTAA_composeAuthResult); + FREE(current_hostname); + FREE(current_docname); + FREE(proxy_hostname); + FREE(proxy_docname); + FREE(compose_auth_stringResult); + FREE(secret_key); +} + +/* BROWSER PUBLIC HTAA_composeAuth() + * + * SELECT THE AUTHENTICATION SCHEME AND + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE + * IF WE ALREADY KNOW THAT THE HOST REQUIRES AUTHENTICATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: Basic username:password" + * + * As usual, this string is automatically freed. + */ +char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy) +{ + char *auth_string; + BOOL retry; + HTAAScheme scheme; + size_t len; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Make gateway httpds pass authorization field as it was received. (This + * still doesn't really work because Authenticate: headers from remote + * server are not forwarded to client yet so it cannot really know that it + * should send authorization; I will not implement it yet because I feel we + * will soon change radically the way requests are represented to allow + * multithreading on server-side. Life is hard.) + */ + if (HTAAForwardAuth) { + CTRACE((tfp, "HTAA_composeAuth: %s\n", + "Forwarding received authorization")); + StrAllocCopy(HTAA_composeAuthResult, HTAAForwardAuth); + HTAAForwardAuth_reset(); /* Just a precaution */ + return HTAA_composeAuthResult; + } + + FREE(HTAA_composeAuthResult); /* From previous call */ + + if (IsProxy) { + /* + * Proxy Authorization required. - AJL + */ + + CTRACE((tfp, "Composing Proxy Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (proxy_portnumber != portnumber || + !proxy_hostname || !proxy_docname || + !hostname || !docname || + 0 != strcmp(proxy_hostname, hostname) || + 0 != strcmp(proxy_docname, docname)) { + + retry = NO; + + proxy_portnumber = portnumber; + + if (hostname) + StrAllocCopy(proxy_hostname, hostname); + else + FREE(proxy_hostname); + + if (docname) + StrAllocCopy(proxy_docname, docname); + else + FREE(proxy_docname); + } else { + retry = YES; + } + + if (!proxy_setup || !retry) + proxy_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!proxy_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(proxy_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, proxy_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = NULL; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose proxy authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + proxy_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 26; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Proxy-Authorization: "); + + } else { + /* + * Normal WWW authorization. + */ + CTRACE((tfp, "Composing Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (current_portnumber != portnumber || + !current_hostname || !current_docname || + !hostname || !docname || + 0 != strcmp(current_hostname, hostname) || + 0 != strcmp(current_docname, docname)) { + + retry = NO; + + current_portnumber = portnumber; + + if (hostname) + StrAllocCopy(current_hostname, hostname); + else + FREE(current_hostname); + + if (docname) + StrAllocCopy(current_docname, docname); + else + FREE(current_docname); + } else { + retry = YES; + } + + if (!current_setup || !retry) + current_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!current_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(current_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, current_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = 0; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + current_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 20; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Authorization: "); + } + + strcat(HTAA_composeAuthResult, HTAAScheme_name(scheme)); + strcat(HTAA_composeAuthResult, " "); + strcat(HTAA_composeAuthResult, auth_string); + return HTAA_composeAuthResult; +} + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * IsProxy should be TRUE if this is a proxy. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ +BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy) +{ + HTAAScheme scheme; + char *line = NULL; + int num_schemes = 0; + HTList *valid_schemes = HTList_new(); + HTAssocList **scheme_specifics = NULL; + char *ctemplate = NULL; + char *temp = NULL; + BOOL result = NO; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Read server reply header lines + */ + CTRACE((tfp, "Server reply header lines:\n")); + + HTAA_setupReader(start_of_headers, length, soc); + while (NULL != (line = HTAA_getUnfoldedLine()) && *line != '\0') { + CTRACE((tfp, "%s\n", line)); + + if (StrChr(line, ':')) { /* Valid header line */ + + char *p = line; + char *fieldname = HTNextField(&p); + char *arg1 = HTNextField(&p); + char *args = p; + + if ((IsProxy && + 0 == strcasecomp(fieldname, "Proxy-Authenticate:")) || + (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Authenticate:"))) { + if (isEmpty(arg1) || isEmpty(args)) { + HTSprintf0(&temp, gettext("Invalid header '%s%s%s%s%s'"), line, + (non_empty(arg1) ? " " : ""), + NonNull(arg1), + (non_empty(args) ? " " : ""), + NonNull(args)); + HTAlert(temp); + FREE(temp); + } else if (HTAA_UNKNOWN != (scheme = HTAAScheme_enum(arg1))) { + HTList_addObject(valid_schemes, (void *) scheme); + if (!scheme_specifics) { + int i; + + scheme_specifics = + typecallocn(HTAssocList *, HTAA_MAX_SCHEMES); + + if (!scheme_specifics) + outofmem(__FILE__, "HTAA_shouldRetryWithAuth"); + + for (i = 0; i < HTAA_MAX_SCHEMES; i++) + scheme_specifics[i] = NULL; + } + scheme_specifics[scheme] = HTAA_parseArgList(args); + num_schemes++; + } else { + CTRACE((tfp, "Unknown scheme `%s' %s\n", + NONNULL(arg1), + (IsProxy ? + "in Proxy-Authenticate: field" : + "in WWW-Authenticate: field"))); + } + } + + else if (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Protection-Template:")) { + CTRACE((tfp, "Protection template set to `%s'\n", arg1)); + StrAllocCopy(ctemplate, arg1); + } + + } else { + CTRACE((tfp, "Invalid header line `%s' ignored\n", line)); + } + + FREE(line); + } /* while header lines remain */ + FREE(line); + + /* + * So should we retry with authorization? + */ + if (IsProxy) { + if (num_schemes == 0) { + /* + * No proxy authorization valid + */ + proxy_setup = NULL; + result = NO; + } + /* + * Doing it for proxy. -AJL + */ + else if (proxy_setup && proxy_setup->server) { + /* + * We have already tried with proxy authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(proxy_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + proxy_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + HTList_delete(valid_schemes); + proxy_setup->retry = YES; + result = YES; + } + } else { + /* + * proxy_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents + * than we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(proxy_hostname, + proxy_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(proxy_hostname, + proxy_portnumber, + IsProxy); + } + if (!ctemplate) /* Proxy matches everything -AJL */ + StrAllocCopy(ctemplate, "*"); + proxy_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Proxy authorization required -- retrying")); + result = YES; + } + } + /* + * Normal WWW authorization. + */ + else if (num_schemes == 0) { + /* + * No authorization valid. + */ + current_setup = NULL; + result = NO; + } else if (current_setup && current_setup->server) { + /* + * So we have already tried with WWW authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(current_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + current_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + current_setup->retry = YES; + result = YES; + } + } else { + /* + * current_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents than + * we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(current_hostname, + current_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(current_hostname, + current_portnumber, + IsProxy); + } + if (!ctemplate) + ctemplate = HTAA_makeProtectionTemplate(current_docname); + current_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Access without authorization denied -- retrying")); + result = YES; + } + + if (result == NO) { + HTList_delete(valid_schemes); + } + return result; +} + +/* + * This function clears all authorization information by + * invoking the free_HTAAGlobals() function, which normally + * is invoked at exit. It allows a browser command to do + * this at any time, for example, if the user is leaving + * the terminal for a period of time, but does not want + * to end the current session. - FM + */ +void HTClearHTTPAuthInfo(void) +{ + /* + * Need code to check cached documents against the protection templates, + * and do something to ensure that any protected documents no longer can be + * accessed without a new retrieval. - FM + */ + + /* + * Now free all of the authorization info, and reset the + * free_HTAAGlobalsSet flag. - FM + */ + free_HTAAGlobals(); + free_HTAAGlobalsSet = FALSE; +} diff --git a/WWW/Library/Implementation/HTAABrow.h b/WWW/Library/Implementation/HTAABrow.h new file mode 100644 index 0000000..f151deb --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.h @@ -0,0 +1,142 @@ +/* + * $LynxId: HTAABrow.h,v 1.17 2016/11/24 23:32:22 tom Exp $ + * + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + + This module is the browser side interface to Access Authorization (AA) package. It + contains code only for browser. + + Important to know about memory allocation: + + Routines in this module use dynamic allocation, but free automatically all the memory + reserved by them. + + Therefore the caller never has to (and never should) free() any object returned by + these functions. + + Therefore also all the strings returned by this package are only valid until the next + call to the same function is made. This approach is selected, because of the nature of + access authorization: no string returned by the package needs to be valid longer than + until the next call. + + This also makes it easy to plug the AA package in: you don't have to ponder whether to + free()something here or is it done somewhere else (because it is always done somewhere + else). + + The strings that the package needs to store are copied so the original strings given as + parameters to AA functions may be freed or modified with no side effects. + + Also note:The AA package does not free() anything else than what it has itself + allocated. + + */ + +#ifndef HTAABROW_H +#define HTAABROW_H + +#include /* Common parts of AA */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + Routines for Browser Side Recording of AA Info + + Most of the browser-side AA is done by the following two functions (which are called + from file HTTP.c so the browsers using libwww only need to be linked with the new + library and not be changed at all): + + HTAA_composeAuth() composes the Authorization: line contents, if the AA package + thinks that the given document is protected. Otherwise this function returns NULL. + This function also calls the functions HTPrompt(),HTPromptPassword() and HTConfirm() + to get the username, password and some confirmation from the user. + + HTAA_shouldRetryWithAuth() determines whether to retry the request with AA or with a + new AA (in case username or password was misspelled). + + */ +/* PUBLIC HTAA_composeAuth() + * + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE IF WE + * ALREADY KNOW, THAT THE HOST MIGHT REQUIRE AUTHORIZATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: basic username:password" + * + * As usual, this string is automatically freed. + */ + extern char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy); + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ + extern BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy); + +/* + * Function to allow clearing of all Authorization info + * via a browser command. - FM + */ + extern void HTClearHTTPAuthInfo(void); + +/* + * Check if a hostname-string contains user information. + */ + extern BOOL HTAA_HaveUserinfo(const char *hostname); + +/* + +Enabling Gateway httpds to Forward Authorization + + These functions should only be called from daemon code, and HTAAForwardAuth_reset() + must be called before the next request is handled to make sure that authorization + string isn't cached in daemon so that other people can access private files using + somebody else's previous authorization information. + + */ + + extern void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics); + extern void HTAAForwardAuth_reset(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAABROW_H */ diff --git a/WWW/Library/Implementation/HTAAProt.c b/WWW/Library/Implementation/HTAAProt.c new file mode 100644 index 0000000..d5117f1 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.c @@ -0,0 +1,738 @@ +/* + * $LynxId: HTAAProt.c,v 1.34 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAProt.c + * PROTECTION FILE PARSING MODULE + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 20 Oct 93 AL Now finds uid/gid for nobody/nogroup by name + * (doesn't use default 65534 right away). + * Also understands negative uids/gids. + * 14 Nov 93 MD Added VMS compatibility + * + * BUGS: + * + * + */ + +#include + +#ifndef VMS +#ifndef NOUSERS +#include /* Unix password file routine: getpwnam() */ +#include /* Unix group file routine: getgrnam() */ +#endif /* NOUSERS */ +#endif /* not VMS */ + +#include +#include /* Lexical analysor */ +#include /* Implemented here */ + +#include +#include + +#define NOBODY 65534 /* -2 in 16-bit environment */ +#define NONESUCH 65533 /* -3 in 16-bit environment */ + +/* + * Protection setup caching + */ +typedef struct { + char *prot_filename; + HTAAProt *prot; +} HTAAProtCache; + +static HTList *prot_cache = NULL; /* Protection setup cache. */ +static HTAAProt *default_prot = NULL; /* Default protection. */ +static HTAAProt *current_prot = NULL; /* Current protection mode */ + + /* which is set up by callbacks */ + /* from the rule system when */ + /* a "protect" rule is matched. */ + +#ifndef NOUSERS +/* static isNumber() + * DOES A CHARACTER STRING REPRESENT A NUMBER + */ +static BOOL isNumber(const char *s) +{ + const char *cur = s; + + if (isEmpty(s)) + return NO; + + if (*cur == '-') + cur++; /* Allow initial minus sign in a number */ + + while (*cur) { + if (*cur < '0' || *cur > '9') + return NO; + cur++; + } + return YES; +} + +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ +int HTAA_getUid(void) +{ + int uid; + + if (current_prot && current_prot->uid_name) { + if (isNumber(current_prot->uid_name)) { + uid = atoi(current_prot->uid_name); + if ((*HTAA_UidToName(uid)) != '\0') { + return uid; + } + } else { /* User name (not a number) */ + if ((uid = HTAA_NameToUid(current_prot->uid_name)) != NONESUCH) { + return uid; + } + } + } + /* + * Ok, then let's get uid for nobody. + */ + if ((uid = HTAA_NameToUid("nobody")) != NONESUCH) { + return uid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nobody */ +} + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ +int HTAA_getGid(void) +{ + int gid; + + if (current_prot && current_prot->gid_name) { + if (isNumber(current_prot->gid_name)) { + gid = atoi(current_prot->gid_name); + if (*HTAA_GidToName(gid) != '\0') { + return gid; + } + } else { /* Group name (not number) */ + if ((gid = HTAA_NameToGid(current_prot->gid_name)) != NONESUCH) { + return gid; + } + } + } + /* + * Ok, then let's get gid for nogroup. + */ + if ((gid = HTAA_NameToGid("nogroup")) != NONESUCH) { + return gid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nogroup */ +} +#endif /* !NOUSERS */ + +/* static HTAA_setIds() + * SET UID AND GID (AS NAMES OR NUMBERS) + * TO HTAAProt STRUCTURE + * ON ENTRY: + * prot destination. + * ids is a string like "james.www" or "1422.69" etc. + * giving uid and gid. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_setIds(HTAAProt *prot, const char *ids) +{ + if (ids) { + char *local_copy = NULL; + char *point; + + StrAllocCopy(local_copy, ids); + point = StrChr(local_copy, '.'); + if (point) { + *(point++) = (char) 0; + StrAllocCopy(prot->gid_name, point); + } else { + StrAllocCopy(prot->gid_name, "nogroup"); + } + StrAllocCopy(prot->uid_name, local_copy); + FREE(local_copy); + } else { + StrAllocCopy(prot->uid_name, "nobody"); + StrAllocCopy(prot->gid_name, "nogroup"); + } +} + +/* static HTAA_parseProtFile() + * PARSE A PROTECTION SETUP FILE AND + * PUT THE RESULT IN A HTAAProt STRUCTURE + * ON ENTRY: + * prot destination structure. + * fp open protection file. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_parseProtFile(HTAAProt *prot, FILE *fp) +{ + if (prot && fp) { + LexItem lex_item; + char *fieldname = NULL; + + while (LEX_EOF != (lex_item = lex(fp))) { + + while (lex_item == LEX_REC_SEP) /* Ignore empty lines */ + lex_item = lex(fp); + + if (lex_item == LEX_EOF) /* End of file */ + break; + + if (lex_item == LEX_ALPH_STR) { /* Valid setup record */ + + StrAllocCopy(fieldname, HTlex_buffer); + + if (LEX_FIELD_SEP != (lex_item = lex(fp))) + unlex(lex_item); /* If someone wants to use colon */ + /* after field name it's ok, but */ + /* not required. Here we read it. */ + + if (0 == strncasecomp(fieldname, "Auth", 4)) { + lex_item = lex(fp); + while (lex_item == LEX_ALPH_STR) { + HTAAScheme scheme = HTAAScheme_enum(HTlex_buffer); + + if (scheme != HTAA_UNKNOWN) { + if (!prot->valid_schemes) + prot->valid_schemes = HTList_new(); + HTList_addObject(prot->valid_schemes, (void *) scheme); + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: valid", + "authentication scheme:", + HTAAScheme_name(scheme))); + } else { + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: unknown", + "authentication scheme:", + HTlex_buffer)); + } + + if (LEX_ITEM_SEP != (lex_item = lex(fp))) + break; + /* + * Here lex_item == LEX_ITEM_SEP; after item separator + * it is ok to have one or more newlines (LEX_REC_SEP) + * and they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + } /* while items in list */ + } + /* if "Authenticate" */ + else if (0 == strncasecomp(fieldname, "mask", 4)) { + prot->mask_group = HTAA_parseGroupDef(fp); + lex_item = LEX_REC_SEP; /*groupdef parser read this already */ + if (TRACE) { + if (prot->mask_group) { + fprintf(tfp, + "HTAA_parseProtFile: Mask group:\n"); + HTAA_printGroupDef(prot->mask_group); + } else + fprintf(tfp, + "HTAA_parseProtFile: Mask group syntax error\n"); + } + } + /* if "Mask" */ + else { /* Just a name-value pair, put it to assoclist */ + + if (LEX_ALPH_STR == (lex_item = lex(fp))) { + if (!prot->values) + prot->values = HTAssocList_new(); + HTAssocList_add(prot->values, fieldname, HTlex_buffer); + lex_item = lex(fp); /* Read record separator */ + CTRACE((tfp, "%s `%s' bound to value `%s'\n", + "HTAA_parseProtFile: Name", + fieldname, HTlex_buffer)); + } + } /* else name-value pair */ + + } + /* if valid field */ + if (lex_item != LEX_EOF && lex_item != LEX_REC_SEP) { + CTRACE((tfp, "%s %s %d (that line ignored)\n", + "HTAA_parseProtFile: Syntax error", + "in protection setup file at line", + HTlex_line)); + do { + lex_item = lex(fp); + } while (lex_item != LEX_EOF && lex_item != LEX_REC_SEP); + } /* if syntax error */ + } /* while not end-of-file */ + FREE(fieldname); + } /* if valid parameters */ +} + +/* static HTAAProt_new() + * ALLOCATE A NEW HTAAProt STRUCTURE AND + * INITIALIZE IT FROM PROTECTION SETUP FILE + * ON ENTRY: + * cur_docname current filename after rule translations. + * prot_filename protection setup file name. + * If NULL, not an error. + * ids Uid and gid names or numbers, + * examples: + * james ( <=> james.nogroup) + * .www ( <=> nobody.www) + * james.www + * james.69 + * 1422.69 + * 1422.www + * + * May be NULL, defaults to nobody.nogroup. + * Should be NULL, if prot_file is NULL. + * + * ON EXIT: + * returns returns a new and initialized protection + * setup structure. + * If setup file is already read in (found + * in cache), only sets uid_name and gid + * fields, and returns that. + */ +static HTAAProt *HTAAProt_new(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + HTList *cur = prot_cache; + HTAAProtCache *cache_item = NULL; + HTAAProt *prot; + FILE *fp; + + if (!prot_cache) + prot_cache = HTList_new(); + + while (NULL != (cache_item = (HTAAProtCache *) HTList_nextObject(cur))) { + if (!strcmp(cache_item->prot_filename, prot_filename)) + break; + } + if (cache_item) { + prot = cache_item->prot; + CTRACE((tfp, "%s `%s' already in cache\n", + "HTAAProt_new: Protection file", prot_filename)); + } else { + CTRACE((tfp, "HTAAProt_new: Loading protection file `%s'\n", + prot_filename)); + + if ((prot = typecalloc(HTAAProt)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + prot->ctemplate = NULL; + prot->filename = NULL; + prot->uid_name = NULL; + prot->gid_name = NULL; + prot->valid_schemes = HTList_new(); + prot->mask_group = NULL; /* Masking disabled by defaults */ + prot->values = HTAssocList_new(); + + if (prot_filename && NULL != (fp = fopen(prot_filename, TXT_R))) { + HTAA_parseProtFile(prot, fp); + fclose(fp); + if ((cache_item = typecalloc(HTAAProtCache)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + cache_item->prot = prot; + cache_item->prot_filename = NULL; + StrAllocCopy(cache_item->prot_filename, prot_filename); + HTList_addObject(prot_cache, (void *) cache_item); + } else { + CTRACE((tfp, "HTAAProt_new: %s `%s'\n", + "Unable to open protection setup file", + NONNULL(prot_filename))); + } + } + + if (cur_docname) + StrAllocCopy(prot->filename, cur_docname); + HTAA_setIds(prot, ids); + + return prot; +} + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ +void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + default_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + default_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + CTRACE((tfp, "%s %s\n", + "HTAA_setDefaultProtection: ERROR: Protection file", + "not specified (obligatory for DefProt rule)!!\n")); + } +} + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ +void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + current_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + current_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + if (default_prot) { + current_prot = default_prot; + HTAA_setIds(current_prot, ids); + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: Protection file", + "not specified for Protect rule", + "-- using default protection")); + } else { + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: ERROR: Protection", + "file not specified for Protect rule, and", + "default protection is not set!!")); + } + } +} + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ +HTAAProt *HTAA_getCurrentProtection(void) +{ + return current_prot; +} + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * AND SET IT TO CURRENT PROTECTION + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + * IMPORTANT: + * As a side-effect this tells the protection system that + * the file is in fact protected and sets the current + * protection mode to default. + */ +HTAAProt *HTAA_getDefaultProtection(void) +{ + if (!current_prot) { + current_prot = default_prot; + default_prot = NULL; + } + return current_prot; +} + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ +void HTAA_clearProtections(void) +{ + current_prot = NULL; /* These are not freed because */ + default_prot = NULL; /* they are actually in cache. */ +} + +typedef struct { + char *name; + int user; +} USER_DATA; + +#ifndef NOUSERS +static HTList *known_grp = NULL; +static HTList *known_pwd = NULL; +static BOOL uidgid_cache_inited = NO; +#endif + +#ifdef LY_FIND_LEAKS +static void clear_uidgid_cache(void) +{ +#ifndef NOUSERS + USER_DATA *data; + + if (known_grp) { + while ((data = HTList_removeLastObject(known_grp)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_grp); + } + if (known_pwd) { + while ((data = HTList_removeLastObject(known_pwd)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_pwd); + } +#endif +} +#endif /* LY_FIND_LEAKS */ + +#ifndef NOUSERS +static void save_gid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_grp) { + known_grp = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_grp, data); +} +#endif /* NOUSERS */ + +#ifndef NOUSERS +static void save_uid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_pwd) { + known_pwd = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_pwd, data); +} +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name, or an empty string if not found. + */ +const char *HTAA_UidToName(int uid GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (uid == data->user) + return data->name; + } + + if ((pw = getpwuid((uid_t) uid)) != 0 + && pw->pw_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_UidToName: getpwuid", + uid, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return pw->pw_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id, or NONESUCH if not found. + */ +int HTAA_NameToUid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((pw = getpwnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToUid: getpwnam", + name, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return (int) pw->pw_uid; + } +#endif + return NONESUCH; +} + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name, or an empty string if not found. + */ +const char *HTAA_GidToName(int gid GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (gid == data->user) + return data->name; + } + + if ((gr = getgrgid((gid_t) gid)) != 0 + && gr->gr_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_GidToName: getgrgid", + gid, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return gr->gr_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id, or NONESUCH if not found. + */ +int HTAA_NameToGid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((gr = getgrnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToGid: getgrnam", + name, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return (int) gr->gr_gid; + } +#endif + return NONESUCH; +} diff --git a/WWW/Library/Implementation/HTAAProt.h b/WWW/Library/Implementation/HTAAProt.h new file mode 100644 index 0000000..22e3d92 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.h @@ -0,0 +1,226 @@ +/* PROTECTION SETUP FILE + + */ + +#ifndef HTAAPROT_H +#define HTAAPROT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Server's Representation of Document (Tree) Protections + + */ typedef struct { + char *ctemplate; /* Template for this protection */ + char *filename; /* Current document file */ + char *uid_name; /* Effective uid (name of it) */ + char *gid_name; /* Effective gid (name of it) */ + GroupDef *mask_group; /* Allowed users and IP addresses */ + HTList *valid_schemes; /* Valid authentication schemes */ + HTAssocList *values; /* Association list for scheme specific */ + /* parameters. */ + } HTAAProt; + +/* + +Callbacks for rule system + + The following three functioncs are called by the rule system: + + HTAA_clearProtections() when starting to translate a filename + + HTAA_setDefaultProtection() when "defprot" rule is matched + + HTAA_setCurrentProtection() when "protect" rule is matched + + Protection setup files are cached by these functions. + + */ + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ + extern void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ + extern void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ + extern void HTAA_clearProtections(void); + +/* + +Getting Protection Settings + + HTAA_getCurrentProtection() returns the current protection mode (if there was a + "protect" rule). NULL, if no "protect" rule has been matched. + + HTAA_getDefaultProtection() sets the current protection mode to what it was set to + by "defprot" rule and also returns it (therefore after this call also + HTAA_getCurrentProtection() returns the same structure. + + */ + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getCurrentProtection(void); + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getDefaultProtection(void); + +/* + +Get User and Group IDs to Which Set to + + */ + +#ifndef NOUSERS +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ + extern int HTAA_getUid(void); + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ + extern int HTAA_getGid(void); +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name + */ + extern const char *HTAA_UidToName(int uid); + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id + */ + extern int HTAA_NameToUid(const char *name); + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name + */ + extern const char *HTAA_GidToName(int gid); + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id + */ + extern int HTAA_NameToGid(const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTAAPROT_H */ diff --git a/WWW/Library/Implementation/HTAAUtil.c b/WWW/Library/Implementation/HTAAUtil.c new file mode 100644 index 0000000..1be26f9 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.c @@ -0,0 +1,605 @@ +/* + * $LynxId: HTAAUtil.c,v 1.36 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAUtil.c + * COMMON PARTS OF ACCESS AUTHORIZATION MODULE + * FOR BOTH SERVER AND BROWSER + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AA (Access Authorization) package means modules which + * names start with HTAA. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 8 Nov 93 MD (VMS only) Added case insensitive comparison in HTAA_templateCaseMatch + * + * + * BUGS: + * + * + */ + +#include + +#include /* Implemented here */ +#include /* Assoc list */ +#include +#include + +#include +#include +#include + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME INTO + * A SCHEME ENUMERATION + * + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ +HTAAScheme HTAAScheme_enum(const char *name) +{ + char *upcased = NULL; + + if (!name) + return HTAA_UNKNOWN; + + StrAllocCopy(upcased, name); + LYUpperCase(upcased); + + if (!StrNCmp(upcased, "NONE", 4)) { + FREE(upcased); + return HTAA_NONE; + } else if (!StrNCmp(upcased, "BASIC", 5)) { + FREE(upcased); + return HTAA_BASIC; + } else if (!StrNCmp(upcased, "PUBKEY", 6)) { + FREE(upcased); + return HTAA_PUBKEY; + } else if (!StrNCmp(upcased, "KERBEROSV4", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V4; + } else if (!StrNCmp(upcased, "KERBEROSV5", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V5; + } else { + FREE(upcased); + return HTAA_UNKNOWN; + } +} + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "None", "Basic", "Pubkey", ... + */ +const char *HTAAScheme_name(HTAAScheme scheme) +{ + switch (scheme) { + case HTAA_NONE: + return "None"; + case HTAA_BASIC: + return "Basic"; + case HTAA_PUBKEY: + return "Pubkey"; + case HTAA_KERBEROS_V4: + return "KerberosV4"; + case HTAA_KERBEROS_V5: + return "KerberosV5"; + case HTAA_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ +HTAAMethod HTAAMethod_enum(const char *name) +{ + if (!name) + return METHOD_UNKNOWN; + + if (0 == strcasecomp(name, "GET")) + return METHOD_GET; + else if (0 == strcasecomp(name, "PUT")) + return METHOD_PUT; + else + return METHOD_UNKNOWN; +} + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ +const char *HTAAMethod_name(HTAAMethod method) +{ + switch (method) { + case METHOD_GET: + return "GET"; + case METHOD_PUT: + return "PUT"; + case METHOD_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ +BOOL HTAAMethod_inList(HTAAMethod method, HTList *list) +{ + HTList *cur = list; + char *item; + + while (NULL != (item = (char *) HTList_nextObject(cur))) { + CTRACE((tfp, " %s", item)); + if (method == HTAAMethod_enum(item)) + return YES; + } + + return NO; /* Not found */ +} + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + for (; *p && *q && *p == *q; p++, q++) /* Find first mismatch */ + ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcmp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * template is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + /* Find first mismatch */ + for (; *p && *q && TOUPPER(*p) == TOUPPER(*q); p++, q++) ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcasecomp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ +char *HTAA_makeProtectionTemplate(const char *docname) +{ + char *ctemplate = NULL; + char *slash = NULL; + + if (docname) { + StrAllocCopy(ctemplate, docname); + slash = strrchr(ctemplate, '/'); + if (slash) + slash++; + else + slash = ctemplate; + *slash = '\0'; + StrAllocCat(ctemplate, "*"); + } else + StrAllocCopy(ctemplate, "*"); + + CTRACE((tfp, "make_template: made template `%s' for file `%s'\n", + ctemplate, docname)); + + return ctemplate; +} + +/* + * Skip leading whitespace from *s forward + */ +#define SKIPWS(s) while (*s==' ' || *s=='\t') s++; + +/* + * Kill trailing whitespace starting from *(s-1) backwards + */ +#define KILLWS(s) {char *c=s-1; while (*c==' ' || *c=='\t') *(c--)='\0';} + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ +HTAssocList *HTAA_parseArgList(char *str) +{ + HTAssocList *assoc_list = HTAssocList_new(); + char *cur = NULL; + char *name = NULL; + int n = 0; + + if (!str) + return assoc_list; + + while (*str) { + SKIPWS(str); /* Skip leading whitespace */ + cur = str; + n++; + + while (*cur && *cur != '=' && *cur != ',') + cur++; /* Find end of name (or lonely value without a name) */ + KILLWS(cur); /* Kill trailing whitespace */ + + if (*cur == '=') { /* Name followed by a value */ + *(cur++) = '\0'; /* Terminate name */ + StrAllocCopy(name, str); + SKIPWS(cur); /* Skip WS leading the value */ + str = cur; + if (*str == '"') { /* Quoted value */ + str++; + cur = str; + while (*cur && *cur != '"') + cur++; + if (*cur == '"') + *(cur++) = '\0'; /* Terminate value */ + /* else it is lacking terminating quote */ + SKIPWS(cur); /* Skip WS leading comma */ + if (*cur == ',') + cur++; /* Skip separating colon */ + } else { /* Unquoted value */ + while (*cur && *cur != ',') + cur++; + KILLWS(cur); /* Kill trailing whitespace */ + if (*cur == ',') + *(cur++) = '\0'; + /* else *cur already NULL */ + } + } else { /* No name, just a value */ + if (*cur == ',') + *(cur++) = '\0'; /* Terminate value */ + /* else last value on line (already terminated by NULL) */ + HTSprintf0(&name, "%d", n); /* Item order number for name */ + } + HTAssocList_add(assoc_list, name, str); + str = cur; + } /* while *str */ + + FREE(name); + return assoc_list; +} + +/************** HEADER LINE READER -- DOES UNFOLDING *************************/ + +#define BUFFER_SIZE 1024 + +static size_t buffer_length; +static char *buffer = 0; +static char *start_pointer; +static char *end_pointer; +static int in_soc = -1; + +#ifdef LY_FIND_LEAKS +static void FreeHTAAUtil(void) +{ + FREE(buffer); +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ +void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc) +{ + if (!start_of_headers) + length = 0; /* initialize length (is this reached at all?) */ + if (buffer == NULL) { /* first call? */ + buffer_length = length; + if (buffer_length < BUFFER_SIZE) /* would fall below BUFFER_SIZE? */ + buffer_length = BUFFER_SIZE; + buffer = (char *) malloc((size_t) (sizeof(char) * (buffer_length + 1))); + } else if (length > buffer_length) { /* need more space? */ + buffer_length = length; + buffer = (char *) realloc((char *) buffer, + (size_t) (sizeof(char) * (buffer_length + 1))); + } + if (buffer == NULL) + outofmem(__FILE__, "HTAA_setupReader"); + +#ifdef LY_FIND_LEAKS + atexit(FreeHTAAUtil); +#endif + start_pointer = buffer; + if (start_of_headers) { + LYStrNCpy(buffer, start_of_headers, length); + end_pointer = buffer + length; + } else { + *start_pointer = '\0'; + end_pointer = start_pointer; + } + in_soc = soc; +} + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ +char *HTAA_getUnfoldedLine(void) +{ + char *line = NULL; + char *cur; + int count; + BOOL peek_for_folding = NO; + + if (in_soc < 0) { + CTRACE((tfp, "%s %s\n", + "HTAA_getUnfoldedLine: buffer not initialized", + "with function HTAA_setupReader()")); + return NULL; + } + + for (;;) { + + /* Reading from socket */ + + if (start_pointer >= end_pointer) { /*Read the next block and continue */ +#ifdef USE_SSL + if (SSL_handle) + count = SSL_read(SSL_handle, buffer, BUFFER_SIZE); + else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#endif /* USE_SSL */ + if (count <= 0) { + in_soc = -1; + return line; + } + if (count > (int) buffer_length) + count = (int) buffer_length; + start_pointer = buffer; + end_pointer = buffer + count; + *end_pointer = '\0'; +#ifdef NOT_ASCII + cur = start_pointer; + while (cur < end_pointer) { + *cur = TOASCII(*cur); + cur++; + } +#endif /*NOT_ASCII */ + } + cur = start_pointer; + + /* Unfolding */ + + if (peek_for_folding) { + if (*cur != ' ' && *cur != '\t') + return line; /* Ok, no continuation line */ + else /* So this is a continuation line, continue */ + peek_for_folding = NO; + } + + /* Finding end-of-line */ + + while (cur < end_pointer && *cur != '\n') /* Find the end-of-line */ + cur++; /* (or end-of-buffer). */ + + /* Terminating line */ + + if (cur < end_pointer) { /* So *cur==LF, terminate line */ + *cur = '\0'; /* Overwrite LF */ + if (*(cur - 1) == '\r') + *(cur - 1) = '\0'; /* Overwrite CR */ + peek_for_folding = YES; /* Check for a continuation line */ + } + + /* Copying the result */ + + if (line) + StrAllocCat(line, start_pointer); /* Append */ + else + StrAllocCopy(line, start_pointer); /* A new line */ + + start_pointer = cur + 1; /* Skip the read line */ + + } /* forever */ +} diff --git a/WWW/Library/Implementation/HTAAUtil.h b/WWW/Library/Implementation/HTAAUtil.h new file mode 100644 index 0000000..33a8ee3 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.h @@ -0,0 +1,318 @@ +/* + * $LynxId: HTAAUtil.h,v 1.13 2010/10/27 00:09:52 tom Exp $ + * + * Utilities for the Authorization parts of libwww + * COMMON PARTS OF AUTHORIZATION MODULE TO BOTH SERVER AND BROWSER + * + * This module is the interface to the common parts of Access Authorization (AA) package + * for both server and browser. Important to know about memory allocation: + * + * Routines in this module use dynamic allocation, but free automatically all the memory + * reserved by them. + * + * Therefore the caller never has to (and never should) free() any object returned by + * these functions. + * + * Therefore also all the strings returned by this package are only valid until the next + * call to the same function is made. This approach is selected, because of the nature of + * access authorization: no string returned by the package needs to be valid longer than + * until the next call. + * + * This also makes it easy to plug the AA package in: you don't have to ponder whether to + * free() something here or is it done somewhere else (because it is always done somewhere + * else). + * + * The strings that the package needs to store are copied so the original strings given as + * parameters to AA functions may be freed or modified with no side effects. + * + * Also note: The AA package does not free() anything else than what it has itself + * allocated. + * + */ + +#ifndef HTAAUTIL_H +#define HTAAUTIL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Numeric constants + */ +#define MAX_USERNAME_LEN 16 /* @@ Longest allowed username */ +#define MAX_PASSWORD_LEN 3*13 /* @@ Longest allowed password */ + /* (encrypted, so really only 3*8) */ +#define MAX_METHODNAME_LEN 12 /* @@ Longest allowed method name */ +#define MAX_FIELDNAME_LEN 16 /* @@ Longest field name in */ + /* protection setup file */ +#define MAX_PATHNAME_LEN 80 /* @@ Longest passwd/group file */ +/* pathname to allow */ +/* + + Datatype definitions + + HTAASCHEME + + The enumeration HTAAScheme represents the possible authentication schemes used by the + WWW Access Authorization. + + */ typedef enum { + HTAA_UNKNOWN, + HTAA_NONE, + HTAA_BASIC, + HTAA_PUBKEY, + HTAA_KERBEROS_V4, + HTAA_KERBEROS_V5, + HTAA_MAX_SCHEMES /* THIS MUST ALWAYS BE LAST! Number of schemes */ + } HTAAScheme; + +/* + + ENUMERATION TO REPRESENT HTTP METHODS + + */ + + typedef enum { + METHOD_UNKNOWN, + METHOD_GET, + METHOD_PUT + } HTAAMethod; + +/* + +Authentication Schemes + + */ + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME TO A SCHEME ENUMERATION + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ + extern HTAAScheme HTAAScheme_enum(const char *name); + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "none", "basic", "pubkey", ... + */ + extern const char *HTAAScheme_name(HTAAScheme scheme); + +/* + +Methods + + */ + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ + extern HTAAMethod HTAAMethod_enum(const char *name); + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ + extern const char *HTAAMethod_name(HTAAMethod method); + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ + extern BOOL HTAAMethod_inList(HTAAMethod method, HTList *list); + +/* + +Match Template Against Filename + + */ + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ + extern char *HTAA_makeProtectionTemplate(const char *docname); + +/* + +MIME Argument List Parser + + */ + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ + extern HTList *HTAA_parseArgList(char *str); + +/* + +Header Line Reader + + */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ + extern void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc); + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ + extern char *HTAA_getUnfoldedLine(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAAUTIL_H */ diff --git a/WWW/Library/Implementation/HTAccess.c b/WWW/Library/Implementation/HTAccess.c new file mode 100644 index 0000000..a79a8a2 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.c @@ -0,0 +1,1460 @@ +/* + * $LynxId: HTAccess.c,v 1.85 2019/08/24 00:27:06 tom Exp $ + * + * Access Manager HTAccess.c + * ============== + * + * Authors + * TBL Tim Berners-Lee timbl@info.cern.ch + * JFG Jean-Francois Groff jfg@dxcern.cern.ch + * DD Denis DeLaRoca (310) 825-4580 + * FM Foteos Macrides macrides@sci.wfeb.edu + * PDM Danny Mayer mayer@ljo.dec.com + * + * History + * 8 Jun 92 Telnet hopping prohibited as telnet is not secure TBL + * 26 Jun 92 When over DECnet, suppressed FTP, Gopher and News. JFG + * 6 Oct 92 Moved HTClientHost and logfile into here. TBL + * 17 Dec 92 Tn3270 added, bug fix. DD + * 4 Feb 93 Access registration, Search escapes bad chars TBL + * PARAMETERS TO HTSEARCH AND HTLOADRELATIVE CHANGED + * 28 May 93 WAIS gateway explicit if no WAIS library linked in. + * 31 May 94 Added DIRECT_WAIS support for VMS. FM + * 27 Jan 95 Fixed proxy support to use NNTPSERVER for checking + * whether or not to use the proxy server. PDM + * 27 Jan 95 Ensured that proxy service will be overridden for files + * on the local host (because HTLoadFile() doesn't try ftp + * for those) and will substitute ftp for remote files. FM + * 28 Jan 95 Tweaked PDM's proxy override mods to handle port info + * for news and wais URL's. FM + * + * Bugs + * This module assumes that that the graphic object is hypertext, as it + * needs to select it when it has been loaded. A superclass needs to be + * defined which accepts select and select_anchor. + */ + +#ifdef VMS +#define DIRECT_WAIS +#endif /* VMS */ + +#include +#include +#include +/* + * Implements: + */ +#include + +/* + * Uses: + */ +#include +#include /* SCW */ + +#ifndef NO_RULES +#include +#endif + +#include +#include /* See bugs above */ +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * These flags may be set to modify the operation of this module + */ +char *HTClientHost = NULL; /* Name of remote login host if any */ +FILE *HTlogfile = NULL; /* File to which to output one-liners */ +BOOL HTSecure = NO; /* Disable access for telnet users? */ +BOOL HTPermitRedir = NO; /* Always allow redirection in getfile()? */ + +BOOL using_proxy = NO; /* are we using a proxy gateway? */ + +/* + * To generate other things, play with these: + */ +HTFormat HTOutputFormat = NULL; +HTStream *HTOutputStream = NULL; /* For non-interactive, set this */ + +static HTList *protocols = NULL; /* List of registered protocol descriptors */ + +char *use_this_url_instead = NULL; + +static int pushed_assume_LYhndl = -1; /* see LYUC* functions below - kw */ +static char *pushed_assume_MIMEname = NULL; + +#ifdef LY_FIND_LEAKS +static void free_protocols(void) +{ + HTList_delete(protocols); + protocols = NULL; + FREE(pushed_assume_MIMEname); /* shouldn't happen, just in case - kw */ +} +#endif /* LY_FIND_LEAKS */ + +/* Register a Protocol. HTRegisterProtocol() + * -------------------- + */ +BOOL HTRegisterProtocol(HTProtocol * protocol) +{ + if (!protocols) { + protocols = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_protocols); +#endif + } + HTList_addObject(protocols, protocol); + return YES; +} + +/* Register all known protocols. HTAccessInit() + * ----------------------------- + * + * Add to or subtract from this list if you add or remove protocol + * modules. This routine is called the first time the protocol list + * is needed, unless any protocols are already registered, in which + * case it is not called. Therefore the application can override + * this list. + * + * Compiling with NO_INIT prevents all known protocols from being + * forced in at link time. + */ +#ifndef NO_INIT +#ifdef GLOBALREF_IS_MACRO +extern GLOBALREF (HTProtocol, HTTP); +extern GLOBALREF (HTProtocol, HTTPS); +extern GLOBALREF (HTProtocol, HTFile); +extern GLOBALREF (HTProtocol, HTTelnet); +extern GLOBALREF (HTProtocol, HTTn3270); +extern GLOBALREF (HTProtocol, HTRlogin); + +#ifndef DECNET +#ifndef DISABLE_FTP +extern GLOBALREF (HTProtocol, HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +extern GLOBALREF (HTProtocol, HTNews); +extern GLOBALREF (HTProtocol, HTNNTP); +extern GLOBALREF (HTProtocol, HTNewsPost); +extern GLOBALREF (HTProtocol, HTNewsReply); +extern GLOBALREF (HTProtocol, HTSNews); +extern GLOBALREF (HTProtocol, HTSNewsPost); +extern GLOBALREF (HTProtocol, HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +extern GLOBALREF (HTProtocol, HTGopher); +extern GLOBALREF (HTProtocol, HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +extern GLOBALREF (HTProtocol, HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +extern GLOBALREF (HTProtocol, HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#else +GLOBALREF HTProtocol HTTP, HTTPS, HTFile, HTTelnet, HTTn3270, HTRlogin; + +#ifndef DECNET +#ifndef DISABLE_FTP +GLOBALREF HTProtocol HTFTP; +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +GLOBALREF HTProtocol HTNews, HTNNTP, HTNewsPost, HTNewsReply; +GLOBALREF HTProtocol HTSNews, HTSNewsPost, HTSNewsReply; +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +GLOBALREF HTProtocol HTGopher, HTCSO; +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +GLOBALREF HTProtocol HTFinger; +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +GLOBALREF HTProtocol HTWAIS; +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#endif /* GLOBALREF_IS_MACRO */ + +static void HTAccessInit(void) /* Call me once */ +{ + HTRegisterProtocol(&HTTP); + HTRegisterProtocol(&HTTPS); + HTRegisterProtocol(&HTFile); + HTRegisterProtocol(&HTTelnet); + HTRegisterProtocol(&HTTn3270); + HTRegisterProtocol(&HTRlogin); +#ifndef DECNET +#ifndef DISABLE_FTP + HTRegisterProtocol(&HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS + HTRegisterProtocol(&HTNews); + HTRegisterProtocol(&HTNNTP); + HTRegisterProtocol(&HTNewsPost); + HTRegisterProtocol(&HTNewsReply); + HTRegisterProtocol(&HTSNews); + HTRegisterProtocol(&HTSNewsPost); + HTRegisterProtocol(&HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER + HTRegisterProtocol(&HTGopher); + HTRegisterProtocol(&HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER + HTRegisterProtocol(&HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS + HTRegisterProtocol(&HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ + LYRegisterLynxProtocols(); +} +#endif /* !NO_INIT */ + +/* Check for proxy override. override_proxy() + * ------------------------- + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ +BOOL override_proxy(const char *addr) +{ + const char *no_proxy = getenv("no_proxy"); + char *p = NULL; + char *at = NULL; + char *host = NULL; + char *Host = NULL; + char *acc_method = NULL; + int port = 0; + int h_len = 0; + + /* + * Check for global override. + */ + if (no_proxy) { + if (!strcmp(no_proxy, "*")) + return YES; + } + + /* + * Never proxy file:// URLs if they are on the local host. HTLoadFile() + * will not attempt ftp for those if direct access fails. We'll check that + * first, in case no_proxy hasn't been defined. - FM + */ + if (!addr) + return NO; + if (!(host = HTParse(addr, "", PARSE_HOST))) + return NO; + if (!*host) { + FREE(host); + return NO; + } + Host = (((at = StrChr(host, '@')) != NULL) ? (at + 1) : host); + + if ((acc_method = HTParse(addr, "", PARSE_ACCESS))) { + if (!strcmp("file", acc_method) && + (LYSameHostname(Host, "localhost") || + LYSameHostname(Host, HTHostName()))) { + FREE(host); + FREE(acc_method); + return YES; + } + FREE(acc_method); + } + + if (!no_proxy) { + FREE(host); + return NO; + } + + if (NULL != (p = HTParsePort(Host, &port))) { /* Port specified */ + *p = 0; /* Chop off port */ + } else { /* Use default port */ + acc_method = HTParse(addr, "", PARSE_ACCESS); + if (acc_method != NULL) { + /* *INDENT-OFF* */ + if (!strcmp(acc_method, "http")) port = 80; + else if (!strcmp(acc_method, "https")) port = 443; + else if (!strcmp(acc_method, "ftp")) port = 21; +#ifndef DISABLE_GOPHER + else if (!strcmp(acc_method, "gopher")) port = 70; +#endif + else if (!strcmp(acc_method, "cso")) port = 105; +#ifndef DISABLE_NEWS + else if (!strcmp(acc_method, "news")) port = 119; + else if (!strcmp(acc_method, "nntp")) port = 119; + else if (!strcmp(acc_method, "newspost")) port = 119; + else if (!strcmp(acc_method, "newsreply")) port = 119; + else if (!strcmp(acc_method, "snews")) port = 563; + else if (!strcmp(acc_method, "snewspost")) port = 563; + else if (!strcmp(acc_method, "snewsreply")) port = 563; +#endif + else if (!strcmp(acc_method, "wais")) port = 210; +#ifndef DISABLE_FINGER + else if (!strcmp(acc_method, "finger")) port = 79; +#endif + else if (!strcmp(acc_method, "telnet")) port = 23; + else if (!strcmp(acc_method, "tn3270")) port = 23; + else if (!strcmp(acc_method, "rlogin")) port = 513; + /* *INDENT-ON* */ + + FREE(acc_method); + } + } + if (!port) + port = 80; /* Default */ + h_len = (int) strlen(Host); + + while (*no_proxy) { + const char *end; + const char *colon = NULL; + int templ_port = 0; + int t_len; + int brackets = 0; + + while (*no_proxy && (WHITE(*no_proxy) || *no_proxy == ',')) + no_proxy++; /* Skip whitespace and separators */ + + end = no_proxy; + while (*end && !WHITE(*end) && *end != ',') { /* Find separator */ + if (!brackets && (*end == ':')) + colon = end; /* Port number given */ + else if (*end == '[') + ++brackets; + else if (*end == ']') + --brackets; + end++; + } + + if (colon) { + /* unlike HTParsePort(), this may be followed by non-digits */ + templ_port = atoi(colon + 1); + t_len = (int) (colon - no_proxy); + } else { + t_len = (int) (end - no_proxy); + } + + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + !strncasecomp(Host + h_len - t_len, no_proxy, t_len))) { + FREE(host); + return YES; + } +#ifdef CJK_EX /* ASATAKU PROXY HACK */ + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + isdigit(UCH(*no_proxy)) && + !StrNCmp(host, no_proxy, t_len))) { + FREE(host); + return YES; + } +#endif /* ASATAKU PROXY HACK */ + + if (*end) + no_proxy = (end + 1); + else + break; + } + + FREE(host); + return NO; +} + +/* Find physical name and access protocol get_physical() + * -------------------------------------- + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns HT_NO_ACCESS Error has occurred. + * HT_OK Success + */ +static int get_physical(const char *addr, + HTParentAnchor *anchor) +{ + int result; + char *acc_method = NULL; /* Name of access method */ + char *physical = NULL; + char *Server_addr = NULL; + BOOL override_flag = NO; + + CTRACE((tfp, "get_physical %s\n", addr)); + + /* + * Make sure the using_proxy variable is FALSE. + */ + using_proxy = NO; + +#ifndef NO_RULES + if ((physical = HTTranslate(addr)) == 0) { + if (redirecting_url) { + return HT_REDIRECTING; + } + return HT_FORBIDDEN; + } + if (anchor->isISMAPScript == TRUE) { + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + } + if (!StrNCmp(physical, "Proxied=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + using_proxy = YES; + } else if (!StrNCmp(physical, "NoProxy=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + override_flag = YES; + } else { + HTAnchor_setPhysical(anchor, physical); + } + FREE(physical); /* free our copy */ +#else + if (anchor->isISMAPScript == TRUE) { + StrAllocCopy(physical, addr); + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + HTAnchor_setPhysical(anchor, physical); + FREE(physical); /* free our copy */ + } else { + HTAnchor_setPhysical(anchor, addr); + } +#endif /* NO_RULES */ + + acc_method = HTParse(HTAnchor_physical(anchor), STR_FILE_URL, PARSE_ACCESS); + + /* + * Check whether gateway access has been set up for this. + * + * This function can be replaced by the rule system above. + * + * If the rule system has already determined that we should use a proxy, or + * that we shouldn't, ignore proxy-related settings, don't use no_proxy + * either. + */ +#define USE_GATEWAYS +#ifdef USE_GATEWAYS + + if (!override_flag && !using_proxy) { /* else ignore no_proxy env var */ + char *host = NULL; + int port; + + if (!strcasecomp(acc_method, "news")) { + /* + * News is different, so we need to check the name of the server, + * as well as the default port for selective exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (HTParsePort(host, &port) == NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":119/"); + } + FREE(host); + } else if (LYGetEnv("NNTPSERVER") != NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, LYGetEnv("NNTPSERVER")); + StrAllocCat(Server_addr, ":119/"); + } + } else if (!strcasecomp(acc_method, "wais")) { + /* + * Wais also needs checking of the default port for selective + * exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (!(HTParsePort(host, &port))) { + StrAllocCopy(Server_addr, "wais://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":210/"); + } + FREE(host); + } else + StrAllocCopy(Server_addr, addr); + } else { + StrAllocCopy(Server_addr, addr); + } + override_flag = override_proxy(Server_addr); + } + + if (!override_flag && !using_proxy) { + char *gateway_parameter = NULL, *gateway, *proxy; + + /* + * Search for gateways. + */ + HTSprintf0(&gateway_parameter, "WWW_%s_GATEWAY", acc_method); + gateway = LYGetEnv(gateway_parameter); /* coerce for decstation */ + + /* + * Search for proxy servers. + */ + if (!strcmp(acc_method, "file")) + /* + * If we got to here, a file URL is for ftp on a remote host. - FM + */ + strcpy(gateway_parameter, "ftp_proxy"); + else + sprintf(gateway_parameter, "%s_proxy", acc_method); + proxy = LYGetEnv(gateway_parameter); + FREE(gateway_parameter); + + if (gateway) + CTRACE((tfp, "Gateway found: %s\n", gateway)); + if (proxy) + CTRACE((tfp, "proxy server found: %s\n", proxy)); + + /* + * Proxy servers have precedence over gateway servers. + */ + if (proxy) { + char *gatewayed = NULL; + + StrAllocCopy(gatewayed, proxy); + if (!StrNCmp(gatewayed, "http", 4)) { + char *cp = strrchr(gatewayed, '/'); + + /* Append a slash to the proxy specification if it doesn't + * end in one but otherwise looks normal (starts with "http", + * has no '/' other than ones before the hostname). - kw */ + if (cp && (cp - gatewayed) <= 7) + LYAddHtmlSep(&gatewayed); + } + /* + * Ensure that the proxy server uses ftp for file URLs. - FM + */ + if (!StrNCmp(addr, "file", 4)) { + StrAllocCat(gatewayed, "ftp"); + StrAllocCat(gatewayed, (addr + 4)); + } else + StrAllocCat(gatewayed, addr); + using_proxy = YES; + if (anchor->isISMAPScript == TRUE) + StrAllocCat(gatewayed, "?0,0"); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + + } else if (gateway) { + char *path = HTParse(addr, "", + PARSE_HOST + PARSE_PATH + PARSE_PUNCTUATION); + + /* Chop leading / off to make host into part of path */ + char *gatewayed = HTParse(path + 1, gateway, PARSE_ALL); + + FREE(path); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + } + } + FREE(Server_addr); +#endif /* use gateways */ + + /* + * Search registered protocols to find suitable one. + */ + result = HT_NO_ACCESS; + { + int i, n; + +#ifndef NO_INIT + if (!protocols) + HTAccessInit(); +#endif + n = HTList_count(protocols); + for (i = 0; i < n; i++) { + HTProtocol *p = (HTProtocol *) HTList_objectAt(protocols, i); + + if (!strcmp(p->name, acc_method)) { + HTAnchor_setProtocol(anchor, p); + FREE(acc_method); + result = HT_OK; + break; + } + } + } + + FREE(acc_method); + return result; +} + +/* + * Temporarily set the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec + * used for charset "assuming" to the values implied by a HTParentAnchor's + * UCStages, after saving the current values for later restoration. - kw @@@ + * These functions may not really belong here, but where else? I want the + * "pop" to occur as soon as possible after loading has finished. - kw @@@ + */ +void LYUCPushAssumed(HTParentAnchor *anchor) +{ + int anchor_LYhndl = -1; + LYUCcharset *anchor_UCI = NULL; + + if (anchor) { + anchor_LYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER); + if (anchor_LYhndl >= 0) + anchor_UCI = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_PARSER); + if (anchor_UCI && anchor_UCI->MIMEname) { + pushed_assume_MIMEname = UCAssume_MIMEcharset; + UCAssume_MIMEcharset = NULL; + if (HTCJK == JAPANESE) + StrAllocCopy(UCAssume_MIMEcharset, pushed_assume_MIMEname); + else + StrAllocCopy(UCAssume_MIMEcharset, anchor_UCI->MIMEname); + pushed_assume_LYhndl = anchor_LYhndl; + /* some diagnostics */ + if (UCLYhndl_for_unspec != anchor_LYhndl) + CTRACE((tfp, + "LYUCPushAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + anchor_LYhndl)); + UCLYhndl_for_unspec = anchor_LYhndl; + return; + } + } + pushed_assume_LYhndl = -1; + FREE(pushed_assume_MIMEname); +} + +/* + * Restore the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec used for + * charset "assuming" from the values saved by LYUCPushAssumed, if any. - kw + */ +int LYUCPopAssumed(void) +{ + if (pushed_assume_LYhndl >= 0) { + /* some diagnostics */ + if (UCLYhndl_for_unspec != pushed_assume_LYhndl) + CTRACE((tfp, + "LYUCPopAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + pushed_assume_LYhndl)); + UCLYhndl_for_unspec = pushed_assume_LYhndl; + pushed_assume_LYhndl = -1; + FREE(UCAssume_MIMEcharset); + UCAssume_MIMEcharset = pushed_assume_MIMEname; + pushed_assume_MIMEname = NULL; + return UCLYhndl_for_unspec; + } + return -1; +} + +/* Load a document HTLoad() + * --------------- + * + * This is an internal routine, which has an address AND a matching + * anchor. (The public routines are called with one OR the other.) + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns <0 Error has occurred. + * HT_LOADED Success + * HT_NO_DATA Success, but no document loaded. + * (telnet session started etc) + */ +static int HTLoad(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTProtocol *p; + int status = get_physical(addr, anchor); + + if (reloading) { + FREE(anchor->charset); + FREE(anchor->UCStages); + } + + if (status == HT_FORBIDDEN) { + /* prevent crash if telnet or similar was forbidden by rule. - kw */ + LYFixCursesOn("show alert:"); + status = HTLoadError(sink, 500, gettext("Access forbidden by rule")); + } else if (status == HT_REDIRECTING) { + ; /* fake redirection by rule, to redirecting_url */ + } else if (status >= 0) { + /* prevent crash if telnet or similar mapped or proxied by rule. - kw */ + LYFixCursesOnForAccess(addr, HTAnchor_physical(anchor)); + p = (HTProtocol *) HTAnchor_protocol(anchor); + anchor->parent->underway = TRUE; /* Hack to deal with caching */ + status = p->load(HTAnchor_physical(anchor), + anchor, format_out, sink); + anchor->parent->underway = FALSE; + LYUCPopAssumed(); + } + return status; +} + +/* Get a save stream for a document HTSaveStream() + * -------------------------------- + */ +HTStream *HTSaveStream(HTParentAnchor *anchor) +{ + HTProtocol *p = (HTProtocol *) HTAnchor_protocol(anchor); + + if (!p) + return NULL; + + return p->saveStream(anchor); +} + +int redirection_limit = 10; +int redirection_attempts = 0; /* counter in HTLoadDocument */ + +static BOOL too_many_redirections(void) +{ + if (redirection_attempts > redirection_limit) { + char *msg = NULL; + + HTSprintf0(&msg, TOO_MANY_REDIRECTIONS, redirection_limit); + free(msg); + redirection_attempts = 0; + return TRUE; + } + return FALSE; +} + +/* Load a document - with logging etc HTLoadDocument() + * ---------------------------------- + * + * - Checks or documents already loaded + * - Logs the access + * - Allows stdin filter option + * - Trace output and error messages + * + * On Entry, + * anchor is the node_anchor for the document + * full_address The address of the document to be accessed. + * filter if YES, treat stdin as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +static BOOL HTLoadDocument(const char *full_address, /* may include #fragment */ + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + HText *text; + const char *address_to_load = full_address; + char *cp; + BOOL ForcingNoCache = LYforce_no_cache; + + CTRACE((tfp, "HTAccess: loading document %s\n", NonNull(address_to_load))); + if (isEmpty(address_to_load)) + return NO; + + /* + * Free use_this_url_instead and reset permanent_redirection if not done + * elsewhere. - FM + */ + FREE(use_this_url_instead); + permanent_redirection = FALSE; + + if (too_many_redirections()) { + return NO; + } + + /* + * If this is marked as an internal link but we don't have the document + * loaded any more, and we haven't explicitly flagged that we want to + * reload with LYforce_no_cache, then something has disappeared from the + * cache when we expected it to be still there. The user probably doesn't + * expect a new network access. So if we have POST data and safe is not + * set in the anchor, ask for confirmation, and fail if not granted. The + * exception are LYNXIMGMAP documents, for which we defer to LYLoadIMGmap + * for prompting if necessary. - kw + */ + text = (HText *) HTAnchor_document(anchor); + if (LYinternal_flag && !text && !LYforce_no_cache && + anchor->post_data && !anchor->safe && + !isLYNXIMGMAP(full_address) && + HTConfirm(gettext("Document with POST content not found in cache. Resubmit?")) + != TRUE) { + return NO; + } + + /* + * If we don't have POST content, check whether this is a previous + * redirecting URL, and keep re-checking until we get to the final + * destination or redirection limit. If we do have POST content, we didn't + * allow permanent redirection, and an interactive user will be deciding + * whether to keep redirecting. - FM + */ + if (!anchor->post_data) { + while ((cp = HTAnchor_physical(anchor)) != NULL && + !StrNCmp(cp, "Location=", 9)) { + DocAddress NewDoc; + + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + anchor->address)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", cp + 9)); + + /* + * Don't exceed the redirection_attempts limit. - FM + */ + ++redirection_attempts; + if (too_many_redirections()) { + FREE(use_this_url_instead); + return NO; + } + + /* + * Set up the redirection. - FM + */ + StrAllocCopy(use_this_url_instead, cp + 9); + NewDoc.address = use_this_url_instead; + NewDoc.post_data = NULL; + NewDoc.post_content_type = NULL; + NewDoc.bookmark = anchor->bookmark; + NewDoc.isHEAD = anchor->isHEAD; + NewDoc.safe = anchor->safe; + anchor = HTAnchor_findAddress(&NewDoc); + } + } + /* + * If we had previous redirection, go back and check out that the URL under + * the current restrictions. - FM + */ + if (use_this_url_instead) { + FREE(redirecting_url); + return (NO); + } + + /* + * See if we can use an already loaded document. + */ + text = (HText *) HTAnchor_document(anchor); + if (text && !LYforce_no_cache) { + /* + * We have a cached rendition of the target document. Check if it's OK + * to re-use it. We consider it OK if: + * (1) the anchor does not have the no_cache element set, or + * (2) we've overridden it, e.g., because we are acting on a PREV_DOC + * command or a link in the History Page and it's not a reply from a + * POST with the LYresubmit_posts flag set, or + * (3) we are repositioning within the currently loaded document based + * on the target anchor's address (URL_Reference). + * + * If track_internal_links is false, HText_AreDifferent() is + * used to determine whether (3) applies. If the target address + * differs from that of the current document only by a fragment and the + * target address has an appended fragment, repositioning without + * reloading is always assumed. Note that HText_AreDifferent() + * currently always returns TRUE if the target has a LYNXIMGMAP URL, so + * that an internally generated pseudo-document will normally not be + * re-used unless condition (2) applies. (Condition (1) cannot apply + * since in LYMap.c, no_cache is always set in the anchor object). + * This doesn't guarantee that the resource from which the MAP element + * is taken will be read again (reloaded) when the list of links for a + * client-side image map is regenerated, when in some cases it should + * (e.g., user requested RELOAD, or HTTP response with no-cache header + * and we are not overriding). + * + * If track_internal_links is true, a target address that + * points to the same URL as the current document may still result in + * reloading, depending on whether the original URL-Reference was given + * as an internal link in the context of the previously loaded + * document. HText_AreDifferent() is not used here for testing whether + * we are just repositioning. For an internal link, the potential + * callers of this function from mainloop() down will either avoid + * making the call (and do the repositioning differently) or set + * LYinternal_flag (or LYoverride_no_cache). Note that (a) LYNXIMGMAP + * pseudo-documents and (b) The "List Page" document are treated + * logically as being part of the document on which they are based, for + * the purpose of whether to treat a link as internal, but the logic + * for this (by setting LYinternal_flag as necessary) is implemented + * elsewhere. There is a specific test for LYNXIMGMAP here so that the + * generated pseudo-document will not be re-used unless + * LYoverride_no_cache is set. The same caveat as above applies w.r.t. + * reloading of the underlying resource. + * + * We also should be checking other aspects of cache regulation (e.g., + * based on an If-Modified-Since check, etc.) but the code for doing + * those other things isn't available yet. + */ + if ((reloading != REAL_RELOAD) && + (LYoverride_no_cache || + ((!track_internal_links && + (!HText_hasNoCacheSet(text) || + !HText_AreDifferent(anchor, full_address))) || + (track_internal_links && + (((LYinternal_flag || !HText_hasNoCacheSet(text)) && + !isLYNXIMGMAP(full_address))))))) { + CTRACE((tfp, "HTAccess: Document already in memory.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } else { + ForcingNoCache = YES; + BStrFree(anchor->post_data); + CTRACE((tfp, "HTAccess: Auto-reloading document.\n")); + } + } + + if (HText_HaveUserChangedForms(text)) { + /* + * Issue a warning. User forms content will be lost. + * Will not restore changed forms, currently. + */ + HTAlert(RELOADING_FORM); + } + + /* + * Get the document from the net. If we are auto-reloading, the mutable + * anchor elements from the previous rendition should be freed in + * conjunction with loading of the new rendition. - FM + */ + LYforce_no_cache = NO; /* reset after each time through */ + if (ForcingNoCache) { + FREE(anchor->title); /* ??? */ + } + status = HTLoad(address_to_load, anchor, format_out, sink); + CTRACE((tfp, "HTAccess: status=%d\n", status)); + + /* + * RECOVERY: if the loading failed, and we had a cached HText copy, and no + * new HText created - use a previous copy, issue a warning. + */ + if (text && status < 0 && (HText *) HTAnchor_document(anchor) == text) { + HTAlert(gettext("Loading failed, use a previous copy.")); + CTRACE((tfp, "HTAccess: Loading failed, use a previous copy.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } + + /* + * Log the access if necessary. + */ + if (HTlogfile) { + time_t theTime; + + time(&theTime); + fprintf(HTlogfile, "%24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address); + fflush(HTlogfile); /* Actually update it on disk */ + CTRACE((tfp, "Log: %24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address)); + } + + /* + * Check out what we received from the net. + */ + if (status == HT_REDIRECTING) { + /* Exported from HTMIME.c, of all places. */ + /* NO!! - FM */ + /* + * Doing this via HTMIME.c meant that the redirection cover page was + * already loaded before we learned that we want a different URL. + * Also, changing anchor->address, as Lynx was doing, meant we could + * never again access its hash table entry, creating an insolvable + * memory leak. Instead, if we had a 301 status and set + * permanent_redirection, we'll load the new URL in anchor->physical, + * preceded by a token, which we can check to make replacements on + * subsequent access attempts. We'll check recursively, and retrieve + * the final URL if we had multiple redirections to it. If we just + * went to HTLoad now, as Lou originally had this, we couldn't do + * Lynx's security checks and alternate handling of some URL types. + * So, instead, we'll go all the way back to the top of getfile in + * LYGetFile.c when the status is HT_REDIRECTING. This may seem + * bizarre, but it works like a charm! - FM + * + * Actually, the location header for redirections is now again picked + * up in HTMIME.c. But that's an internal matter between HTTP.c and + * HTMIME.c, is still under control of HTLoadHTTP for http URLs, is + * done in a way that doesn't load the redirection response's body + * (except when wanted as an error fallback), and thus need not concern + * us here. - kw 1999-12-02 + */ + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + address_to_load)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", + redirecting_url)); + /* + * Prevent circular references. + */ + if (strcmp(address_to_load, redirecting_url)) { /* if different */ + /* + * Load token and redirecting url into anchor->physical if we had + * 301 Permanent redirection. HTTP.c does not allow this if we + * have POST content. - FM + */ + if (permanent_redirection) { + StrAllocCopy(anchor->physical, "Location="); + StrAllocCat(anchor->physical, redirecting_url); + } + + /* + * Set up flags before return to getfile. - FM + */ + StrAllocCopy(use_this_url_instead, redirecting_url); + if (ForcingNoCache) + LYforce_no_cache = YES; + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (NO); + } + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (YES); + } + + /* + * We did not receive a redirecting URL. - FM + */ + redirection_attempts = 0; + FREE(redirecting_url); + permanent_redirection = FALSE; + + if (status == HT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed.\n", + full_address)); + return YES; + } + if (status == HT_PARTIAL_CONTENT) { + HTAlert(gettext("Loading incomplete.")); + CTRACE((tfp, "HTAccess: `%s' has been accessed, partial content.\n", + full_address)); + return YES; + } + + if (status == HT_NO_DATA) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data left.\n", + full_address)); + return NO; + } + + if (status == HT_NOT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data loaded.\n", + full_address)); + return NO; + } + + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTAccess: `%s' has been accessed, transfer interrupted.\n", + full_address)); + return NO; + } + + if (status > 0) { + /* + * If you get this, then please find which routine is returning a + * positive unrecognized error code! + */ + fprintf(stderr, + gettext("**** HTAccess: socket or file number returned by obsolete load routine!\n")); + fprintf(stderr, + gettext("**** HTAccess: Internal software error. Please mail lynx-dev@nongnu.org!\n")); + fprintf(stderr, gettext("**** HTAccess: Status returned was: %d\n"), status); + exit_immediately(EXIT_FAILURE); + } + + /* Failure in accessing a document */ + cp = NULL; + StrAllocCopy(cp, gettext("Can't Access")); + StrAllocCat(cp, " `"); + StrAllocCat(cp, full_address); + StrAllocCat(cp, "'"); + _HTProgress(cp); + FREE(cp); + + CTRACE((tfp, "HTAccess: Can't access `%s'\n", full_address)); + HTLoadError(sink, 500, gettext("Unable to access document.")); + return NO; +} /* HTLoadDocument */ + +/* Load a document from absolute name. HTLoadAbsolute() + * ----------------------------------- + * + * On Entry, + * addr The absolute address of the document to be accessed. + * filter if YES, treat document as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadAbsolute(const DocAddress *docaddr) +{ + BOOL result; + HTParentAnchor *anchor = HTAnchor_findAddress(docaddr); + + result = HTLoadDocument(docaddr->address, + anchor, + (HTOutputFormat ? HTOutputFormat : WWW_PRESENT), + HTOutputStream); + if (!result) { + HTAnchor_delete(anchor->parent); + } + return result; +} + +#ifdef NOT_USED_CODE +/* Load a document from absolute name to stream. HTLoadToStream() + * --------------------------------------------- + * + * On Entry, + * addr The absolute address of the document to be accessed. + * sink if non-NULL, send data down this stream + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadToStream(const char *addr, + BOOL filter, + HTStream *sink) +{ + return HTLoadDocument(addr, + HTAnchor_findSimpleAddress(addr), + (HTOutputFormat ? HTOutputFormat : WWW_PRESENT), + sink); +} +#endif /* NOT_USED_CODE */ + +/* Load a document from relative name. HTLoadRelative() + * ----------------------------------- + * + * On Entry, + * relative_name The relative address of the document + * to be accessed. + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here) +{ + DocAddress full_address; + BOOL result; + char *mycopy = NULL; + char *stripped = NULL; + + full_address.address = NULL; + full_address.post_data = NULL; + full_address.post_content_type = NULL; + full_address.bookmark = NULL; + full_address.isHEAD = FALSE; + full_address.safe = FALSE; + + StrAllocCopy(mycopy, relative_name); + + stripped = HTStrip(mycopy); + full_address.address = + HTParse(stripped, + here->address, + PARSE_ALL_WITHOUT_ANCHOR); + result = HTLoadAbsolute(&full_address); + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. - FM + */ + FREE(full_address.address); + FREE(mycopy); /* Memory leak fixed 10/7/92 -- JFG */ + return result; +} + +/* Load if necessary, and select an anchor. HTLoadAnchor() + * ---------------------------------------- + * + * On Entry, + * destination The child or parent anchor to be loaded. + * + * On Exit, + * returns YES Success + * NO Failure + */ +BOOL HTLoadAnchor(HTAnchor * destination) +{ + HTParentAnchor *parent; + BOOL loaded = NO; + + if (!destination) + return NO; /* No link */ + + parent = HTAnchor_parent(destination); + + if (HTAnchor_document(parent) == NULL) { /* If not already loaded */ + /* TBL 921202 */ + BOOL result; + + result = HTLoadDocument(parent->address, + parent, + HTOutputFormat ? + HTOutputFormat : WWW_PRESENT, + HTOutputStream); + if (!result) + return NO; + loaded = YES; + } { + HText *text = (HText *) HTAnchor_document(parent); + + if ((destination != (HTAnchor *) parent) && + (destination != (HTAnchor *) (parent->parent))) { + /* If child anchor */ + HText_selectAnchor(text, /* Double display? @@ */ + (HTChildAnchor *) destination); + } else { + if (!loaded) + HText_select(text); + } + } + return YES; + +} /* HTLoadAnchor */ + +/* Search. HTSearch() + * ------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * here is anchor search is to be done on. + */ +static char hex(int i) +{ + const char *hexchars = "0123456789ABCDEF"; + + return hexchars[i]; +} + +BOOL HTSearch(const char *keywords, + HTParentAnchor *here) +{ +#define acceptable \ +"1234567890abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_" + + char *q, *u; + const char *p, *s, *e; /* Pointers into keywords */ + char *address = NULL; + BOOL result; + char *escaped = typecallocn(char, (strlen(keywords) * 3) + 1); + static const BOOL isAcceptable[96] = + /* *INDENT-OFF* */ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0, /* 2x !"#$%&'()*+,-./ */ + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4x @ABCDEFGHIJKLMNO */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6x `abcdefghijklmno */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; /* 7X pqrstuvwxyz{\}~ DEL */ + /* *INDENT-ON* */ + + if (escaped == NULL) + outofmem(__FILE__, "HTSearch"); + + if (here->isIndexAction == NULL) { + free(escaped); + return FALSE; + } + StrAllocCopy(address, here->isIndexAction); + + /* + * Convert spaces to + and hex escape unacceptable characters. + */ + for (s = keywords; *s && WHITE(*s); s++) /* Scan */ + ; /* Skip white space */ + for (e = s + strlen(s); e > s && WHITE(*(e - 1)); e--) /* Scan */ + ; /* Skip trailers */ + for (q = escaped, p = s; p < e; p++) { /* Scan stripped field */ + unsigned char c = UCH(TOASCII(*p)); + + if (WHITE(*p)) { + *q++ = '+'; + } else if (IS_CJK_TTY) { + *q++ = *p; + } else if (c >= 32 && c <= UCH(127) && isAcceptable[c - 32]) { + *q++ = *p; /* 930706 TBL for MVS bug */ + } else { + *q++ = '%'; + *q++ = hex((int) (c >> 4)); + *q++ = hex((int) (c & 15)); + } + } /* Loop over string */ + *q = '\0'; /* Terminate escaped string */ + u = StrChr(address, '?'); /* Find old search string */ + if (u != NULL) + *u = '\0'; /* Chop old search off */ + + StrAllocCat(address, "?"); + StrAllocCat(address, escaped); + FREE(escaped); + result = HTLoadRelative(address, here); + FREE(address); + + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. Only an http server (not a gopher or wais server) could + * return redirection. Lynx will go all the way back to its mainloop() and + * subject a redirecting URL to all of its security and restrictions + * checks. - FM + */ + return result; +} + +/* Search Given Indexname. HTSearchAbsolute() + * ----------------------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * *indexname is name of object search is to be done on. + */ +BOOL HTSearchAbsolute(const char *keywords, + char *indexname) +{ + DocAddress abs_doc; + HTParentAnchor *anchor; + + abs_doc.address = indexname; + abs_doc.post_data = NULL; + abs_doc.post_content_type = NULL; + abs_doc.bookmark = NULL; + abs_doc.isHEAD = FALSE; + abs_doc.safe = FALSE; + + anchor = HTAnchor_findAddress(&abs_doc); + return HTSearch(keywords, anchor); +} + +#ifdef NOT_USED_CODE +/* Generate the anchor for the home page. HTHomeAnchor() + * -------------------------------------- + * + * As it involves file access, this should only be done once + * when the program first runs. + * This is a default algorithm -- browser don't HAVE to use this. + * But consistency between browsers is STRONGLY recommended! + * + * Priority order is: + * 1 WWW_HOME environment variable (logical name, etc) + * 2 ~/WWW/default.html + * 3 /usr/local/bin/default.html + * 4 http://www.w3.org/default.html + */ +HTParentAnchor *HTHomeAnchor(void) +{ + char *my_home_document = NULL; + char *home = LYGetEnv(LOGICAL_DEFAULT); + char *ref; + HTParentAnchor *anchor; + + if (home) { + StrAllocCopy(my_home_document, home); +#define MAX_FILE_NAME 1024 /* @@@ */ + } else if (HTClientHost) { /* Telnet server */ + /* + * Someone telnets in, they get a special home. + */ + FILE *fp = fopen(REMOTE_POINTER, "r"); + char *status; + + if (fp) { + my_home_document = typecallocn(char, MAX_FILE_NAME); + + if (my_home_document == NULL) + outofmem(__FILE__, "HTHomeAnchor"); + status = fgets(my_home_document, MAX_FILE_NAME, fp); + if (!status) { + FREE(my_home_document); + } + fclose(fp); + } + if (my_home_document == NULL) + StrAllocCopy(my_home_document, REMOTE_ADDRESS); + } +#ifdef UNIX + if (my_home_document == NULL) { + FILE *fp = NULL; + char *home = LYGetEnv("HOME"); + + if (home != 0) { + HTSprintf0(&my_home_document, "%s/%s", home, PERSONAL_DEFAULT); + fp = fopen(my_home_document, "r"); + } + + if (!fp) { + StrAllocCopy(my_home_document, LOCAL_DEFAULT_FILE); + fp = fopen(my_home_document, "r"); + } + if (fp) { + fclose(fp); + } else { + CTRACE((tfp, "HTBrowse: No local home document ~/%s or %s\n", + PERSONAL_DEFAULT, LOCAL_DEFAULT_FILE)); + FREE(my_home_document); + } + } +#endif /* UNIX */ + ref = HTParse((my_home_document ? + my_home_document : (HTClientHost ? + REMOTE_ADDRESS : LAST_RESORT)), + STR_FILE_URL, + PARSE_ALL_WITHOUT_ANCHOR); + if (my_home_document) { + CTRACE((tfp, "HTAccess: Using custom home page %s i.e., address %s\n", + my_home_document, ref)); + FREE(my_home_document); + } + anchor = HTAnchor_findSimpleAddress(ref); + FREE(ref); + return anchor; +} +#endif /* NOT_USED_CODE */ diff --git a/WWW/Library/Implementation/HTAccess.h b/WWW/Library/Implementation/HTAccess.h new file mode 100644 index 0000000..b898535 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.h @@ -0,0 +1,268 @@ +/* + * $LynxId: HTAccess.h,v 1.21 2019/08/23 23:51:45 tom Exp $ + * HTAccess: Access manager for libwww + * ACCESS MANAGER + * + * This module keeps a list of valid protocol (naming scheme) specifiers with + * associated access code. It allows documents to be loaded given various + * combinations of parameters. New access protocols may be registered at any + * time. + * + * Part of the libwww library . + * + */ +#ifndef HTACCESS_H +#define HTACCESS_H + +/* Definition uses: +*/ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern char *use_this_url_instead; + + extern int redirection_limit; + extern int redirection_attempts; + +/* Return codes from load routines: + * + * These codes may be returned by the protocol modules, + * and by the HTLoad routines. + * In general, positive codes are OK and negative ones are bad. + */ + +/* + +Default Addresses + + These control the home page selection. To mess with these for normal browses is asking + for user confusion. + + */ +#define LOGICAL_DEFAULT "WWW_HOME" /* Defined to be the home page */ + +#ifndef PERSONAL_DEFAULT +#define PERSONAL_DEFAULT "WWW/default.html" /* in home directory */ +#endif +#ifndef LOCAL_DEFAULT_FILE +#define LOCAL_DEFAULT_FILE "/usr/local/lib/WWW/default.html" +#endif +/* If one telnets to a www access point, + it will look in this file for home page */ +#ifndef REMOTE_POINTER +#define REMOTE_POINTER "/etc/www-remote.url" /* can't be file */ +#endif +/* and if that fails it will use this. */ +#ifndef REMOTE_ADDRESS +#define REMOTE_ADDRESS "http://www.w3.org/remote.html" /* can't be file */ +#endif + +/* If run from telnet daemon and no -l specified, use this file: +*/ +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "/usr/adm/www-log/www-log" +#endif + +/* If the home page isn't found, use this file: +*/ +#ifndef LAST_RESORT +#define LAST_RESORT "http://www.w3.org/default.html" +#endif + +/* + +Flags which may be set to control this module + + */ +#ifdef NOT + extern int HTDiag; /* Flag: load source as plain text */ +#endif /* NOT */ + extern char *HTClientHost; /* Name or number of telnetting host */ + extern FILE *HTlogfile; /* File to output one-liners to */ + extern BOOL HTSecure; /* Disable security holes? */ + extern BOOL HTPermitRedir; /* Special flag for getfile() */ + extern HTStream *HTOutputStream; /* For non-interactive, set this */ + extern HTFormat HTOutputFormat; /* To convert on load, set this */ + +/* Check for proxy override. override_proxy() + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ + extern BOOL override_proxy(const char *addr); + +/* + +Load a document from relative name + + ON ENTRY, + relative_name The relative address of the file to be accessed. + here The anchor of the object being searched + + ON EXIT, + returns YES Success in opening file + NO Failure + + */ + extern BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here); + +/* + +Load a document from absolute name + + ON ENTRY, + addr The absolute address of the document to be accessed. + filter_it if YES, treat document as HTML + + ON EXIT, + returns YES Success in opening document + NO Failure + + */ + extern BOOL HTLoadAbsolute(const DocAddress *addr); + +/* + +Load a document from absolute name to a stream + + ON ENTRY, + addr The absolute address of the document to be accessed. + filter_it if YES, treat document as HTML + + ON EXIT, + returns YES Success in opening document + NO Failure + + Note: This is equivalent to HTLoadDocument + + */ + extern BOOL HTLoadToStream(const char *addr, BOOL filter_it, + HTStream *sink); + +/* + +Load if necessary, and select an anchor + + ON ENTRY, + destination The child or parent anchor to be loaded. + + ON EXIT, + returns YES Success + returns NO Failure + + */ + extern BOOL HTLoadAnchor(HTAnchor * destination); + +/* + +Make a stream for Saving object back + + ON ENTRY, + anchor is valid anchor which has previously been loaded + + ON EXIT, + returns 0 if error else a stream to save the object to. + + */ + extern HTStream *HTSaveStream(HTParentAnchor *anchor); + +/* + +Search + + Performs a search on word given by the user. Adds the search words to the end of the + current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + here The anchor of the object being searched + + */ + extern BOOL HTSearch(const char *keywords, HTParentAnchor *here); + +/* + +Search Given Indexname + + Performs a keyword search on word given by the user. Adds the keyword to the end of + the current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + *indexname is name of object search is to be done on. + + */ + extern BOOL HTSearchAbsolute(const char *keywords, + char *indexname); + +/* + +Register an access method + + */ + + typedef struct _HTProtocol { + const char *name; + + int (*load) (const char *full_address, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + + HTStream *(*saveStream) (HTParentAnchor *anchor); + + } HTProtocol; + + extern BOOL HTRegisterProtocol(HTProtocol * protocol); + +/* + +Generate the anchor for the home page + + */ + +/* + + As it involves file access, this should only be done once when the program first runs. + This is a default algorithm -- browser don't HAVE to use this. + + */ + extern HTParentAnchor *HTHomeAnchor(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +/* + +For registering protocols supported by Lynx + +*/ + extern void LYRegisterLynxProtocols(void); + + extern void LYUCPushAssumed(HTParentAnchor *anchor); + extern int LYUCPopAssumed(void); + + extern BOOL using_proxy; /* Are we using an NNTP proxy? */ + +#ifdef __cplusplus +} +#endif +#endif /* HTACCESS_H */ 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 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); +} diff --git a/WWW/Library/Implementation/HTAnchor.h b/WWW/Library/Implementation/HTAnchor.h new file mode 100644 index 0000000..3b1e6e6 --- /dev/null +++ b/WWW/Library/Implementation/HTAnchor.h @@ -0,0 +1,412 @@ +/* + * $LynxId: HTAnchor.h,v 1.40 2018/03/11 18:43:50 tom Exp $ + * + * Hypertext "Anchor" Object HTAnchor.h + * ========================== + * + * An anchor represents a region of a hypertext document which is linked + * to another anchor in the same or a different document. + */ + +#ifndef HTANCHOR_H +#define HTANCHOR_H + +/* Version 0 (TBL) written in Objective-C for the NeXT browser */ +/* Version 1 of 24-Oct-1991 (JFG), written in C, browser-independent */ + +#include +#include +#include +#include +#include + +typedef struct _HyperDoc HyperDoc; /* Ready for forward references */ +typedef struct _HTAnchor HTAnchor; +typedef struct _HTParentAnchor HTParentAnchor; +typedef struct _HTParentAnchor0 HTParentAnchor0; + +#include + +#ifdef __cplusplus +extern "C" { +#endif + struct _HTAnchor { + /* Generic anchor */ + HTParentAnchor0 *parent; /* Parent of this anchor (self for adults) */ + }; + +#define HASH_TYPE unsigned short + + struct _HTParentAnchor0 { /* One for adult_table, + * generally not used outside HTAnchor.c */ + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* (self) */ + + /* ParentAnchor0-specific information */ + char *address; /* Absolute address of this node */ + HTParentAnchor *info; /* additional info, allocated on demand */ + + HTBTree *children; /* Subanchors , sorted by tag */ + HTList sources; /* List of anchors pointing to this, if any */ + + HTList _add_adult; /* - just a memory for list entry:) */ + HASH_TYPE adult_hash; /* adult list number */ + BOOL underway; /* Document about to be attached to it */ + }; + + /* + * Separated from the above to save memory: allocated on demand, + * it is nearly 1:1 to HText (well, sometimes without HText...), + * available for SGML, HTML, and HText stages. + * [being precise, we currently allocate it before HTLoadDocument(), + * in HTAnchor_findAddress() and HTAnchor_parent()]. + */ + struct _HTParentAnchor { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ParentAnchor-specific information */ + HTList children_notag; /* Subanchors , tag is NULL */ + HyperDoc *document; /* The document within which this is an anchor */ + + char *address; /* parent->address, a pointer */ + bstring *post_data; /* Posting data */ + char *post_content_type; /* Type of post data */ + char *bookmark; /* Bookmark filename */ + HTFormat format; /* Pointer to node format descriptor */ + char *charset; /* Pointer to character set (kludge, for now */ + BOOL isIndex; /* Acceptance of a keyword search */ + char *isIndexAction; /* URL of isIndex server */ + char *isIndexPrompt; /* Prompt for isIndex query */ + char *title; /* Title of document */ + char *owner; /* Owner of document */ + char *RevTitle; /* TITLE in REV="made" or REV="owner" LINK */ + char *citehost; /* Citehost from REL="citehost" LINK */ +#ifdef USE_COLOR_STYLE + char *style; +#endif + + HTList *methods; /* Methods available as HTAtoms */ + void *protocol; /* Protocol object */ + char *physical; /* Physical address */ + BOOL isISMAPScript; /* Script for clickable image map */ + BOOL isHEAD; /* Document is headers from a HEAD request */ + BOOL safe; /* Safe */ +#ifdef USE_SOURCE_CACHE + char *source_cache_file; + HTChunk *source_cache_chunk; +#endif + char *FileCache; /* Path to a disk-cached copy (see src/HTFWriter.c) */ + char *SugFname; /* Suggested filename */ + char *cache_control; /* Cache-Control */ + BOOL no_cache; /* Cache-Control, Pragma or META "no-cache"? */ + BOOL inHEAD; /* HTMIMEConvert is decoding server-headers */ + BOOL inBASE; /* duplicated from HTStructured (HTML.c/h) */ + HTChunk http_headers; + BOOL no_content_encoding; /* server did not use C-T? */ + char *content_type_params; /* Content-Type (with parameters if any) */ + char *content_type; /* Content-Type */ + char *content_language; /* Content-Language */ + char *content_encoding; /* Compression algorithm */ + char *content_base; /* Content-Base */ + char *content_disposition; /* Content-Disposition */ + char *content_location; /* Content-Location */ + char *content_md5; /* Content-MD5 */ + char *message_id; /* Message-ID */ + char *subject; /* Subject */ + off_t header_length; /* length of headers */ + off_t content_length; /* Content-Length */ + off_t actual_length; /* actual length may differ */ + char *date; /* Date */ + char *expires; /* Expires */ + char *last_modified; /* Last-Modified */ + char *ETag; /* ETag (HTTP1.1 cache validator) */ + char *server; /* Server */ + UCAnchorInfo *UCStages; /* chartrans stages */ + HTList *imaps; /* client side image maps */ + }; + + typedef HTAtom HTLinkType; + + typedef struct { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ChildAnchor-specific information */ + char *tag; /* #fragment, relative to the parent */ + + HTAnchor *dest; /* The anchor to which this leads */ + HTLinkType *type; /* Semantics of this link */ + + HTList _add_children_notag; /* - just a memory for list entry:) */ + HTList _add_sources; /* - just a memory for list entry:) */ + } HTChildAnchor; + + /* + * DocAddress structure is used for loading an absolute anchor with all + * needed information including posting data and post content type. + */ + typedef struct _DocAddress { + char *address; + bstring *post_data; + char *post_content_type; + char *bookmark; + BOOL isHEAD; + BOOL safe; + } DocAddress; + + /* "internal" means "within the same document, with certainty". */ + extern HTLinkType *HTInternalLink; + + /* 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) + */ + extern 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 */ + + /* Create new or find old parent anchor + * ------------------------------------ + * + * This 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. + */ + extern HTParentAnchor *HTAnchor_findAddress(const DocAddress *address); + + /* 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 + */ + extern HTParentAnchor *HTAnchor_findSimpleAddress(const char *url); + + /* 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 their 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. + */ + extern BOOL HTAnchor_delete(HTParentAnchor0 *me); + + /* + * 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(). + */ + extern void HTAnchor_delete_links(HTParentAnchor *me); + +#ifdef USE_SOURCE_CACHE + extern void HTAnchor_clearSourceCache(HTParentAnchor *me); +#endif + + /* Data access functions + * --------------------- + */ + extern HTParentAnchor *HTAnchor_parent(HTAnchor * me); + + extern void HTAnchor_setDocument(HTParentAnchor *me, + HyperDoc *doc); + + extern HyperDoc *HTAnchor_document(HTParentAnchor *me); + + /* Returns the full URI of the anchor, child or parent + * as a malloc'd string to be freed by the caller. + */ + extern char *HTAnchor_address(HTAnchor * me); + + extern char *HTAnchor_short_address(HTAnchor * me); + + extern void HTAnchor_setFormat(HTParentAnchor *me, + HTFormat form); + + extern HTFormat HTAnchor_format(HTParentAnchor *me); + + extern void HTAnchor_setIndex(HTParentAnchor *me, + const char *address); + + extern void HTAnchor_setPrompt(HTParentAnchor *me, + const char *prompt); + + extern BOOL HTAnchor_isIndex(HTParentAnchor *me); + + extern BOOL HTAnchor_isISMAPScript(HTAnchor * me); + +#if defined(USE_COLOR_STYLE) + extern const char *HTAnchor_style(HTParentAnchor *me); + + extern void HTAnchor_setStyle(HTParentAnchor *me, + const char *style); +#endif + + /* Title handling. + */ + extern const char *HTAnchor_title(HTParentAnchor *me); + + extern void HTAnchor_setTitle(HTParentAnchor *me, + const char *title); + + extern void HTAnchor_appendTitle(HTParentAnchor *me, + const char *title); + + /* Bookmark handling. + */ + extern const char *HTAnchor_bookmark(HTParentAnchor *me); + + extern void HTAnchor_setBookmark(HTParentAnchor *me, + const char *bookmark); + + /* Owner handling. + */ + extern const char *HTAnchor_owner(HTParentAnchor *me); + + extern void HTAnchor_setOwner(HTParentAnchor *me, + const char *owner); + + /* TITLE handling in LINKs with REV="made" or REV="owner". - FM + */ + extern const char *HTAnchor_RevTitle(HTParentAnchor *me); + + extern void HTAnchor_setRevTitle(HTParentAnchor *me, + const char *title); + + /* Citehost for bibp links from LINKs with REL="citehost". - RDC + */ + extern const char *HTAnchor_citehost(HTParentAnchor *me); + + extern void HTAnchor_setCitehost(HTParentAnchor *me, + const char *citehost); + + /* Suggested filename handling. - FM + * (will be loaded if we had a Content-Disposition + * header or META element with filename=name.suffix) + */ + extern const char *HTAnchor_SugFname(HTParentAnchor *me); + + /* HTTP Headers. + */ + extern const char *HTAnchor_http_headers(HTParentAnchor *me); + + /* Content-Type handling (parameter list). + */ + extern const char *HTAnchor_content_type_params(HTParentAnchor *me); + + /* Content-Type handling. - FM + */ + extern const char *HTAnchor_content_type(HTParentAnchor *me); + + /* Content-Encoding handling. - FM + * (will be loaded if we had a Content-Encoding + * header.) + */ + extern const char *HTAnchor_content_encoding(HTParentAnchor *me); + + /* Last-Modified header handling. - FM + */ + extern const char *HTAnchor_last_modified(HTParentAnchor *me); + + /* Date header handling. - FM + */ + extern const char *HTAnchor_date(HTParentAnchor *me); + + /* Server header handling. - FM + */ + extern const char *HTAnchor_server(HTParentAnchor *me); + + /* Safe header handling. - FM + */ + extern BOOL HTAnchor_safe(HTParentAnchor *me); + + /* Content-Base header handling. - FM + */ + extern const char *HTAnchor_content_base(HTParentAnchor *me); + + /* Content-Location header handling. - FM + */ + extern const char *HTAnchor_content_location(HTParentAnchor *me); + + /* Message-ID, used for mail replies - kw + */ + extern const char *HTAnchor_messageID(HTParentAnchor *me); + + extern BOOL HTAnchor_setMessageID(HTParentAnchor *me, + const char *messageid); + + /* Subject, used for mail replies - kw + */ + extern const char *HTAnchor_subject(HTParentAnchor *me); + + extern BOOL HTAnchor_setSubject(HTParentAnchor *me, + const char *subject); + + /* Manipulation of links + * --------------------- + */ + extern HTAnchor *HTAnchor_followLink(HTChildAnchor *me); + + extern HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me, + HTLinkType *type); + + /* Read and write methods + * ---------------------- + */ + extern HTList *HTAnchor_methods(HTParentAnchor *me); + + /* Protocol + * -------- + */ + extern void *HTAnchor_protocol(HTParentAnchor *me); + + extern void HTAnchor_setProtocol(HTParentAnchor *me, + void *protocol); + + /* Physical address + * ---------------- + */ + extern char *HTAnchor_physical(HTParentAnchor *me); + + extern void HTAnchor_setPhysical(HTParentAnchor *me, + char *protocol); + + extern LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me, + int which_stage); + + extern int HTAnchor_getUCLYhndl(HTParentAnchor *me, + int which_stage); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me, + int to_stage, + int from_stage, + int set_by); + + extern void ImageMapList_free(HTList *list); + +#ifdef __cplusplus +} +#endif +#endif /* HTANCHOR_H */ diff --git a/WWW/Library/Implementation/HTAssoc.c b/WWW/Library/Implementation/HTAssoc.c new file mode 100644 index 0000000..831b196 --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.c @@ -0,0 +1,82 @@ +/* + * $LynxId: HTAssoc.c,v 1.11 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAssoc.c + * ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS. + * NAMES NOT CASE SENSITIVE, AND ONLY COMMON LENGTH + * IS CHECKED (allows abbreviations; well, length is + * taken from lookup-up name, so if table contains + * a shorter abbrev it is not found). + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * + * + * BUGS: + * + * + */ + +#include + +#include + +#include + +HTAssocList *HTAssocList_new(void) +{ + return HTList_new(); +} + +void HTAssocList_delete(HTAssocList *alist) +{ + if (alist) { + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + FREE(assoc->name); + FREE(assoc->value); + FREE(assoc); + } + HTList_delete(alist); + alist = NULL; + } +} + +void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value) +{ + HTAssoc *assoc; + + if (alist) { + if (!(assoc = (HTAssoc *) malloc(sizeof(HTAssoc)))) + outofmem(__FILE__, "HTAssoc_add"); + + assoc->name = NULL; + assoc->value = NULL; + + if (name) + StrAllocCopy(assoc->name, name); + if (value) + StrAllocCopy(assoc->value, value); + HTList_addObject(alist, (void *) assoc); + } else { + CTRACE((tfp, "HTAssoc_add: ERROR: assoc list NULL!!\n")); + } +} + +char *HTAssocList_lookup(HTAssocList *alist, + const char *name) +{ + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + if (!strncasecomp(assoc->name, name, (int) strlen(name))) + return assoc->value; + } + return NULL; +} diff --git a/WWW/Library/Implementation/HTAssoc.h b/WWW/Library/Implementation/HTAssoc.h new file mode 100644 index 0000000..327809c --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.h @@ -0,0 +1,35 @@ +/* ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS + + Lookups from association list are not case-sensitive. + + */ + +#ifndef HTASSOC_H +#define HTASSOC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef HTList HTAssocList; + + typedef struct { + char *name; + char *value; + } HTAssoc; + + extern HTAssocList *HTAssocList_new(void); + extern void HTAssocList_delete(HTAssocList *alist); + + extern void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value); + + extern char *HTAssocList_lookup(HTAssocList *alist, + const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTASSOC_H */ diff --git a/WWW/Library/Implementation/HTAtom.c b/WWW/Library/Implementation/HTAtom.c new file mode 100644 index 0000000..07eca77 --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.c @@ -0,0 +1,107 @@ +/* + * $LynxId: HTAtom.c,v 1.22 2018/03/06 09:46:58 tom Exp $ + * + * Atoms: Names to numbers HTAtom.c + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and comparisons + * for equality done more efficiently. + * + * Atoms are kept in a hash table consisting of an array of linked lists. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#include +#include +#include +#include +#include + +#define HASH_SIZE 101 /* Arbitrary prime. Memory/speed tradeoff */ + +static HTAtom *hash_table[HASH_SIZE]; +static BOOL initialised = NO; + +/* + * To free off all atoms. + */ +#ifdef LY_FIND_LEAKS +static void free_atoms(void); +#endif + +#define HASH_FUNCTION(cp_hash) ((strlen(cp_hash) * UCH(*cp_hash)) % HASH_SIZE) + +HTAtom *HTAtom_for(const char *string) +{ + size_t hash; + HTAtom *a; + + if (!initialised) { + memset(hash_table, 0, sizeof(hash_table)); + initialised = YES; +#ifdef LY_FIND_LEAKS + atexit(free_atoms); +#endif + } + + hash = HASH_FUNCTION(string); + + for (a = hash_table[hash]; a; a = a->next) { + if (0 == strcasecomp(a->name, string)) { + return a; + } + } + + a = (HTAtom *) malloc(sizeof(*a)); + if (a == NULL) + outofmem(__FILE__, "HTAtom_for"); + + a->name = (char *) malloc(strlen(string) + 1); + if (a->name == NULL) + outofmem(__FILE__, "HTAtom_for"); + + strcpy(a->name, string); + a->next = hash_table[hash]; + hash_table[hash] = a; + return a; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free off all atoms. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-29-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_atoms(void) +{ + auto int i_counter; + HTAtom *HTAp_freeme; + + /* + * Loop through all lists of atoms. + */ + for (i_counter = 0; i_counter < HASH_SIZE; i_counter++) { + /* + * Loop through the list. + */ + while (hash_table[i_counter] != NULL) { + /* + * Free off atoms and any members. + */ + HTAp_freeme = hash_table[i_counter]; + hash_table[i_counter] = HTAp_freeme->next; + FREE(HTAp_freeme->name); + FREE(HTAp_freeme); + } + } +} +#endif /* LY_FIND_LEAKS */ diff --git a/WWW/Library/Implementation/HTAtom.h b/WWW/Library/Implementation/HTAtom.h new file mode 100644 index 0000000..0d787bc --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.h @@ -0,0 +1,53 @@ +/* */ + +/* Atoms: Names to numbers HTAtom.h + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and compaisons + * for equality done more efficiently. + * + * HTAtom_for(string) returns a representative value such that it + * will always (within one run of the program) return the same + * value for the same given string. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#ifndef HTATOM_H +#define HTATOM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTAtom HTAtom; + + struct _HTAtom { + HTAtom *next; + char *name; + }; /* struct _HTAtom */ + + extern HTAtom *HTAtom_for(const char *string); + +#define HTAtom_name(a) ((a)->name) + +/* + +The HTFormat type + + We use the HTAtom object for holding representations. This allows faster manipulation + (comparison and copying) that if we stayed with strings. + + */ + typedef HTAtom *HTFormat; + +#ifdef __cplusplus +} +#endif +#endif /* HTATOM_H */ diff --git a/WWW/Library/Implementation/HTBTree.c b/WWW/Library/Implementation/HTBTree.c new file mode 100644 index 0000000..f595bae --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.c @@ -0,0 +1,680 @@ +/* Binary Tree for sorting things + * ============================== + * Author: Arthur Secret + * + * 4 March 94: Bug fixed in the balancing procedure + * + */ + +#include +#include + +#define MAXIMUM(a,b) ((a)>(b)?(a):(b)) + +#include + +/********************************************************* + * This function returns an HTBTree with memory allocated + * for it when given a mean to compare things + */ +HTBTree *HTBTree_new(HTComparer comp) +{ + HTBTree *tree = typeMalloc(HTBTree); + + if (tree == NULL) + outofmem(__FILE__, "HTBTree_new"); + + tree->compare = comp; + tree->top = NULL; + + return tree; +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElement_free(HTBTElement *element) +{ + if (element) { + if (element->left != NULL) + HTBTElement_free(element->left); + if (element->right != NULL) + HTBTElement_free(element->right); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTree_free(HTBTree *tree) +{ + HTBTElement_free(tree->top); + FREE(tree); +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElementAndObject_free(HTBTElement *element) +{ + if (element) { /* Just in case nothing was in the tree anyway */ + if (element->left != NULL) + HTBTElementAndObject_free(element->left); + if (element->right != NULL) + HTBTElementAndObject_free(element->right); + FREE(element->object); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTreeAndObject_free(HTBTree *tree) +{ + HTBTElementAndObject_free(tree->top); + FREE(tree); +} + +/********************************************************************* + * Returns a pointer to equivalent object in a tree or NULL if none. + */ +void *HTBTree_search(HTBTree *tree, + void *object) +{ + HTBTElement *cur = tree->top; + int res; + + while (cur != NULL) { + res = tree->compare(object, cur->object); + + if (res == 0) + return cur->object; + else if (res < 0) + cur = cur->left; + else if (res > 0) + cur = cur->right; + } + return NULL; +} + +/********************************************************************* + * This void is the core of HTBTree.c . It will + * 1/ add a new element to the tree at the right place + * so that the tree remains sorted + * 2/ balance the tree to be as fast as possible when reading it + */ +void HTBTree_add(HTBTree *tree, + void *object) +{ + HTBTElement *father_of_element; + HTBTElement *added_element; + HTBTElement *forefather_of_element; + HTBTElement *father_of_forefather; + BOOL father_found, top_found; + int depth, depth2, corrections; + + /* father_of_element is a pointer to the structure that is the father of + * the new object "object". added_element is a pointer to the structure + * that contains or will contain the new object "object". + * father_of_forefather and forefather_of_element are pointers that are + * used to modify the depths of upper elements, when needed. + * + * father_found indicates by a value NO when the future father of "object" + * is found. top_found indicates by a value NO when, in case of a + * difference of depths < 2, the top of the tree is encountered and forbids + * any further try to balance the tree. corrections is an integer used to + * avoid infinite loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used here to show that it need not be the top of the tree. + */ + + /* + * 1/ Adding of the element to the binary tree + */ + + if (tree->top == NULL) { + tree->top = typeMalloc(HTBTElement); + + if (tree->top == NULL) + outofmem(__FILE__, "HTBTree_add"); + + tree->top->up = NULL; + tree->top->object = object; + tree->top->left = NULL; + tree->top->left_depth = 0; + tree->top->right = NULL; + tree->top->right_depth = 0; + } else { + father_found = YES; + father_of_element = tree->top; + added_element = NULL; + father_of_forefather = NULL; + forefather_of_element = NULL; + while (father_found) { + int res = tree->compare(object, father_of_element->object); + + if (res < 0) { + if (father_of_element->left != NULL) + father_of_element = father_of_element->left; + else { + father_found = NO; + father_of_element->left = typeMalloc(HTBTElement); + + if (father_of_element->left == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->left; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } else { /* res >= 0 */ + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + } else { + father_found = NO; + father_of_element->right = typeMalloc(HTBTElement); + + if (father_of_element->right == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->right; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } + } + + /* + * Changing of all depths that need to be changed + */ + father_of_forefather = father_of_element; + forefather_of_element = added_element; + do { + if (father_of_forefather->left == forefather_of_element) { + depth = father_of_forefather->left_depth; + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = father_of_forefather; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && (father_of_forefather != NULL)); + + /* + * 2/ Balancing the binary tree, if necessary + */ + top_found = YES; + corrections = 0; + while ((top_found) && (corrections < 7)) { + if ((abs(father_of_element->left_depth + - father_of_element->right_depth)) < 2) { + if (father_of_element->up != NULL) + father_of_element = father_of_element->up; + else + top_found = NO; + } else { /* We start the process of balancing */ + + corrections = corrections + 1; + /* + * corrections is an integer used to avoid infinite + * loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used to show that it need not be the top of the tree + * But let's avoid these two exceptions anyhow + * with the two following conditions (4 March 94 - AS) + */ + + if (father_of_element->left == NULL) { + if ((father_of_element->right != NULL) + && (father_of_element->right->right == NULL) + && (father_of_element->right->left != NULL) + && (father_of_element->right->left->left == NULL) + && (father_of_element->right->left->right == NULL)) { + corrections = 7; + } + } else { + if ((father_of_element->right == NULL) + && (father_of_element->left->left == NULL) + && (father_of_element->left->right != NULL) + && (father_of_element->left->right->right == NULL) + && (father_of_element->left->right->left == NULL)) { + corrections = 7; + } + } + + if ((father_of_element->left != NULL) + && (father_of_element->left_depth > father_of_element->right_depth)) { + added_element = father_of_element->left; + father_of_element->left_depth = added_element->right_depth; + added_element->right_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) { + /* Bug fixed in March 94 - AS */ + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = forefather_of_element->up; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 2 + * When tree 2 3 becomes 4 1 + * 4 5 5 3 + * + * 1 is used to show that it is the top of the tree + */ + added_element->up = NULL; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->left != NULL) + father_of_element->left->up = father_of_element; + } else if (father_of_element->right != NULL) { + added_element = father_of_element->right; + father_of_element->right_depth = added_element->left_depth; + added_element->left_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) + /* Bug fixed in March 94 - AS */ + { + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + father_of_forefather = father_of_forefather->up; + forefather_of_element = forefather_of_element->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 3 + * When tree 2 3 becomes 1 5 + * 4 5 2 4 + * + * 1 is used to show that it is the top of the tree. + */ + added_element->up = NULL; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->right != NULL) + father_of_element->right->up = father_of_element; + } + } + } + while (father_of_element->up != NULL) { + father_of_element = father_of_element->up; + } + tree->top = father_of_element; + } +} + +/************************************************************************* + * this function returns a pointer to the leftmost element if ele is NULL, + * and to the next object to the right otherwise. + * If no elements left, returns a pointer to NULL. + */ +HTBTElement *HTBTree_next(HTBTree *tree, + HTBTElement *ele) +{ + HTBTElement *father_of_element; + HTBTElement *father_of_forefather; + + if (ele == NULL) { + father_of_element = tree->top; + if (father_of_element != NULL) + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_element = ele; + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_forefather = father_of_element->up; + while (father_of_forefather && + (father_of_forefather->right == father_of_element)) { + father_of_element = father_of_forefather; + father_of_forefather = father_of_element->up; + } + father_of_element = father_of_forefather; + } + } +#ifdef BTREE_TRACE + /* The option -DBTREE_TRACE will give much more information + * about the way the process is running, for debugging matters + */ + if (father_of_element != NULL) { + printf("\nObject = %s\t", (char *) father_of_element->object); + if (father_of_element->up != NULL) + printf("Objet du pere = %s\n", + (char *) father_of_element->up->object); + else + printf("Pas de Pere\n"); + if (father_of_element->left != NULL) + printf("Objet du fils gauche = %s\t", + (char *) father_of_element->left->object); + else + printf("Pas de fils gauche\t"); + if (father_of_element->right != NULL) + printf("Objet du fils droit = %s\n", + (char *) father_of_element->right->object); + else + printf("Pas de fils droit\n"); + printf("Profondeur gauche = %d\t", father_of_element->left_depth); + printf("Profondeur droite = %d\n", father_of_element->right_depth); + printf(" **************\n"); + } +#endif + return father_of_element; +} + +#ifdef TEST +/***************************************************** + * This is just a test to show how to handle HTBTree.c + */ +main() +{ + HTBTree *tree; + HTBTElement *next_element; + + tree = HTBTree_new((HTComparer) strcasecomp); + HTBTree_add(tree, "hypertext"); + HTBTree_add(tree, "Addressing"); + HTBTree_add(tree, "X11"); + HTBTree_add(tree, "Tools"); + HTBTree_add(tree, "Proposal.wn"); + HTBTree_add(tree, "Protocols"); + HTBTree_add(tree, "NeXT"); + HTBTree_add(tree, "Daemon"); + HTBTree_add(tree, "Test"); + HTBTree_add(tree, "Administration"); + HTBTree_add(tree, "LineMode"); + HTBTree_add(tree, "DesignIssues"); + HTBTree_add(tree, "MarkUp"); + HTBTree_add(tree, "Macintosh"); + HTBTree_add(tree, "Proposal.rtf.wn"); + HTBTree_add(tree, "FIND"); + HTBTree_add(tree, "Paper"); + HTBTree_add(tree, "Tcl"); + HTBTree_add(tree, "Talks"); + HTBTree_add(tree, "Architecture"); + HTBTree_add(tree, "VMSHelp"); + HTBTree_add(tree, "Provider"); + HTBTree_add(tree, "Archive"); + HTBTree_add(tree, "SLAC"); + HTBTree_add(tree, "Project"); + HTBTree_add(tree, "News"); + HTBTree_add(tree, "Viola"); + HTBTree_add(tree, "Users"); + HTBTree_add(tree, "FAQ"); + HTBTree_add(tree, "WorkingNotes"); + HTBTree_add(tree, "Windows"); + HTBTree_add(tree, "FineWWW"); + HTBTree_add(tree, "Frame"); + HTBTree_add(tree, "XMosaic"); + HTBTree_add(tree, "People"); + HTBTree_add(tree, "All"); + HTBTree_add(tree, "Curses"); + HTBTree_add(tree, "Erwise"); + HTBTree_add(tree, "Carl"); + HTBTree_add(tree, "MidasWWW"); + HTBTree_add(tree, "XPM"); + HTBTree_add(tree, "MailRobot"); + HTBTree_add(tree, "Illustrations"); + HTBTree_add(tree, "VMClient"); + HTBTree_add(tree, "XPA"); + HTBTree_add(tree, "Clients.html"); + HTBTree_add(tree, "Library"); + HTBTree_add(tree, "CERNLIB_Distribution"); + HTBTree_add(tree, "libHTML"); + HTBTree_add(tree, "WindowsPC"); + HTBTree_add(tree, "tkWWW"); + HTBTree_add(tree, "tk2.3"); + HTBTree_add(tree, "CVS-RCS"); + HTBTree_add(tree, "DecnetSockets"); + HTBTree_add(tree, "SGMLStream"); + HTBTree_add(tree, "NextStep"); + HTBTree_add(tree, "CVSRepository_old"); + HTBTree_add(tree, "ArthurSecret"); + HTBTree_add(tree, "CVSROOT"); + HTBTree_add(tree, "HytelnetGate"); + HTBTree_add(tree, "cern.www.new.src"); + HTBTree_add(tree, "Conditions"); + HTBTree_add(tree, "HTMLGate"); + HTBTree_add(tree, "Makefile"); + HTBTree_add(tree, "Newsgroups.html"); + HTBTree_add(tree, "People.html"); + HTBTree_add(tree, "Bugs.html"); + HTBTree_add(tree, "Summary.html"); + HTBTree_add(tree, "zDesignIssues.wn"); + HTBTree_add(tree, "HT.draw"); + HTBTree_add(tree, "HTandCERN.wn"); + HTBTree_add(tree, "Ideas.wn"); + HTBTree_add(tree, "MarkUp.wn"); + HTBTree_add(tree, "Proposal.html"); + HTBTree_add(tree, "SearchPanel.draw"); + HTBTree_add(tree, "Comments.wn"); + HTBTree_add(tree, "Xanadu.html"); + HTBTree_add(tree, "Storinglinks.html"); + HTBTree_add(tree, "TheW3Book.html"); + HTBTree_add(tree, "Talk_Feb-91.html"); + HTBTree_add(tree, "JFosterEntry.txt"); + HTBTree_add(tree, "Summary.txt"); + HTBTree_add(tree, "Bibliography.html"); + HTBTree_add(tree, "HTandCern.txt"); + HTBTree_add(tree, "Talk.draw"); + HTBTree_add(tree, "zDesignNotes.html"); + HTBTree_add(tree, "Link.html"); + HTBTree_add(tree, "Status.html"); + HTBTree_add(tree, "http.txt"); + HTBTree_add(tree, "People.html~"); + HTBTree_add(tree, "TAGS"); + HTBTree_add(tree, "summary.txt"); + HTBTree_add(tree, "Technical.html"); + HTBTree_add(tree, "Terms.html"); + HTBTree_add(tree, "JANETAccess.html"); + HTBTree_add(tree, "People.txt"); + HTBTree_add(tree, "README.txt"); + HTBTree_add(tree, "CodingStandards.html"); + HTBTree_add(tree, "Copyright.txt"); + HTBTree_add(tree, "Status_old.html"); + HTBTree_add(tree, "patches~"); + HTBTree_add(tree, "RelatedProducts.html"); + HTBTree_add(tree, "Implementation"); + HTBTree_add(tree, "History.html"); + HTBTree_add(tree, "Makefile.bak"); + HTBTree_add(tree, "Makefile.old"); + HTBTree_add(tree, "Policy.html"); + HTBTree_add(tree, "WhatIs.html"); + HTBTree_add(tree, "TheProject.html"); + HTBTree_add(tree, "Notation.html"); + HTBTree_add(tree, "Helping.html"); + HTBTree_add(tree, "Cyber-WWW.sit.Hqx"); + HTBTree_add(tree, "Glossary.html"); + HTBTree_add(tree, "maketags.html"); + HTBTree_add(tree, "IntroCS.html"); + HTBTree_add(tree, "Contrib"); + HTBTree_add(tree, "Help.html"); + HTBTree_add(tree, "CodeManagExec"); + HTBTree_add(tree, "HT-0.1draz"); + HTBTree_add(tree, "Cello"); + HTBTree_add(tree, "TOPUB"); + HTBTree_add(tree, "BUILD"); + HTBTree_add(tree, "BUILDALL"); + HTBTree_add(tree, "Lynx"); + HTBTree_add(tree, "ArthurLibrary"); + HTBTree_add(tree, "RashtyClient"); + HTBTree_add(tree, "#History.html#"); + HTBTree_add(tree, "PerlServers"); + HTBTree_add(tree, "modules"); + HTBTree_add(tree, "NCSA_httpd"); + HTBTree_add(tree, "MAIL2HTML"); + HTBTree_add(tree, "core"); + HTBTree_add(tree, "EmacsWWW"); +#ifdef BTREE_TRACE + printf("\nTreeTopObject=%s\n\n", tree->top->object); +#endif + next_element = HTBTree_next(tree, NULL); + while (next_element != NULL) { +#ifndef BTREE_TRACE + printf("The next element is %s\n", next_element->object); +#endif + next_element = HTBTree_next(tree, next_element); + } + HTBTree_free(tree); +} + +#endif diff --git a/WWW/Library/Implementation/HTBTree.h b/WWW/Library/Implementation/HTBTree.h new file mode 100644 index 0000000..a4f78f9 --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.h @@ -0,0 +1,104 @@ +/* /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTBTree.html + BALANCED BINARY TREE FOR SORTING THINGS + + Tree creation, traversal and freeing. User-supplied comparison routine. + + Author: Arthur Secret, CERN. Public domain. Please mail bugs and changes to + www-request@info.cern.ch + + part of libWWW + + */ +#ifndef HTBTREE_H +#define HTBTREE_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Data structures + + */ typedef struct _HTBTree_element { + void *object; /* User object */ + struct _HTBTree_element *up; + struct _HTBTree_element *left; + int left_depth; + struct _HTBTree_element *right; + int right_depth; + } HTBTElement; + + typedef int (*HTComparer) (void *a, void *b); + + typedef struct _HTBTree_top { + HTComparer compare; + struct _HTBTree_element *top; + } HTBTree; + +/* + +Create a binary tree given its discrimination routine + + */ + extern HTBTree *HTBTree_new(HTComparer comp); + +/* + +Free storage of the tree but not of the objects + + */ + extern void HTBTree_free(HTBTree *tree); + +/* + +Free storage of the tree and of the objects + + */ + extern void HTBTreeAndObject_free(HTBTree *tree); + +/* + +Add an object to a binary tree + + */ + + extern void HTBTree_add(HTBTree *tree, void *object); + +/* + +Search an object in a binary tree + + returns Pointer to equivalent object in a tree or NULL if none. + */ + + extern void *HTBTree_search(HTBTree *tree, void *object); + +/* + +Find user object for element + + */ +#define HTBTree_object(element) ((element)->object) + +/* + +Find next element in depth-first order + + ON ENTRY, + + ele if NULL, start with leftmost element. if != 0 give next object to + the right. + + returns Pointer to element or NULL if none left. + + */ + extern HTBTElement *HTBTree_next(HTBTree *tree, HTBTElement *ele); + +#ifdef __cplusplus +} +#endif +#endif /* HTBTREE_H */ diff --git a/WWW/Library/Implementation/HTCJK.h b/WWW/Library/Implementation/HTCJK.h new file mode 100644 index 0000000..7edf50b --- /dev/null +++ b/WWW/Library/Implementation/HTCJK.h @@ -0,0 +1,121 @@ +/* + * $LynxId: HTCJK.h,v 1.22 2021/07/01 23:51:38 tom Exp $ + * + * CJK character converter HTCJK.h + * ======================= + * + * Added 11-Jun-96 by FM, based on jiscode.h for + * Yutaka Sato's (ysato@etl.go.jp) SJIS.c, and + * Takuya ASADA's (asada@three-a.co.jp) CJK patches. + * (see SGML.c). + * + */ + +#ifndef HTCJK_H +#define HTCJK_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + * STATUS CHANGE CODES + */ +#define TO_2BCODE '$' +#define TO_1BCODE '(' +#define TO_KANA '\016' +#define TO_KANAOUT '\017' +#define TO_KANJI "\033$B" +#define TO_HANJI "\033$A" +#define TO_HANGUL "\033$(C" +#define TO_ASCII "\033(B" + +#define IS_GBK_LO(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) +#define IS_GBK_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xF7)) + +#define IS_SJIS_LO(lo) ((0x40 <= (lo)) && ((lo) != 0x7F) && ((lo) <= 0xFC)) +#define IS_SJIS_HI1(hi) ((0x81 <= (hi)) && ((hi) <= 0x9F)) /* 1st lev. */ +#define IS_SJIS_HI2(hi) ((0xE0 <= (hi)) && ((hi) <= 0xEF)) /* 2nd lev. */ +#define IS_SJIS(hi,lo,in_sjis) (!IS_SJIS_LO(lo) ? 0 : IS_SJIS_HI1(hi) ? (in_sjis=1) : in_sjis && IS_SJIS_HI2(hi)) +#define IS_SJIS_2BYTE(hi,lo) (IS_SJIS_LO(lo) && (IS_SJIS_HI1(hi) || IS_SJIS_HI2(hi))) +#define IS_SJIS_X0201KANA(lo) ((0xA1 <= (lo)) && ((lo) <= 0xDF)) + +#define IS_EUC_LOS(lo) ((0x21 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_EUC_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_EUC_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_EUC_X0201KANA(hi,lo) (((hi) == 0x8E) && (0xA1 <= (lo)) && ((lo) <= 0xDF)) +#define IS_EUC(hi,lo) ((IS_EUC_HI(hi) && IS_EUC_LOX(lo)) || IS_EUC_X0201KANA(hi,lo)) + +#define IS_JAPANESE_2BYTE(hi,lo) (IS_SJIS_2BYTE(hi,lo) || IS_EUC(hi,lo)) + +#define IS_BIG5_LOS(lo) ((0x40 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_BIG5_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_BIG5_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && (IS_BIG5_LOS(lo) || IS_BIG5_LOX(lo))) + + typedef enum { + NOKANJI = 0, EUC, SJIS, JIS + } HTkcode; + typedef enum { + NOCJK = 0, JAPANESE, CHINESE, KOREAN, TAIPEI + } HTCJKlang; + + extern HTCJKlang HTCJK; + +/* + * Function prototypes. + */ + extern void JISx0201TO0208_EUC(unsigned IHI, + unsigned ILO, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_JIS1(unsigned HI, + unsigned LO, + unsigned char *JCODE); + + extern unsigned char *JIS_TO_SJIS1(unsigned HI, + unsigned LO, + unsigned char *SJCODE); + + extern unsigned char *EUC_TO_SJIS1(unsigned HI, + unsigned LO, + register unsigned char *SJCODE); + + extern void JISx0201TO0208_SJIS(unsigned I, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_EUC1(unsigned HI, + unsigned LO, + unsigned char *EUCp); + + extern unsigned char *SJIS_TO_EUC(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_SJIS(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_JIS(unsigned char *src, + unsigned char *dst, + const char *toK, + const char *toA); + + extern unsigned char *TO_EUC(const unsigned char *jis, + unsigned char *euc); + + extern void TO_SJIS(const unsigned char *any, + unsigned char *sjis); + + extern void TO_JIS(const unsigned char *any, + unsigned char *jis); + + extern char *str_kcode(HTkcode code); + +#ifdef __cplusplus +} +#endif +#endif /* HTCJK_H */ diff --git a/WWW/Library/Implementation/HTChunk.c b/WWW/Library/Implementation/HTChunk.c new file mode 100644 index 0000000..6b67011 --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.c @@ -0,0 +1,332 @@ +/* + * $LynxId: HTChunk.c,v 1.28 2016/11/24 15:29:50 tom Exp $ + * + * Chunk handling: Flexible arrays + * =============================== + * + */ + +#include +#include + +#include + +/* + * Initialize a chunk with a certain allocation unit + */ +void HTChunkInit(HTChunk *ch, int grow) +{ + ch->data = 0; + ch->growby = grow; + ch->size = 0; + ch->allocated = 0; +} + +/* Create a chunk with a certain allocation unit + * -------------- + */ +HTChunk *HTChunkCreate(int grow) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "creation of chunk"); + + HTChunkInit(ch, grow); + return ch; +} + +HTChunk *HTChunkCreateMayFail(int grow, int failok) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) { + if (!failok) { + outofmem(__FILE__, "creation of chunk"); + } else { + return ch; + } + } + + HTChunkInit(ch, grow); + ch->failok = failok; + return ch; +} + +/* Create a chunk with a certain allocation unit and ensured size + * -------------- + */ +HTChunk *HTChunkCreate2(int grow, size_t needed) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "HTChunkCreate2"); + + HTChunkInit(ch, grow); + if (needed-- > 0) { + /* Round up */ + ch->allocated = (int) (needed - (needed % (size_t) ch->growby) + + (unsigned) ch->growby); + CTRACE((tfp, "HTChunkCreate2: requested %d, allocate %u\n", + (int) needed, (unsigned) ch->allocated)); + ch->data = typecallocn(char, (unsigned) ch->allocated); + + if (!ch->data) + outofmem(__FILE__, "HTChunkCreate2 data"); + } + return ch; +} + +/* Clear a chunk of all data + * -------------------------- + */ +void HTChunkClear(HTChunk *ch) +{ + FREE(ch->data); + ch->size = 0; + ch->allocated = 0; +} + +/* Free a chunk (and it's chain, if any) + * ------------------------------------- + */ +void HTChunkFree(HTChunk *ch) +{ + HTChunk *next; + + do { + next = ch->next; + FREE(ch->data); + FREE(ch); + ch = next; + } while (ch != NULL); +} + +/* Realloc the chunk + * ----------------- + */ +BOOL HTChunkRealloc(HTChunk *ch, int growby) +{ + char *data; + + ch->allocated = ch->allocated + growby; + + data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (data) { + ch->data = data; + } else if (ch->failok) { + HTChunkClear(ch); /* allocation failed, clear all data - kw */ + return FALSE; /* caller should check ch->allocated - kw */ + } else { + outofmem(__FILE__, "HTChunkRealloc"); + } + return TRUE; +} + +/* Append a character + * ------------------ + */ +void HTChunkPutc(HTChunk *ch, unsigned c) +{ + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + return; + } + ch->data[ch->size++] = (char) c; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutc2(HTChunk *ch, int c) +{ + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPutc(ch, UCH(c)); + } else { + ch->data[ch->size++] = (char) c; + } + return ch; +} + +/* Ensure a certain size + * --------------------- + */ +void HTChunkEnsure(HTChunk *ch, int needed) +{ + if (needed <= ch->allocated) + return; + ch->allocated = needed - 1 - ((needed - 1) % ch->growby) + + ch->growby; /* Round up */ + ch->data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (ch->data == NULL) + outofmem(__FILE__, "HTChunkEnsure"); +} + +/* + * Append a block of characters. + */ +void HTChunkPutb(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return; + if (ch->size + l > ch->allocated) { + int growby = l - (l % ch->growby) + ch->growby; /* Round up */ + + if (!HTChunkRealloc(ch, growby)) + return; + } + MemCpy(ch->data + ch->size, b, l); + ch->size += l; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return ch; + if (ch->size + l > ch->allocated) { + HTChunk *chunk; + int m = ch->allocated - ch->size; + + MemCpy(ch->data + ch->size, b, (unsigned) m); + ch->size += m; + + chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + ch->next = chunk; + ch = chunk; + HTChunkPutb(ch, b + m, l - m); + } else { + MemCpy(ch->data + ch->size, b, (unsigned) l); + ch->size += l; + } + return ch; +} + +#define PUTC(code) ch->data[ch->size++] = (char)(code) +#define PUTC2(code) ch->data[ch->size++] = (char)(0x80|(0x3f &(code))) + +/* + * Append a character encoded as UTF-8. + */ +void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code) +{ + int utflen; + + if (TOASCII(code) < 128) + utflen = 1; + else if (code < 0x800L) { + utflen = 2; + } else if (code < 0x10000L) { + utflen = 3; + } else if (code < 0x200000L) { + utflen = 4; + } else if (code < 0x4000000L) { + utflen = 5; + } else if (code <= 0x7fffffffL) { + utflen = 6; + } else + utflen = 0; + + if (ch->size + utflen > ch->allocated) { + int growby = (ch->growby >= utflen) ? ch->growby : utflen; + + if (!HTChunkRealloc(ch, growby)) + return; + } + + switch (utflen) { + case 0: + return; + case 1: + ch->data[ch->size++] = (char) code; + return; + case 2: + PUTC(0xc0 | (code >> 6)); + break; + case 3: + PUTC(0xe0 | (code >> 12)); + break; + case 4: + PUTC(0xf0 | (code >> 18)); + break; + case 5: + PUTC(0xf8 | (code >> 24)); + break; + case 6: + PUTC(0xfc | (code >> 30)); + break; + } + switch (utflen) { + case 6: + PUTC2(code >> 24); + /* FALLTHRU */ + case 5: + PUTC2(code >> 18); + /* FALLTHRU */ + case 4: + PUTC2(code >> 12); + /* FALLTHRU */ + case 3: + PUTC2(code >> 6); + /* FALLTHRU */ + case 2: + PUTC2(code); + break; + } +} + +/* Terminate a chunk + * ----------------- + */ +void HTChunkTerminate(HTChunk *ch) +{ + HTChunkPutc(ch, (char) 0); +} + +/* Append a string + * --------------- + */ +void HTChunkPuts(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + break; + } + ch->data[ch->size++] = *p; + } + } +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPuts2(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPuts(ch, p); + break; + } + ch->data[ch->size++] = *p; + } + } + return ch; +} diff --git a/WWW/Library/Implementation/HTChunk.h b/WWW/Library/Implementation/HTChunk.h new file mode 100644 index 0000000..fa51c99 --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.h @@ -0,0 +1,228 @@ +/* + * $LynxId: HTChunk.h,v 1.21 2020/01/21 22:02:43 tom Exp $ + * + * HTChunk: Flexible array handling for libwww + * CHUNK HANDLING: + * FLEXIBLE ARRAYS + * + * This module implements a flexible array. It is a general utility module. A + * chunk is a structure which may be extended. These routines create and + * append data to chunks, automatically reallocating them as necessary. + * + */ +#ifndef HTCHUNK_H +#define HTCHUNK_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTChunk HTChunk; + + struct _HTChunk { + int size; /* In bytes */ + int growby; /* Allocation unit in bytes */ + int allocated; /* Current size of *data */ + char *data; /* Pointer to malloc'd area or 0 */ + int failok; /* allowed to fail without exiting program? */ + HTChunk *next; /* pointer to the next chunk */ + }; + +/* + * Initialize a chunk's allocation data and allocation-increment. + */ + extern void HTChunkInit(HTChunk *ch, int grow); + +/* + * + * Create new chunk + * + * ON ENTRY, + * + * growby The number of bytes to allocate at a time when the chunk + * is later extended. Arbitrary but normally a trade-off + * of time vs memory. + * + * ON EXIT, + * + * returns A chunk pointer to the new chunk, + * + */ + + extern HTChunk *HTChunkCreate(int growby); + +/* + * Create a chunk for which an allocation error is not a fatal application + * error if failok != 0, but merely resets the chunk. When using a chunk + * created this way, the caller should always check whether the contents + * are ok each time after data have been appended. + * The create call may also fail and will return NULL in that case. - kw + */ + extern HTChunk *HTChunkCreateMayFail(int growby, int failok); + +/* + * Like HTChunkCreate but with initial allocation - kw + * + */ + extern HTChunk *HTChunkCreate2(int growby, size_t needed); + +/* + * + * Free a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * ch is invalid and may not be used. + * + */ + + extern void HTChunkFree(HTChunk *ch); + +/* + * + * Clear a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch The size of the chunk is zero. + * + */ + + extern void HTChunkClear(HTChunk *ch); + +/* + * + * Realloc a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * growby growby + * + * ON EXIT, + * + * *ch Expanded by growby + * + */ + + extern BOOL HTChunkRealloc(HTChunk *ch, int growby); + +/* + * + * Ensure a chunk has a certain space in + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * s The size required + * + * ON EXIT, + * + * *ch Has size at least s + * + */ + + extern void HTChunkEnsure(HTChunk *ch, int s); + +/* + * + * Append a character to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + extern void HTChunkPutc(HTChunk *ch, unsigned c); + + extern void HTChunkPutb(HTChunk *ch, const char *b, int l); + + extern void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code); + +/* + * Append a string to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * str Points to a zero-terminated string to be appended + * + * ON EXIT, + * + * *ch Is bigger by strlen(str) + * + */ + + extern void HTChunkPuts(HTChunk *ch, const char *str); + +/* + * + * Append a zero character to a chunk + * + */ + +/* + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + + extern void HTChunkTerminate(HTChunk *ch); + +/* like the above but no realloc: extend to another chunk if necessary */ +/* + * + * Append a character (string, data) to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * returns original chunk or a pointer to the new chunk + * (original chunk is referenced to the new one + * by the field 'next') + * + */ + extern HTChunk *HTChunkPutc2(HTChunk *ch, int c); + extern HTChunk *HTChunkPuts2(HTChunk *ch, const char *str); + extern HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l); + +/* New pool infrastructure: UNlike the above, store data using alignment */ + extern HTChunk *HTChunkPutb0(HTChunk *ch, const char *b, int l); + +#ifdef __cplusplus +} +#endif +#endif /* HTCHUNK_H */ diff --git a/WWW/Library/Implementation/HTDOS.c b/WWW/Library/Implementation/HTDOS.c new file mode 100644 index 0000000..84bff79 --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.c @@ -0,0 +1,241 @@ +/* + * $LynxId: HTDOS.c,v 1.40 2013/11/28 11:11:05 tom Exp $ + * DOS specific routines + */ + +#include +#include +#include +#include + +#include + +#ifdef _WINDOWS +#include +#include +#endif + +/* + * Make a copy of the source argument in the result, allowing some extra + * space so we can append directly onto the result without reallocating. + */ +static char *copy_plus(char **result, const char *source) +{ + int length = (int) strlen(source); + int extra = 10; + int n; + + for (n = 0; n < length; ++n) { + if (source[n] == ' ') + ++extra; + } + + HTSprintf0(result, "%-*s", length + extra, source); + (*result)[length] = 0; + return (*result); +} + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname) +{ + static char *wwwname = NULL; + char *cp_url = copy_plus(&wwwname, dosname); + int wwwname_len; + char ch; + + while ((ch = *dosname) != '\0') { + switch (ch) { + case '\\': + /* convert dos backslash to unix-style */ + *cp_url++ = '/'; + break; + case ' ': + *cp_url++ = '%'; + *cp_url++ = '2'; + *cp_url++ = '0'; + break; + default: + *cp_url++ = ch; + break; + } + dosname++; + } + *cp_url = '\0'; + + wwwname_len = (int) strlen(wwwname); + if (wwwname_len > 1) + cp_url--; /* point last char */ + + if (wwwname_len > 3 && *cp_url == '/') { + cp_url++; + *cp_url = '\0'; + } + return (wwwname); +} + +/* + * Convert slashes from Unix to DOS + */ +char *HTDOS_slashes(char *path) +{ + char *s; + + for (s = path; *s != '\0'; ++s) { + if (*s == '/') { + *s = '\\'; + } + } + return path; +} + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + */ +char *HTDOS_name(const char *wwwname) +{ + static char *result = NULL; + int joe; + +#if defined(SH_EX) /* 2000/03/07 (Tue) 18:32:42 */ + if (unsafe_filename(wwwname)) { + HTUserMsg2("unsafe filename : %s", wwwname); + copy_plus(&result, "BAD_LOCAL_FILE_NAME"); + } else { + copy_plus(&result, wwwname); + } +#else + copy_plus(&result, wwwname); +#endif +#ifdef __DJGPP__ + if (result[0] == '/' + && result[1] == 'd' + && result[2] == 'e' + && result[3] == 'v' + && result[4] == '/' + && isalpha(result[5])) { + return (result); + } +#endif /* __DJGPP__ */ + + (void) HTDOS_slashes(result); + + /* pesky leading slash, rudiment from file://localhost/ */ + /* the rest of path may be with or without drive letter */ + if ((result[1] != '\\') && (result[0] == '\\')) { + for (joe = 0; (result[joe] = result[joe + 1]) != 0; joe++) ; + } + /* convert '|' after the drive letter to ':' */ + if (isalpha(UCH(result[0])) && result[1] == '|') { + result[1] = ':'; + } +#ifdef _WINDOWS /* 1998/04/02 (Thu) 08:59:48 */ + if (LYLastPathSep(result) != NULL + && !LYIsDosDrive(result)) { + char temp_buff[LY_MAXPATH]; + + sprintf(temp_buff, "%.3s\\%.*s", windows_drive, + (int) (sizeof(temp_buff) - 5), result); + StrAllocCopy(result, temp_buff); + } +#endif + /* + * If we have only a device, add a trailing slash. Otherwise it just + * refers to the current directory on the given device. + */ + if (LYLastPathSep(result) == NULL + && LYIsDosDrive(result)) + LYAddPathSep0(result); + + CTRACE((tfp, "HTDOS_name changed `%s' to `%s'\n", wwwname, result)); + return (result); +} + +#ifdef WIN_EX +char *HTDOS_short_name(const char *path) +{ + static char sbuf[LY_MAXPATH]; + char *ret; + DWORD r; + + if (StrChr(path, '/')) + path = HTDOS_name(path); + r = GetShortPathName(path, sbuf, sizeof sbuf); + if (r >= sizeof(sbuf) || r == 0) { + ret = LYStrNCpy(sbuf, path, sizeof(sbuf)); + } else { + ret = sbuf; + } + return ret; +} +#endif + +#if defined(DJGPP) +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + * This is required to be able to finish off dead sockets, + * answer pings etc. + */ +#include +#include +#include +#include + +void djgpp_idle_loop(void) +{ + while (_farpeekw(_dos_ds, 0x41a) == _farpeekw(_dos_ds, 0x41c)) { + tcp_tick(NULL); + __dpmi_yield(); +#if defined(USE_SLANG) + if (SLang_input_pending(1)) + break; +#endif + } +} + +/* PUBLIC getxkey() + * Replaces libc's getxkey() with polling of tcp/ip + * library (WatTcp or Watt-32). * + * ON EXIT: + * returns extended keypress. + */ + +/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ + +int getxkey(void) +{ +#if defined(DJGPP_KEYHANDLER) + __dpmi_regs r; + + djgpp_idle_loop(); + + r.h.ah = 0x10; + __dpmi_int(0x16, &r); + + if (r.h.al == 0x00) + return 0x0100 | r.h.ah; + if (r.h.al == 0xe0) + return 0x0200 | r.h.ah; + return r.h.al; + +#elif defined(USE_SLANG) + djgpp_idle_loop(); + return SLkp_getkey(); +#else + /* PDcurses uses myGetChar() in LYString.c */ +#endif +} +#endif /* DJGPP */ diff --git a/WWW/Library/Implementation/HTDOS.h b/WWW/Library/Implementation/HTDOS.h new file mode 100644 index 0000000..e1613cb --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.h @@ -0,0 +1,56 @@ +/* + * $LynxId: HTDOS.h,v 1.14 2009/09/09 00:16:06 tom Exp $ + * + * DOS specific routines + */ + +#ifndef HTDOS_H +#define HTDOS_H + +#ifndef HTUTILS_H +#include +#endif /* HTUTILS_H */ + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname); + +/* + * Converts Unix slashes to DOS + */ +char *HTDOS_slashes(char *path); + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTDOS_name(const char *wwwname); + +#ifdef WIN_EX +char *HTDOS_short_name(const char *fn); + +#else +#define HTDOS_short_name(fn) fn +#endif + +#ifdef DJGPP +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + */ +void djgpp_idle_loop(void); +#endif +#endif /* HTDOS_H */ diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c new file mode 100644 index 0000000..decf559 --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.c @@ -0,0 +1,4177 @@ +/* + * $LynxId: HTFTP.c,v 1.148 2023/01/05 09:17:15 tom Exp $ + * + * File Transfer Protocol (FTP) Client + * for a WorldWideWeb browser + * =================================== + * + * A cache of control connections is kept. + * + * Note: Port allocation + * + * It is essential that the port is allocated by the system, rather + * than chosen in rotation by us (POLL_PORTS), or the following + * problem occurs. + * + * It seems that an attempt by the server to connect to a port which has + * been used recently by a listen on the same socket, or by another + * socket this or another process causes a hangup of (almost exactly) + * one minute. Therefore, we have to use a rotating port number. + * The problem remains that if the application is run twice in quick + * succession, it will hang for what remains of a minute. + * + * Authors + * TBL Tim Berners-lee + * DD Denis DeLaRoca 310 825-4580 + * LM Lou Montulli + * FM Foteos Macrides + * History: + * 2 May 91 Written TBL, as a part of the WorldWideWeb project. + * 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc + * 10 Feb 92 Retry if cached connection times out or breaks + * 8 Dec 92 Bug fix 921208 TBL after DD + * 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD + * fails on princeton.edu! + * 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path + * must be Unix-style and cannot include the device + * or top directory. + * ?? ??? ?? (LM) Added code to prompt and send passwords for non + * anonymous FTP + * 25 Mar 94 (LM) Added code to recognize different ftp server types + * and code to parse dates and sizes on most hosts. + * 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts. + * + * Notes: + * Portions Copyright 1994 Trustees of Dartmouth College + * Code for recognizing different FTP servers and + * parsing "ls -l" output taken from Macintosh Fetch + * program with permission from Jim Matthews, + * Dartmouth Software Development Team. + */ + +/* + * BUGS: @@@ Limit connection cache size! + * Error reporting to user. + * 400 & 500 errors are ack'ed by user with windows. + * Use configuration file for user names + * + * Note for portability this version does not use select() and + * so does not watch the control and data channels at the + * same time. + */ + +#include + +#include + +#include /* Implemented here */ +#include +#include +#include + +#define REPEAT_PORT /* Give the port number for each file */ +#define REPEAT_LISTEN /* Close each listen socket and open a new one */ + +/* define POLL_PORTS If allocation does not work, poll ourselves.*/ +#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP) */ + +#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */ +#define LAST_TCP_PORT 5999 + +#define LINE_LENGTH 256 + +#include +#include +#include /* For HTFileFormat() */ +#include +#include +#ifndef IPPORT_FTP +#define IPPORT_FTP 21 +#endif /* !IPORT_FTP */ + +#include +#include +#include +#include + +typedef struct _connection { + struct _connection *next; /* Link on list */ + int socket; /* Socket number for communication */ + BOOL is_binary; /* Binary mode? */ +} connection; + +/* Hypertext object building machinery +*/ +#include + +/* + * socklen_t is the standard, but there are many pre-standard variants. + * This ifdef works around a few of those cases. + * + * Information was obtained from header files on these platforms: + * AIX 4.3.2, 5.1 + * HPUX 10.20, 11.00, 11.11 + * IRIX64 6.5 + * Tru64 4.0G, 4.0D, 5.1 + */ +#if defined(SYS_IRIX64) + /* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */ +# if _NO_XOPEN4 && _NO_XOPEN5 +# define LY_SOCKLEN socklen_t +# elif _ABIAPI +# define LY_SOCKLEN int +# elif _XOPEN5 +# if (_MIPS_SIM != _ABIO32) +# define LY_SOCKLEN socklen_t +# else +# define LY_SOCKLEN int +# endif +# else +# define LY_SOCKLEN size_t +# endif +#elif defined(SYS_HPUX) +# if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE) +# define LY_SOCKLEN socklen_t +# else /* HPUX 10.20, etc. */ +# define LY_SOCKLEN int +# endif +#elif defined(SYS_TRU64) +# if defined(_POSIX_PII_SOCKET) +# define LY_SOCKLEN socklen_t +# elif defined(_XOPEN_SOURCE_EXTENDED) +# define LY_SOCKLEN size_t +# else +# define LY_SOCKLEN int +# endif +#else +# define LY_SOCKLEN socklen_t +#endif + +#define PUTC(c) (*target->isa->put_character) (target, c) +#define PUTS(s) (*target->isa->put_string) (target, s) +#define START(e) (*target->isa->start_element) (target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element) (target, e, 0) +#define FREE_TARGET (*target->isa->_free) (target) +#define ABORT_TARGET (*target->isa->_free) (target) + +#define TRACE_ENTRY(tag, entry_info) \ + CTRACE((tfp, "HTFTP: %s filename: %s date: %s size: %" PRI_off_t "\n", \ + tag, \ + entry_info->filename, \ + NonNull(entry_info->date), \ + CAST_off_t(entry_info->size))) + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* Global Variables + * --------------------- + */ +int HTfileSortMethod = FILE_BY_NAME; + +#ifndef DISABLE_FTP /*This disables everything to end-of-file */ +static char ThisYear[8]; +static char LastYear[8]; +static int TheDate; +static BOOLEAN HaveYears = FALSE; + +/* Module-Wide Variables + * --------------------- + */ +static connection *connections = NULL; /* Linked list of connections */ +static char response_text[LINE_LENGTH + 1]; /* Last response from ftp host */ +static connection *control = NULL; /* Current connection */ +static int data_soc = -1; /* Socket for data transfer =invalid */ +static char *user_entered_password = NULL; +static char *last_username_and_host = NULL; + +/* + * Some ftp servers are known to have a broken implementation of RETR. If + * asked to retrieve a directory, they get confused and fail subsequent + * commands such as CWD and LIST. + */ +static int Broken_RETR = FALSE; + +/* + * Some ftp servers are known to have a broken implementation of EPSV. The + * server will hang for a long time when we attempt to connect after issuing + * this command. + */ +#ifdef INET6 +static int Broken_EPSV = FALSE; +#endif + +typedef enum { + GENERIC_SERVER + ,MACHTEN_SERVER + ,UNIX_SERVER + ,VMS_SERVER + ,CMS_SERVER + ,DCTS_SERVER + ,TCPC_SERVER + ,PETER_LEWIS_SERVER + ,NCSA_SERVER + ,WINDOWS_NT_SERVER + ,WINDOWS_2K_SERVER + ,MS_WINDOWS_SERVER + ,MSDOS_SERVER + ,APPLESHARE_SERVER + ,NETPRESENZ_SERVER + ,DLS_SERVER +} eServerType; + +static eServerType server_type = GENERIC_SERVER; /* the type of ftp host */ +static int unsure_type = FALSE; /* sure about the type? */ +static BOOLEAN use_list = FALSE; /* use the LIST command? */ + +static int interrupted_in_next_data_char = FALSE; + +#ifdef POLL_PORTS +static PortNumber port_number = FIRST_TCP_PORT; +#endif /* POLL_PORTS */ + +static BOOL have_socket = FALSE; /* true if master_socket is valid */ +static LYNX_FD master_socket; /* Listening socket = invalid */ + +static char *port_command; /* Command for setting the port */ +static fd_set open_sockets; /* Mask of active channels */ +static LYNX_FD num_sockets; /* Number of sockets to scan */ +static PortNumber passive_port; /* Port server specified for data */ + +#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */ + +#define DATA_BUFFER_SIZE 2048 +static char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */ +static char *data_read_pointer; +static char *data_write_pointer; + +#define NEXT_DATA_CHAR next_data_char() +static int close_connection(connection * con); + +#ifndef HAVE_ATOLL +off_t LYatoll(const char *value) +{ + off_t result = 0; + + while (*value != '\0') { + result = (result * 10) + (off_t) (*value++ - '0'); + } + return result; +} +#endif + +#ifdef LY_FIND_LEAKS +/* + * This function frees module globals. - FM + */ +static void free_FTPGlobals(void) +{ + FREE(user_entered_password); + FREE(last_username_and_host); + if (control) { + if (control->socket != -1) + close_connection(control); + FREE(control); + } +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTVMS_name(const char *nn, + const char *fn) +{ + /* We try converting the filename into Files-11 syntax. That is, we assume + * first that the file is, like us, on a VMS node. We try remote (or + * local) DECnet access. Files-11, VMS, VAX and DECnet are trademarks of + * Digital Equipment Corporation. The node is assumed to be local if the + * hostname WITHOUT DOMAIN matches the local one. @@@ + */ + static char *vmsname; + char *filename = (char *) malloc(strlen(fn) + 1); + char *nodename = (char *) malloc(strlen(nn) + 2 + 1); /* Copies to hack */ + char *second; /* 2nd slash */ + char *last; /* last slash */ + + const char *hostname = HTHostName(); + + if (!filename || !nodename) + outofmem(__FILE__, "HTVMSname"); + + strcpy(filename, fn); + strcpy(nodename, ""); /* On same node? Yes if node names match */ + if (StrNCmp(nn, "localhost", 9)) { + const char *p; + const char *q; + + for (p = hostname, q = nn; + *p && *p != '.' && *q && *q != '.'; p++, q++) { + if (TOUPPER(*p) != TOUPPER(*q)) { + char *r; + + strcpy(nodename, nn); + r = StrChr(nodename, '.'); /* Mismatch */ + if (r) + *r = '\0'; /* Chop domain */ + strcat(nodename, "::"); /* Try decnet anyway */ + break; + } + } + } + + second = StrChr(filename + 1, '/'); /* 2nd slash */ + last = strrchr(filename, '/'); /* last slash */ + + if (!second) { /* Only one slash */ + HTSprintf0(&vmsname, "%s%s", nodename, filename + 1); + } else if (second == last) { /* Exactly two slashes */ + *second = '\0'; /* Split filename from disk */ + HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1); + *second = '/'; /* restore */ + } else { /* More than two slashes */ + char *p; + + *second = '\0'; /* Split disk from directories */ + *last = '\0'; /* Split dir from filename */ + HTSprintf0(&vmsname, "%s%s:[%s]%s", + nodename, filename + 1, second + 1, last + 1); + *second = *last = '/'; /* restore filename */ + if ((p = StrChr(vmsname, '[')) != 0) { + while (*p != '\0' && *p != ']') { + if (*p == '/') + *p = '.'; /* Convert dir sep. to dots */ + ++p; + } + } + } + FREE(nodename); + FREE(filename); + return vmsname; +} + +/* Procedure: Read a character from the data connection + * ---------------------------------------------------- + */ +static int next_data_char(void) +{ + int status; + + if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) + interrupted_in_next_data_char = 1; + if (status <= 0) + return EOF; + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + } +#ifdef NOT_ASCII + { + char c = *data_read_pointer++; + + return FROMASCII(c); + } +#else + return UCH(*data_read_pointer++); +#endif /* NOT_ASCII */ +} + +/* Close an individual connection + * + */ +static int close_connection(connection * con) +{ + connection *scan; + int status; + + CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket)); + status = NETCLOSE(con->socket); + if (TRACE && status != 0) { +#ifdef UNIX + CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno))); +#else + if (con->socket != INVSOC) + HTInetStatus("HTFTP:close_connection"); +#endif + } + con->socket = -1; + if (connections == con) { + connections = con->next; + return status; + } + for (scan = connections; scan; scan = scan->next) { + if (scan->next == con) { + scan->next = con->next; /* Unlink */ + if (control == con) + control = (connection *) 0; + return status; + } /*if */ + } /* for */ + return -1; /* very strange -- was not on list. */ +} + +static char *help_message_buffer = NULL; /* global :( */ + +static void init_help_message_cache(void) +{ + FREE(help_message_buffer); +} + +static void help_message_cache_add(char *string) +{ + if (help_message_buffer) + StrAllocCat(help_message_buffer, string); + else + StrAllocCopy(help_message_buffer, string); + + CTRACE((tfp, "Adding message to help cache: %s\n", string)); +} + +static char *help_message_cache_non_empty(void) +{ + return (help_message_buffer); +} + +static char *help_message_cache_contents(void) +{ + return (help_message_buffer); +} + +/* Send One Command + * ---------------- + * + * This function checks whether we have a control connection, and sends + * one command if given. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command should already be terminated with the CRLF pair. + * + * On exit, + * returns: 1 for success, + * or negative for communication failure (in which case + * the control connection will be closed). + */ +static int write_cmd(const char *cmd) +{ + int status; + + if (!control) { + CTRACE((tfp, "HTFTP: No control connection set up!!\n")); + return HT_NO_CONNECTION; + } + + if (cmd) { + CTRACE((tfp, " Tx: %s", cmd)); +#ifdef NOT_ASCII + { + char *p; + + for (p = cmd; *p; p++) { + *p = TOASCII(*p); + } + } +#endif /* NOT_ASCII */ + status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd)); + if (status < 0) { + CTRACE((tfp, + "HTFTP: Error %d sending command: closing socket %d\n", + status, control->socket)); + close_connection(control); + return status; + } + } + return 1; +} + +/* + * For each string in the list, check if it is found in the response text. + * If so, return TRUE. + */ +static BOOL find_response(HTList *list) +{ + BOOL result = FALSE; + HTList *p = list; + char *value; + + while ((value = (char *) HTList_nextObject(p)) != NULL) { + if (LYstrstr(response_text, value)) { + result = TRUE; + break; + } + } + return result; +} + +/* Execute Command and get Response + * -------------------------------- + * + * See the state machine illustrated in RFC959, p57. This implements + * one command/reply sequence. It also interprets lines which are to + * be continued, which are marked with a "-" immediately after the + * status code. + * + * Continuation then goes on until a line with a matching reply code + * an a space after it. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command must already be terminated with the CRLF pair. + * + * On exit, + * returns: The first digit of the reply type, + * or negative for communication failure. + */ +static int response(const char *cmd) +{ + int result; /* Three-digit decimal code */ + int continuation_response = -1; + int status; + + if ((status = write_cmd(cmd)) < 0) + return status; + + do { + char *p = response_text; + + for (;;) { + int ich = NEXT_CHAR; + + if (((*p++ = (char) ich) == LF) + || (p == &response_text[LINE_LENGTH])) { + + char continuation; + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + *p = '\0'; /* Terminate the string */ + CTRACE((tfp, " Rx: %s", response_text)); + + /* Check for login or help messages */ + if (!StrNCmp(response_text, "230-", 4) || + !StrNCmp(response_text, "250-", 4) || + !StrNCmp(response_text, "220-", 4)) + help_message_cache_add(response_text + 4); + + sscanf(response_text, "%d%c", &result, &continuation); + if (continuation_response == -1) { + if (continuation == '-') /* start continuation */ + continuation_response = result; + } else { /* continuing */ + if (continuation_response == result && + continuation == ' ') + continuation_response = -1; /* ended */ + } + if (result == 220 && find_response(broken_ftp_retr)) { + Broken_RETR = TRUE; + CTRACE((tfp, "This server is broken (RETR)\n")); + } +#ifdef INET6 + if (result == 220 && find_response(broken_ftp_epsv)) { + Broken_EPSV = TRUE; + CTRACE((tfp, "This server is broken (EPSV)\n")); + } +#endif + break; + } + /* if end of line */ + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + if (ich == EOF) { + CTRACE((tfp, "Error on rx: closing socket %d\n", + control->socket)); + strcpy(response_text, "000 *** TCP read error on response\n"); + close_connection(control); + return -1; /* End of file on response */ + } + } /* Loop over characters */ + + } while (continuation_response != -1); + + if (result == 421) { + CTRACE((tfp, "HTFTP: They close so we close socket %d\n", + control->socket)); + close_connection(control); + return -1; + } + if ((result == 255 && server_type == CMS_SERVER) && + (0 == strncasecomp(cmd, "CWD", 3) || + 0 == strcasecomp(cmd, "CDUP"))) { + /* + * Alas, CMS returns 255 on failure to CWD to parent of root. - PG + */ + result = 555; + } + return result / 100; +} + +static int send_cmd_1(const char *verb) +{ + char command[80]; + + sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF); + return response(command); +} + +static int send_cmd_2(const char *verb, const char *param) +{ + char *command = 0; + int status; + + HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF); + status = response(command); + FREE(command); + + return status; +} + +#define send_cwd(path) send_cmd_2("CWD", path) + +/* + * This function should try to set the macintosh server into binary mode. Some + * servers need an additional letter after the MACB command. + */ +static int set_mac_binary(eServerType ServerType) +{ + /* try to set mac binary mode */ + if (ServerType == APPLESHARE_SERVER || + ServerType == NETPRESENZ_SERVER) { + /* + * Presumably E means "Enable". - KW + */ + return (2 == response("MACB E\r\n")); + } else { + return (2 == response("MACB\r\n")); + } +} + +/* This function gets the current working directory to help + * determine what kind of host it is + */ + +static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* get the working directory (to see what it looks like) */ + int status = response("PWD\r\n"); + + if (status < 0) { + return; + } else { + cp = StrChr(response_text + 5, '"'); + if (cp) + *cp = '\0'; + if (*ServerType == TCPC_SERVER) { + *ServerType = ((response_text[5] == '/') ? + NCSA_SERVER : TCPC_SERVER); + CTRACE((tfp, "HTFTP: Treating as %s server.\n", + ((*ServerType == NCSA_SERVER) ? + "NCSA" : "TCPC"))); + } else if (response_text[5] == '/') { + /* path names beginning with / imply Unix, + * right? + */ + if (set_mac_binary(*ServerType)) { + *ServerType = NCSA_SERVER; + CTRACE((tfp, "HTFTP: Treating as NCSA server.\n")); + } else { + *ServerType = UNIX_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + } + return; + } else if (response_text[strlen(response_text) - 1] == ']') { + /* path names ending with ] imply VMS, right? */ + *ServerType = VMS_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } else { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Treating as Generic server.\n")); + } + + if ((*ServerType == NCSA_SERVER) || + (*ServerType == TCPC_SERVER) || + (*ServerType == PETER_LEWIS_SERVER) || + (*ServerType == NETPRESENZ_SERVER)) + set_mac_binary(*ServerType); + } +} + +/* This function turns MSDOS-like directory output off for + * Windows NT servers. + */ + +static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* This is a toggle. It seems we have to toggle in order to see + * the current state (after toggling), so we may end up toggling + * twice. - kw + */ + int status = response("SITE DIRSTYLE\r\n"); + + if (status != 2) { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n")); + return; + } else { + *UseList = TRUE; + /* Expecting one of: + * 200 MSDOS-like directory output is off + * 200 MSDOS-like directory output is on + * The following code doesn't look for the full exact string - + * who knows how the wording may change in some future version. + * If the first response isn't recognized, we toggle again + * anyway, under the assumption that it's more likely that + * the MSDOS setting was "off" originally. - kw + */ + cp = strstr(response_text + 4, "MSDOS"); + if (cp && strstr(cp, " off")) { + return; /* already off now. */ + } else { + response("SITE DIRSTYLE\r\n"); + } + } +} + +#define CheckForInterrupt(msg) \ + if (status == HT_INTERRUPTED) { \ + CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \ + _HTProgress(CONNECTION_INTERRUPTED); \ + NETCLOSE(control->socket); \ + control->socket = -1; \ + return HT_INTERRUPTED; \ + } + +/* Get a valid connection to the host + * ---------------------------------- + * + * On entry, + * arg points to the name of the host in a hypertext address + * On exit, + * returns <0 if error + * socket number if success + * + * This routine takes care of managing timed-out connections, and + * limiting the number of connections in use at any one time. + * + * It ensures that all connections are logged in if they exist. + * It ensures they have the port number transferred. + */ +static int get_connection(const char *arg, + HTParentAnchor *anchor) +{ + int status; + char *command = 0; + connection *con; + char *username = NULL; + char *password = NULL; + static BOOLEAN firstuse = TRUE; + + if (firstuse) { + /* + * Set up freeing at exit. - FM + */ +#ifdef LY_FIND_LEAKS + atexit(free_FTPGlobals); +#endif + firstuse = FALSE; + } + + if (control != 0) { + connection *next = control->next; + + if (control->socket != -1) { + NETCLOSE(control->socket); + } + memset(con = control, 0, sizeof(*con)); + con->next = next; + } else { + con = typecalloc(connection); + if (con == NULL) + outofmem(__FILE__, "get_connection"); + } + con->socket = -1; + + if (isEmpty(arg)) { + free(con); + return -1; /* Bad if no name specified */ + } + + /* Get node name: + */ + CTRACE((tfp, "get_connection(%s)\n", arg)); + { + char *p1 = HTParse(arg, "", PARSE_HOST); + char *p2 = strrchr(p1, '@'); /* user? */ + char *pw = NULL; + + if (p2 != NULL) { + username = p1; + *p2 = '\0'; /* terminate */ + p1 = p2 + 1; /* point to host */ + pw = StrChr(username, ':'); + if (pw != NULL) { + *pw++ = '\0'; + password = HTUnEscape(pw); + } + if (*username) + HTUnEscape(username); + + /* + * If the password doesn't exist then we are going to have to ask + * the user for it. The only problem is that we don't want to ask + * for it every time, so we will store away in a primitive fashion. + */ + if (!password) { + char *tmp = NULL; + + HTSprintf0(&tmp, "%s@%s", username, p1); + /* + * If the user@host is not equal to the last time through or + * user_entered_password has no data then we need to ask the + * user for the password. + */ + if (!last_username_and_host || + strcmp(tmp, last_username_and_host) || + !user_entered_password) { + + StrAllocCopy(last_username_and_host, tmp); + HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"), + username, p1); + FREE(user_entered_password); + user_entered_password = HTPromptPassword(tmp, NULL); + + } /* else we already know the password */ + password = user_entered_password; + FREE(tmp); + } + } + + if (!username) + FREE(p1); + } /* scope of p1 */ + + status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket); + + if (status < 0) { + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted on connect\n")); + } else { + CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n", + arg)); + } + if (status == HT_INTERRUPTED) { + _HTProgress(CONNECTION_INTERRUPTED); + status = HT_NOT_LOADED; + } else { + HTAlert(gettext("Unable to connect to FTP host.")); + } + if (con->socket != -1) { + NETCLOSE(con->socket); + } + + FREE(username); + if (control == con) + control = NULL; + FREE(con); + return status; /* Bad return */ + } + + CTRACE((tfp, "FTP connected, socket %d control %p\n", + con->socket, (void *) con)); + control = con; /* Current control connection */ + + /* Initialise buffering for control connection */ + HTInitInput(control->socket); + init_help_message_cache(); /* Clear the login message buffer. */ + + /* Now we log in Look up username, prompt for pw. + */ + status = response(NULL); /* Get greeting */ + CheckForInterrupt("at beginning of login"); + + server_type = GENERIC_SERVER; /* reset */ + if (status == 2) { /* Send username */ + char *cp; /* look at greeting text */ + + /* don't gettext() this -- incoming text: */ + if (strlen(response_text) > 4) { + if ((cp = strstr(response_text, " awaits your command")) || + (cp = strstr(response_text, " ready."))) { + *cp = '\0'; + } + cp = response_text + 4; + if (!strncasecomp(cp, "NetPresenz", 10)) + server_type = NETPRESENZ_SERVER; + } else { + cp = response_text; + } + StrAllocCopy(anchor->server, cp); + + status = send_cmd_2("USER", (username && *username) + ? username + : "anonymous"); + + CheckForInterrupt("while sending username"); + } + if (status == 3) { /* Send password */ + if (non_empty(password)) { + HTSprintf0(&command, "PASS %s%c%c", password, CR, LF); + } else { + /* + * No password was given; use mail-address. + */ + const char *the_address; + char *user = NULL; + const char *host = NULL; + char *cp; + + the_address = anonftp_password; + if (isEmpty(the_address)) + the_address = personal_mail_address; + if (isEmpty(the_address)) + the_address = LYGetEnv("USER"); + if (isEmpty(the_address)) + the_address = "WWWuser"; + + StrAllocCopy(user, the_address); + if ((cp = StrChr(user, '@')) != NULL) { + *cp++ = '\0'; + if (*cp == '\0') + host = HTHostName(); + else + host = cp; + } else { + host = HTHostName(); + } + + /* + * If host is not fully qualified, suppress it + * as ftp.uu.net prefers a blank to a bad name + */ + if (!(host) || StrChr(host, '.') == NULL) + host = ""; + + HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF); + FREE(user); + } + status = response(command); + FREE(command); + CheckForInterrupt("while sending password"); + } + FREE(username); + + if (status == 3) { + status = send_cmd_1("ACCT noaccount"); + CheckForInterrupt("while sending password"); + } + if (status != 2) { + CTRACE((tfp, "HTFTP: Login fail: %s", response_text)); + /* if (control->socket > 0) close_connection(control->socket); */ + return -1; /* Bad return */ + } + CTRACE((tfp, "HTFTP: Logged in.\n")); + + /* Check for host type */ + if (server_type != NETPRESENZ_SERVER) + server_type = GENERIC_SERVER; /* reset */ + use_list = FALSE; /* reset */ + if (response("SYST\r\n") == 2) { + /* we got a line -- what kind of server are we talking to? */ + if (StrNCmp(response_text + 4, + "UNIX Type: L8 MAC-OS MachTen", 28) == 0) { + server_type = MACHTEN_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MachTen server.\n")); + + } else if (strstr(response_text + 4, "UNIX") != NULL || + strstr(response_text + 4, "Unix") != NULL) { + server_type = UNIX_SERVER; + unsure_type = FALSE; /* to the best of out knowledge... */ + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + + } else if (strstr(response_text + 4, "MSDOS") != NULL) { + server_type = MSDOS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n")); + + } else if (StrNCmp(response_text + 4, "VMS", 3) == 0) { + char *tilde = strstr(arg, "/~"); + + use_list = TRUE; + if (tilde != 0 + && tilde[2] != 0 + && strstr(response_text + 4, "MadGoat") != 0) { + server_type = UNIX_SERVER; + CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n")); + } else { + server_type = VMS_SERVER; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } + + } else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) || + (StrNCmp(response_text + 4, "VM ", 3) == 0)) { + server_type = CMS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as CMS server.\n")); + + } else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) { + server_type = DCTS_SERVER; + CTRACE((tfp, "HTFTP: Treating as DCTS server.\n")); + + } else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) { + server_type = TCPC_SERVER; + CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + + } else if (server_type == NETPRESENZ_SERVER) { /* already set above */ + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) { + server_type = PETER_LEWIS_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) { + server_type = WINDOWS_NT_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) { + server_type = WINDOWS_2K_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) { + server_type = MS_WINDOWS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n")); + + } else if (StrNCmp(response_text + 4, + "MACOS AppleShare IP FTP Server", 30) == 0) { + server_type = APPLESHARE_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n")); + + } else { + server_type = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Ugh! A Generic server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + } + } else { + /* SYST fails :( try to get the type from the PWD command */ + get_ftp_pwd(&server_type, &use_list); + } + + return con->socket; /* Good return */ +} + +static void reset_master_socket(void) +{ + have_socket = FALSE; +} + +static void set_master_socket(int value) +{ + have_socket = (BOOLEAN) (value >= 0); + if (have_socket) + master_socket = (LYNX_FD) value; +} + +/* Close Master (listening) socket + * ------------------------------- + * + * + */ +static int close_master_socket(void) +{ + int status; + + if (have_socket) + FD_CLR(master_socket, &open_sockets); + + status = NETCLOSE((int) master_socket); + CTRACE((tfp, "HTFTP: Closed master socket %u\n", (unsigned) master_socket)); + + reset_master_socket(); + + if (status < 0) + return HTInetStatus(gettext("close master socket")); + else + return status; +} + +/* Open a master socket for listening on + * ------------------------------------- + * + * When data is transferred, we open a port, and wait for the server to + * connect with the data. + * + * On entry, + * have_socket Must be false, if master_socket is not setup already + * master_socket Must be negative if not set up already. + * On exit, + * Returns socket number if good + * less than zero if error. + * master_socket is socket number if good, else negative. + * port_number is valid if good. + */ +static int get_listen_socket(void) +{ + LY_SOCKADDR soc_A; + +#ifdef INET6 + unsigned short af; + LY_SOCKLEN slen; +#endif /* INET6 */ + int new_socket; /* Will be master_socket */ + + FD_ZERO(&open_sockets); /* Clear our record of open sockets */ + num_sockets = 0; + + FREE(port_command); +#ifndef REPEAT_LISTEN + if (have_socket) + return master_socket; /* Done already */ +#endif /* !REPEAT_LISTEN */ + +#ifdef INET6 + /* query address family of control connection */ + memset(&soc_A, 0, sizeof(soc_A)); + slen = (LY_SOCKLEN) sizeof(soc_A); + if (getsockname(control->socket, SOCKADDR_OF(soc_A), &slen) < 0) { + return HTInetStatus("getsockname failed"); + } + af = SOCKADDR_OF(soc_A)->sa_family; +#endif /* INET6 */ + +/* Create internet socket +*/ +#ifdef INET6 + new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP); +#else + new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif /* INET6 */ + + if (new_socket < 0) + return HTInetStatus(gettext("socket for master socket")); + + CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket)); + +/* Search for a free port. +*/ +#ifdef INET6 + memset(&soc_A, 0, sizeof(soc_A)); + SOCKADDR_OF(soc_A)->sa_family = (unsigned short) af; + switch (af) { + case AF_INET: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in); +#endif /* SIN6_LEN */ + break; + case AF_INET6: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + break; + default: + HTInetStatus("AF"); + } +#else + soc_A.soc_in.sin_family = AF_INET; /* Family = internet, host order */ + soc_A.soc_in.sin_addr.s_addr = INADDR_ANY; /* Any peer address */ +#endif /* INET6 */ +#ifdef POLL_PORTS + { + PortNumber old_port_number = port_number; + + for (port_number = (old_port_number + 1);; port_number++) { + int status; + + if (port_number > LAST_TCP_PORT) + port_number = FIRST_TCP_PORT; + if (port_number == old_port_number) { + return HTInetStatus("bind"); + } +#ifdef INET6 + soc_A.soc_in.sin_port = htons(port_number); +#else + soc_A.sin_port = htons(port_number); +#endif /* INET6 */ +#ifdef SOCKS + if (socks_flag) + if ((status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A))) == 0) { + break; + } else +#endif /* SOCKS */ + if ((status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A) + )) == 0) { + break; + } + CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n", + port_number, status, SOCKET_ERRNO)); + } /* for */ + } +#else + { + int status; + LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + CTRACE((tfp, "HTFTP: This host is %s\n", + HTInetString((void *) &soc_A.soc_in))); + + soc_A.soc_in.sin_port = 0; /* Unspecified: please allocate */ +#ifdef SOCKS + if (socks_flag) + status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + sizeof(soc_A)); + else +#endif /* SOCKS */ + status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A)); + if (status < 0) { + close(new_socket); + return HTInetStatus("bind"); + } + + address_length = sizeof(soc_A); +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + } +#endif /* POLL_PORTS */ + + CTRACE((tfp, "HTFTP: bound to port %d on %s\n", + (int) ntohs(soc_A.soc_in.sin_port), + HTInetString((void *) &soc_A.soc_in))); + +#ifdef REPEAT_LISTEN + if (have_socket) + (void) close_master_socket(); +#endif /* REPEAT_LISTEN */ + + set_master_socket(new_socket); + +/* Now we must find out who we are to tell the other guy +*/ + (void) HTHostName(); /* Make address valid - doesn't work */ +#ifdef INET6 + switch (SOCKADDR_OF(soc_A)->sa_family) { + case AF_INET: +#endif /* INET6 */ + HTSprintf0(&port_command, "PORT %d,%d,%d,%d,%d,%d%c%c", + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 1), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 2), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 3), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 1), + CR, LF); + +#ifdef INET6 + break; + + case AF_INET6: + { + char hostbuf[MAXHOSTNAMELEN]; + char portbuf[MAXHOSTNAMELEN]; + + getnameinfo(SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A), + hostbuf, + (socklen_t) sizeof(hostbuf), + portbuf, + (socklen_t) sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + HTSprintf0(&port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf, + CR, LF); + break; + } + default: + HTSprintf0(&port_command, "JUNK%c%c", CR, LF); + break; + } +#endif /* INET6 */ + if (port_command == NULL) + return -1; + + /* Inform TCP that we will accept connections + */ + { + int status; + +#ifdef SOCKS + if (socks_flag) + status = Rlisten((int) master_socket, 1); + else +#endif /* SOCKS */ + status = listen((int) master_socket, 1); + if (status < 0) { + reset_master_socket(); + return HTInetStatus("listen"); + } + } + CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n")); + FD_SET(master_socket, &open_sockets); + if ((master_socket + 1) > num_sockets) + num_sockets = master_socket + 1; + + return (int) master_socket; /* Good */ + +} /* get_listen_socket */ + +static const char *months[12] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* Procedure: Set the current and last year strings and date integer + * ----------------------------------------------------------------- + * + * Bug: + * This code is for sorting listings by date, if that option + * is selected in Lynx, and doesn't take into account time + * zones or ensure resetting at midnight, so the sort may not + * be perfect, but the actual date isn't changed in the display, + * i.e., the date is still correct. - FM + */ +static void set_years_and_date(void) +{ + char day[8], month[8], date[12]; + time_t NowTime; + int i; + char *printable; + + NowTime = time(NULL); + printable = ctime(&NowTime); + LYStrNCpy(day, printable + 8, 2); + if (day[0] == ' ') { + day[0] = '0'; + } + LYStrNCpy(month, printable + 4, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(date, "9999%02d%.2s", i % 100, day); + TheDate = atoi(date); + LYStrNCpy(ThisYear, printable + 20, 4); + sprintf(LastYear, "%d", (atoi(ThisYear) - 1) % 10000); + HaveYears = TRUE; +} + +typedef struct _EntryInfo { + char *filename; + char *linkname; /* symbolic link, if any */ + char *type; + char *date; + off_t size; + BOOLEAN display; /* show this entry? */ +#ifdef LONG_LIST + unsigned long file_links; + char *file_mode; + char *file_user; + char *file_group; +#endif +} EntryInfo; + +static void free_entryinfo_struct_contents(EntryInfo *entry_info) +{ + if (entry_info) { +#ifdef LONG_LIST + FREE(entry_info->file_mode); + FREE(entry_info->file_user); + FREE(entry_info->file_group); +#endif + FREE(entry_info->filename); + FREE(entry_info->linkname); + FREE(entry_info->type); + FREE(entry_info->date); + } + /* don't free the struct */ +} + +/* + * is_ls_date() -- + * Return TRUE if s points to a string of the form: + * "Sep 1 1990 " or + * "Sep 11 11:59 " or + * "Dec 12 1989 " or + * "FCv 23 1990 " ... + */ +static BOOLEAN is_ls_date(char *s) +{ + /* must start with three alpha characters */ + if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++))) + return FALSE; + + /* space or HT_NON_BREAK_SPACE */ + if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) { + return FALSE; + } + s++; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space */ + if (*s++ != ' ') + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* colon or digit */ + if (!(*s == ':' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* space */ + if (*s != ' ') + return FALSE; + + return TRUE; +} /* is_ls_date() */ + +/* + * Extract the name, size, and date from an EPLF line. - 08-06-96 DJB + */ +static void parse_eplf_line(char *line, + EntryInfo *info) +{ + char *cp = line; + char ct[26]; + off_t size; + time_t secs; + static time_t base; /* time() value on this OS in 1970 */ + static int flagbase = 0; + + if (!flagbase) { + struct tm t; + + t.tm_year = 70; + t.tm_mon = 0; + t.tm_mday = 0; + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = 0; + t.tm_isdst = -1; + base = mktime(&t); /* could return -1 */ + flagbase = 1; + } + + while (*cp) { + switch (*cp) { + case '\t': + StrAllocCopy(info->filename, cp + 1); + return; + case 's': + size = 0; + while (*(++cp) && (*cp != ',')) + size = (size * 10) + (off_t) (*cp - '0'); + info->size = size; + break; + case 'm': + secs = 0; + while (*(++cp) && (*cp != ',')) + secs = (secs * 10) + (*cp - '0'); + secs += base; /* assumes that time_t is #seconds */ + LYStrNCpy(ct, ctime(&secs), 24); + StrAllocCopy(info->date, ct); + break; + case '/': + StrAllocCopy(info->type, ENTRY_IS_DIRECTORY); + /* FALLTHRU */ + default: + while (*cp) { + if (*cp++ == ',') + break; + } + break; + } + } +} /* parse_eplf_line */ + +/* + * Extract the name, size, and date from an ls -l line. + */ +static void parse_ls_line(char *line, + EntryInfo *entry) +{ +#ifdef LONG_LIST + char *next; + char *cp; +#endif + int i, j; + off_t base = 1; + off_t size_num = 0; + + for (i = (int) strlen(line) - 1; + (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12])); + i--) { + ; /* null body */ + } + line[i] = '\0'; + if (i > 13) { + StrAllocCopy(entry->date, &line[i - 12]); + /* replace the 4th location with nbsp if it is a space or zero */ + if (entry->date[4] == ' ' || entry->date[4] == '0') + entry->date[4] = HT_NON_BREAK_SPACE; + /* make sure year or time is flush right */ + if (entry->date[11] == ' ') { + for (j = 11; j > 6; j--) { + entry->date[j] = entry->date[j - 1]; + } + } + } + j = i - 14; + while (isdigit(UCH(line[j]))) { + size_num += ((off_t) (line[j] - '0') * base); + base *= 10; + j--; + } + entry->size = size_num; + StrAllocCopy(entry->filename, &line[i + 1]); + +#ifdef LONG_LIST + line[j] = '\0'; + + /* + * Extract the file-permissions, as a string. + */ + if ((cp = StrChr(line, ' ')) != 0) { + if ((cp - line) == 10) { + *cp = '\0'; + StrAllocCopy(entry->file_mode, line); + *cp = ' '; + } + + /* + * Next is the link-count. + */ + next = 0; + entry->file_links = (unsigned long) strtol(cp, &next, 10); + if (next == 0 || *next != ' ') { + entry->file_links = 0; + next = cp; + } else { + cp = next; + } + /* + * Next is the user-name. + */ + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_user, cp); + /* + * Next is the group-name (perhaps). + */ + if (next != NULL) { + cp = (next + 1); + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_group, cp); + } + } +#endif +} + +/* + * Extract the name and size info and whether it refers to a directory from a + * LIST line in "dls" format. + */ +static void parse_dls_line(char *line, + EntryInfo *entry_info, + char **pspilledname) +{ + short j; + int base = 1; + off_t size_num = 0; + int len; + char *cps = NULL; + + /* README 763 Information about this server\0 + bin/ - \0 + etc/ = \0 + ls-lR 0 \0 + ls-lR.Z 3 \0 + pub/ = Public area\0 + usr/ - \0 + morgan 14 -> ../real/morgan\0 + TIMIT.mostlikely.Z\0 + 79215 \0 + */ + + len = (int) strlen(line); + if (len == 0) { + FREE(*pspilledname); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(line); + if (*cps == '\0') { /* only a filename, save it and return. */ + StrAllocCopy(*pspilledname, line); + entry_info->display = FALSE; + return; + } + if (len < 24 || line[23] != ' ' || + (isspace(UCH(line[0])) && !*pspilledname)) { + /* this isn't the expected "dls" format! */ + if (!isspace(UCH(line[0]))) + *cps = '\0'; + if (*pspilledname && !*line) { + entry_info->filename = *pspilledname; + *pspilledname = NULL; + if (entry_info->filename[strlen(entry_info->filename) - 1] == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + } else { + StrAllocCopy(entry_info->filename, line); + if (cps != line && *(cps - 1) == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + FREE(*pspilledname); + } + return; + } + + j = 22; + if (line[j] == '=' || line[j] == '-') { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } else { + while (isdigit(UCH(line[j]))) { + size_num += (line[j] - '0') * base; + base *= 10; + j--; + } + } + entry_info->size = size_num; + + cps = LYSkipBlanks(&line[23]); + if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') { + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3)); + entry_info->size = 0; /* don't display size */ + } + + if (j > 0) + line[j] = '\0'; + + LYTrimTrailing(line); + + len = (int) strlen(line); + if (len == 0 && *pspilledname && **pspilledname) { + line = *pspilledname; + len = (int) strlen(*pspilledname); + } + if (len > 0 && line[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + if (len > 1) + line[len - 1] = '\0'; + if (!entry_info->type) + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + + StrAllocCopy(entry_info->filename, line); + FREE(*pspilledname); +} /* parse_dls_line() */ + +/* + * parse_vms_dir_entry() + * Format the name, date, and size from a VMS LIST line + * into the EntryInfo structure - FM + */ +static void parse_vms_dir_entry(char *line, + EntryInfo *entry_info) +{ + int i, j; + off_t ialloc; + char *cp, *cpd, *cps, date[16]; + const char *sp = " "; + + /* Get rid of blank lines, and information lines. Valid lines have the ';' + * version number token. + */ + if (!strlen(line) || (cp = StrChr(line, ';')) == NULL) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name at VMS version number. */ + *cp++ = '\0'; + StrAllocCopy(entry_info->filename, line); + + /* Cast VMS non-README file and directory names to lowercase. */ + if (strstr(entry_info->filename, "READ") == NULL) { + LYLowerCase(entry_info->filename); + i = (int) strlen(entry_info->filename); + } else { + i = (int) ((strstr(entry_info->filename, "READ") + - entry_info->filename) + + 4); + if (!StrNCmp(&entry_info->filename[i], "ME", 2)) { + i += 2; + while (entry_info->filename[i] && entry_info->filename[i] != '.') { + i++; + } + } else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) { + i = (int) strlen(entry_info->filename); + } else { + i = 0; + } + LYLowerCase(entry_info->filename + i); + } + + /* Uppercase terminal .z's or _z's. */ + if ((--i > 2) && + entry_info->filename[i] == 'z' && + (entry_info->filename[i - 1] == '.' || + entry_info->filename[i - 1] == '_')) + entry_info->filename[i] = 'Z'; + + /* Convert any tabs in rest of line to spaces. */ + cps = cp - 1; + while ((cps = StrChr(cps + 1, '\t')) != NULL) + *cps = ' '; + + /* Collapse serial spaces. */ + i = 0; + j = 1; + cps = cp; + while (cps[j] != '\0') { + if (cps[i] == ' ' && cps[j] == ' ') + j++; + else + cps[++i] = cps[j++]; + } + cps[++i] = '\0'; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if ((cpd = StrChr(cp, '-')) != NULL && + strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) && + isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') { + + /* Month */ + *(cpd + 2) = (char) TOLOWER(*(cpd + 2)); + *(cpd + 3) = (char) TOLOWER(*(cpd + 3)); + sprintf(date, "%.3s ", cpd + 1); + + /* Day */ + if (isdigit(UCH(*(cpd - 2)))) + sprintf(date + 4, "%.2s ", cpd - 2); + else + sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1); + + /* Time or Year */ + if (!StrNCmp(ThisYear, cpd + 5, 4) && + strlen(cpd) > 15 && *(cpd + 12) == ':') { + sprintf(date + 7, "%.5s", cpd + 10); + } else { + sprintf(date + 7, " %.4s", cpd + 5); + } + + StrAllocCopy(entry_info->date, date); + } + + /* Track down the size */ + if ((cpd = StrChr(cp, '/')) != NULL) { + /* Appears be in used/allocated format */ + cps = cpd; + while (isdigit(UCH(*(cps - 1)))) + cps--; + if (cps < cpd) + *cpd = '\0'; + entry_info->size = LYatoll(cps); + cps = cpd + 1; + while (isdigit(UCH(*cps))) + cps++; + *cps = '\0'; + ialloc = LYatoll(cpd + 1); + /* Check if used is in blocks or bytes */ + if (entry_info->size <= ialloc) + entry_info->size *= 512; + + } else if (strtok(cp, sp) != NULL) { + /* We just initialized on the version number */ + /* Now let's hunt for a lone, size number */ + while ((cps = strtok(NULL, sp)) != NULL) { + cpd = cps; + while (isdigit(UCH(*cpd))) + cpd++; + if (*cpd == '\0') { + /* Assume it's blocks */ + entry_info->size = (LYatoll(cps) * 512); + break; + } + } + } + + TRACE_ENTRY("VMS", entry_info); + return; +} /* parse_vms_dir_entry() */ + +/* + * parse_ms_windows_dir_entry() -- + * Format the name, date, and size from an MS_WINDOWS LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +static void parse_ms_windows_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + cpd = cps; + StrAllocCopy(entry_info->filename, cp); + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd++ = '\0'; + if (isdigit(UCH(*cps))) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if (cpd < end) { + cpd = LYSkipBlanks(cpd); + if (strlen(cpd) > 17) { + *(cpd + 6) = '\0'; /* Month and Day */ + *(cpd + 11) = '\0'; /* Year */ + *(cpd + 17) = '\0'; /* Time */ + if (strcmp(ThisYear, cpd + 7)) + /* Not this year, so show the year */ + sprintf(date, "%.6s %.4s", cpd, (cpd + 7)); + else + /* Is this year, so show the time */ + sprintf(date, "%.6s %.5s", cpd, (cpd + 12)); + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("MS Windows", entry_info); + return; +} /* parse_ms_windows_dir_entry */ + +/* + * parse_windows_nt_dir_entry() -- + * Format the name, date, and size from a WINDOWS_NT LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +#ifdef NOTDEFINED +static void parse_windows_nt_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cpd = cp; + cps = LYSkipNonBlanks(end - 1); + cp = (cps + 1); + if (!strcmp(cp, ".") || !strcmp(cp, "..")) { + entry_info->display = FALSE; + return; + } + StrAllocCopy(entry_info->filename, cp); + if (cps < cpd) + return; + *cp = '\0'; + end = cp; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Cut out the date. */ + cp = cps = cpd; + cps = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end) { + entry_info->display = FALSE; + return; + } + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end || cpd == cps || strlen(cpd) < 7) { + entry_info->display = FALSE; + return; + } + if (strlen(cp) == 8 && + isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' && + isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') { + *(cp + 2) = '\0'; /* Month */ + i = atoi(cp) - 1; + *(cp + 5) = '\0'; /* Day */ + sprintf(date, "%.3s %.2s", months[i], (cp + 3)); + if (date[4] == '0') + date[4] = ' '; + cp += 6; /* Year */ + if (strcmp((ThisYear + 2), cp)) { + /* Not this year, so show the year */ + if (atoi(cp) < 70) { + sprintf(&date[6], " 20%.2s", cp); + } else { + sprintf(&date[6], " 19%.2s", cp); + } + } else { + /* Is this year, so show the time */ + *(cpd + 2) = '\0'; /* Hour */ + i = atoi(cpd); + if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p') + i += 12; + sprintf(&date[6], " %02d:%.2s", i, (cpd + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd = '\0'; + if (isdigit(*cps)) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Wrap it up */ + CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n", + entry_info->filename, + NonNull(entry_info->date), + entry_info->size)); + return; +} /* parse_windows_nt_dir_entry */ +#endif /* NOTDEFINED */ + +/* + * parse_cms_dir_entry() -- + * Format the name, date, and size from a VM/CMS line into + * the EntryInfo structure. - FM + */ +static void parse_cms_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int RecordLength = 0; + int Records = 0; + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + StrAllocCopy(entry_info->filename, cp); + if (StrChr(entry_info->filename, '.') != NULL) + /* If we already have a dot, we did an NLST. */ + return; + cp = LYSkipBlanks(cps); + if (!(*cp)) { + /* If we don't have more, we've misparsed. */ + FREE(entry_info->filename); + FREE(entry_info->type); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) { + /* It's an SFS directory. */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + entry_info->size = 0; + } else { + /* It's a file. */ + cp--; + *cp = '.'; + StrAllocCat(entry_info->filename, cp); + + /* Track down the VM/CMS RECFM or type. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + /* Check cp here, if it's relevant someday. */ + } + } + + /* Track down the record length or dash. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + RecordLength = atoi(cp); + } + } + + /* Track down the number of records or the dash. */ + cp = cps; + if (cps < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + Records = atoi(cp); + } + if (Records > 0 && RecordLength > 0) { + /* Compute an approximate size. */ + entry_info->size = ((off_t) Records * (off_t) RecordLength); + } + } + + /* Set the years and date, if we don't have them yet. */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + cpd = cps; + if (((cps < end) && + (cps = StrChr(cpd, ':')) != NULL) && + (cps < (end - 3) && + isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) { + cps += 3; + *cps = '\0'; + if ((cps - cpd) >= 14) { + cpd = (cps - 14); + *(cpd + 2) = '\0'; /* Month */ + *(cpd + 5) = '\0'; /* Day */ + *(cpd + 8) = '\0'; /* Year */ + cps -= 5; /* Time */ + if (*cpd == ' ') + *cpd = '0'; + i = atoi(cpd) - 1; + sprintf(date, "%.3s %.2s", months[i], (cpd + 3)); + if (date[4] == '0') + date[4] = ' '; + cpd += 6; /* Year */ + if (strcmp((ThisYear + 2), cpd)) { + /* Not this year, so show the year. */ + if (atoi(cpd) < 70) { + sprintf(&date[6], " 20%.2s", cpd); + } else { + sprintf(&date[6], " 19%.2s", cpd); + } + } else { + /* Is this year, so show the time. */ + *(cps + 2) = '\0'; /* Hour */ + i = atoi(cps); + sprintf(&date[6], " %02d:%.2s", i, (cps + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("VM/CMS", entry_info); + return; +} /* parse_cms_dir_entry */ + +/* + * Given a line of LIST/NLST output in entry, return results and a file/dir + * name in entry_info struct + * + * If first is true, this is the first name in a directory. + */ +static EntryInfo *parse_dir_entry(char *entry, + BOOLEAN *first, + char **pspilledname) +{ + EntryInfo *entry_info; + int i; + int len; + BOOLEAN remove_size = FALSE; + char *cp; + + entry_info = typecalloc(EntryInfo); + + if (entry_info == NULL) + outofmem(__FILE__, "parse_dir_entry"); + + entry_info->display = TRUE; + + switch (server_type) { + case DLS_SERVER: + + /* + * Interpret and edit LIST output from a Unix server in "dls" format. + * This one must have claimed to be Unix in order to get here; if the + * first line looks fishy, we revert to Unix and hope that fits better + * (this recovery is untested). - kw + */ + + if (*first) { + len = (int) strlen(entry); + if (!len || entry[0] == ' ' || + (len >= 24 && entry[23] != ' ') || + (len < 24 && StrChr(entry, ' '))) { + server_type = UNIX_SERVER; + CTRACE((tfp, + "HTFTP: Falling back to treating as Unix server.\n")); + } else { + *first = FALSE; + } + } + + if (server_type == DLS_SERVER) { + /* if still unchanged... */ + parse_dls_line(entry, entry_info, pspilledname); + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + /* fall through if server_type changed for *first == TRUE ! */ + /* FALLTHRU */ + case UNIX_SERVER: + case PETER_LEWIS_SERVER: + case MACHTEN_SERVER: + case MSDOS_SERVER: + case WINDOWS_NT_SERVER: + case WINDOWS_2K_SERVER: + case APPLESHARE_SERVER: + case NETPRESENZ_SERVER: + /* + * Check for EPLF output (local times). + */ + if (*entry == '+') { + parse_eplf_line(entry, entry_info); + break; + } + + /* + * Interpret and edit LIST output from Unix server. + */ + len = (int) strlen(entry); + if (*first) { + /* don't gettext() this -- incoming text: */ + if (!strcmp(entry, "can not access directory .")) { + /* + * Don't reset *first, nothing real will follow. - KW + */ + entry_info->display = FALSE; + return (entry_info); + } + *first = FALSE; + if (!StrNCmp(entry, "total ", 6) || + strstr(entry, "not available") != NULL) { + entry_info->display = FALSE; + return (entry_info); + } else if (unsure_type) { + /* this isn't really a unix server! */ + server_type = GENERIC_SERVER; + entry_info->display = FALSE; + return (entry_info); + } + } + + /* + * Check first character of ls -l output. + */ + if (TOUPPER(entry[0]) == 'D') { + /* + * It's a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } else if (entry[0] == 'l') { + /* + * It's a symbolic link, does the user care about knowing if it is + * symbolic? I think so since it might be a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + remove_size = TRUE; /* size is not useful */ + + /* + * Strip off " -> pathname". + */ + for (i = len - 1; (i > 3) && + (!isspace(UCH(entry[i])) || + (entry[i - 1] != '>') || + (entry[i - 2] != '-') || + (entry[i - 3] != ' ')); i--) ; /* null body */ + if (i > 3) { + entry[i - 3] = '\0'; + StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i)); + } + } + /* link */ + parse_ls_line(entry, entry_info); + + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + /* + * Goto the bottom and get real type. + */ + break; + + case VMS_SERVER: + /* + * Interpret and edit LIST output from VMS server and convert + * information lines to zero length. + */ + parse_vms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + + /* + * Trim off VMS directory extensions. + */ + len = (int) strlen(entry_info->filename); + if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) { + entry_info->filename[len - 4] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + case MS_WINDOWS_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_ms_windows_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + +#ifdef NOTDEFINED + case WINDOWS_NT_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_windows_nt_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; +#endif /* NOTDEFINED */ + + case CMS_SERVER: + { + /* + * Interpret and edit LIST output from VM/CMS server and convert + * any information lines to zero length. + */ + parse_cms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + + case NCSA_SERVER: + case TCPC_SERVER: + /* + * Directories identified by trailing "/" characters. + */ + StrAllocCopy(entry_info->filename, entry); + len = (int) strlen(entry); + if (entry[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + entry[len - 1] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + default: + /* + * We can't tell if it is a directory since we only did an NLST :( List + * bad file types anyways? NOT! + */ + StrAllocCopy(entry_info->filename, entry); + return (entry_info); /* mostly empty info */ + + } /* switch (server_type) */ + +#ifdef LONG_LIST + (void) remove_size; +#else + if (remove_size && entry_info->size) { + entry_info->size = 0; + } +#endif + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (strlen(entry_info->filename) > 3) { + if (((cp = strrchr(entry_info->filename, '.')) != NULL && + 0 == strncasecomp(cp, ".me", 3)) && + (cp[3] == '\0' || cp[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix server + * but has the string "read.me", or if it's not a Unix server. - + * FM + */ + if ((server_type != UNIX_SERVER) || + (cp > (entry_info->filename + 3) && + 0 == strncasecomp((cp - 4), "read.me", 7))) { + StrAllocCopy(entry_info->type, STR_PLAINTEXT); + } + } + } + + /* + * Get real types eventually. + */ + if (!entry_info->type) { + const char *cp2; + HTFormat format; + HTAtom *encoding; /* @@ not used at all */ + + format = HTFileFormat(entry_info->filename, &encoding, &cp2); + + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + + StrAllocCopy(entry_info->type, cp2); + } + + return (entry_info); +} + +static void formatDate(char target[16], EntryInfo *entry) +{ + char temp[8], month[4]; + int i; + + /* + * Set up for sorting in reverse chronological order. - FM + */ + if (entry->date[9] == ':') { + strcpy(target, "9999"); + LYStrNCpy(temp, &entry->date[7], 5); + if (temp[0] == ' ') { + temp[0] = '0'; + } + } else { + LYStrNCpy(target, &entry->date[8], 4); + strcpy(temp, "00:00"); + } + LYStrNCpy(month, entry->date, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%02d", i % 100); + strcat(target, month); + StrNCat(target, &entry->date[4], 2); + if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) { + target[6] = '0'; + } + + /* If no year given, assume last year if it would otherwise be in the + * future by more than one day. The one day tolerance is to account for a + * possible timezone difference. - kw + */ + if (target[0] == '9' && atoi(target) > TheDate + 1) { + for (i = 0; i < 4; i++) { + target[i] = LastYear[i]; + } + } + strcat(target, temp); +} + +static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2) +{ + int status; + char date1[16], date2[16]; + int result = strcmp(entry1->filename, entry2->filename); + + switch (HTfileSortMethod) { + case FILE_BY_SIZE: + /* both equal or both 0 */ + if (entry1->size > entry2->size) + result = 1; + else if (entry1->size < entry2->size) + result = -1; + break; + + case FILE_BY_TYPE: + if (entry1->type && entry2->type) { + status = strcasecomp(entry1->type, entry2->type); + if (status) + result = status; + } + break; + + case FILE_BY_DATE: + if (entry1->date && entry2->date && + strlen(entry1->date) == 12 && + strlen(entry2->date) == 12) { + /* + * Set the years and date, if we don't have them yet. + */ + if (!HaveYears) { + set_years_and_date(); + } + formatDate(date1, entry1); + formatDate(date2, entry2); + /* + * Do the comparison. - FM + */ + status = strcasecomp(date2, date1); + if (status) + result = status; + } + break; + + case FILE_BY_NAME: + default: + break; + } + return result; +} + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf(bufp, fmt, value); + } else if (*bufp && !(value && *value)) { + ; + } else if (value) { + StrAllocCat(*bufp, value); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (value)); + + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + unsigned long value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sld", + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%lu", value); + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static void FlushParse(HTStructured * target, char **buf) +{ + if (*buf && **buf) { + PUTS(*buf); + **buf = '\0'; + } +} + +static void LYListFmtParse(const char *fmtstr, + EntryInfo *data, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + BOOL is_directory = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'D')); + BOOL is_symlinked = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'L')); + BOOL remove_size = (BOOL) (is_directory || is_symlinked); + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + StrAllocCat(buf, start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + StrAllocCat(buf, start); + continue; + + case 'A': + case 'a': /* anchor */ + FlushParse(target, &buf); + HTDirEntry(target, tail, data->filename); + FormatStr(&buf, start, data->filename); + PUTS(buf); + END(HTML_A); + if (buf != 0) + *buf = '\0'; + if (c != 'A' && data->linkname != 0) { + PUTS(" -> "); + PUTS(data->linkname); + } + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (is_directory) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else if (is_symlinked) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(data->filename, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + if (data->date) { + FormatStr(&buf, start, data->date); + } else { + FormatStr(&buf, start, " * "); + } + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (remove_size) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + /* FIXME - this is inconsistent with HTFile.c, but historical */ + if (data->size < 1024) { + FormatSize(&buf, start, data->size); + StrAllocCat(buf, " bytes"); + } else { + FormatSize(&buf, start, data->size / 1024); + StrAllocCat(buf, "Kb"); + } + break; + +#ifdef LONG_LIST + case 'p': /* unix-style permission bits */ + FormatStr(&buf, start, NonNull(data->file_mode)); + break; + + case 'o': /* owner */ + FormatStr(&buf, start, NonNull(data->file_user)); + break; + + case 'g': /* group */ + FormatStr(&buf, start, NonNull(data->file_group)); + break; + + case 'l': /* link count */ + FormatNum(&buf, start, data->file_links); + break; +#endif + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + + s++; + } + if (buf) { + LYTrimTrailing(buf); + FlushParse(target, &buf); + FREE(buf); + } + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Read a directory into an hypertext object from the data socket + * -------------------------------------------------------------- + * + * On entry, + * anchor Parent anchor to link the this node to + * address Address of the directory + * On exit, + * returns HT_LOADED if OK + * <0 if error. + */ +static int read_directory(HTParentAnchor *parent, + const char *address, + HTFormat format_out, + HTStream *sink) +{ + int status; + BOOLEAN WasInterrupted = FALSE; + HTStructured *target = HTML_new(parent, format_out, sink); + char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION); + EntryInfo *entry_info; + BOOLEAN first = TRUE; + char *lastpath = NULL; /* prefix for link, either "" (for root) or xxx */ + BOOL tildeIsTop = FALSE; + +#ifndef LONG_LIST + char string_buffer[64]; +#endif + + _HTProgress(gettext("Receiving FTP directory.")); + + /* + * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be + * recalculated for each directory request. Otherwise we have a problem + * with long-running sessions assuming the wrong date for today. - kw + */ + HaveYears = FALSE; + /* + * Check whether we always want the home directory treated as Welcome. - + * FM + */ + if (server_type == VMS_SERVER) + tildeIsTop = TRUE; + + /* + * This should always come back FALSE, since the flag is set only for local + * directory listings if LONG_LIST was defined on compilation, but we could + * someday set up an equivalent listing for Unix ftp servers. - FM + */ + (void) HTDirTitles(target, parent, format_out, tildeIsTop); + + data_read_pointer = data_write_pointer = data_buffer; + + if (*filename == '\0') { /* Empty filename: use root. */ + StrAllocCopy(lastpath, "/"); + } else if (!strcmp(filename, "/")) { /* Root path. */ + StrAllocCopy(lastpath, "/foo/.."); + } else { + char *p = strrchr(filename, '/'); /* Find the lastslash. */ + char *cp; + + if (server_type == CMS_SERVER) { + StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */ + } else { + StrAllocCopy(lastpath, p + 1); /* Take slash off the beginning. */ + } + if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */ + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + } + } + FREE(filename); + + { + HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs); + int ic; + HTChunk *chunk = HTChunkCreate(128); + int BytesReceived = 0; + int BytesReported = 0; + char NumBytes[64]; + char *spilledname = NULL; + + PUTC('\n'); /* prettier LJM */ + for (ic = 0; ic != EOF;) { /* For each entry in the directory */ + HTChunkClear(chunk); + + if (HTCheckForInterrupt()) { + CTRACE((tfp, + "read_directory: interrupted after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } + + /* read directory entry + */ + interrupted_in_next_data_char = FALSE; + for (;;) { /* Read in one line as filename */ + ic = NEXT_DATA_CHAR; + AgainForMultiNet: + if (interrupted_in_next_data_char) { + CTRACE((tfp, + "read_directory: interrupted_in_next_data_char after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } else if ((char) ic == CR || (char) ic == LF) { /* Terminator? */ + if (chunk->size != 0) { /* got some text */ + /* Deal with MultiNet's wrapping of long lines */ + if (server_type == VMS_SERVER) { + /* Deal with MultiNet's wrapping of long lines - F.M. */ + if (data_read_pointer < data_write_pointer && + *(data_read_pointer + 1) == ' ') + data_read_pointer++; + else if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, + DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) { + interrupted_in_next_data_char = 1; + goto AgainForMultiNet; + } + if (status <= 0) { + ic = EOF; + break; + } + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + if (*data_read_pointer == ' ') + data_read_pointer++; + else + break; + } else + break; + } else + break; /* finish getting one entry */ + } + } else if (ic == EOF) { + break; /* End of file */ + } else { + HTChunkPutc(chunk, UCH(ic)); + } + } + HTChunkTerminate(chunk); + + BytesReceived += chunk->size; + if (BytesReceived > BytesReported + 1024) { +#ifdef _WINDOWS + sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"), + BytesReceived, ws_read_per_sec); +#else + sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived); +#endif + HTProgress(NumBytes); + BytesReported = BytesReceived; + } + + if (ic == EOF && chunk->size == 1) + /* 1 means empty: includes terminating 0 */ + break; + CTRACE((tfp, "HTFTP: Line in %s is %s\n", + lastpath, chunk->data)); + + entry_info = parse_dir_entry(chunk->data, &first, &spilledname); + if (entry_info->display) { + FREE(spilledname); + CTRACE((tfp, "Adding file to BTree: %s\n", + entry_info->filename)); + HTBTree_add(bt, entry_info); + } else { + free_entryinfo_struct_contents(entry_info); + FREE(entry_info); + } + + } /* next entry */ + + unload_btree: + + HTChunkFree(chunk); + FREE(spilledname); + + /* print out the handy help message if it exists :) */ + if (help_message_cache_non_empty()) { + START(HTML_PRE); + START(HTML_HR); + PUTC('\n'); + PUTS(help_message_cache_contents()); + init_help_message_cache(); /* to free memory */ + START(HTML_HR); + PUTC('\n'); + } else { + START(HTML_PRE); + PUTC('\n'); + } + + /* Run through tree printing out in order + */ + { +#ifndef LONG_LIST +#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */ + char *p, name_buff[256]; + int name_len, dot_len; + +#define FNAME_WIDTH 30 +#define FILE_GAP 1 + +#endif + int i; +#endif + HTBTElement *ele; + + for (ele = HTBTree_next(bt, NULL); + ele != NULL; + ele = HTBTree_next(bt, ele)) { + entry_info = (EntryInfo *) HTBTree_object(ele); + +#ifdef LONG_LIST + LYListFmtParse(ftp_format, + entry_info, + target, + lastpath); +#else + if (entry_info->date) { + PUTS(entry_info->date); + PUTS(" "); + } else { + PUTS(" * "); + } + + if (entry_info->type) { + for (i = 0; entry_info->type[i] != '\0' && i < 16; i++) + PUTC(entry_info->type[i]); + for (; i < 17; i++) + PUTC(' '); + } + /* start the anchor */ + HTDirEntry(target, lastpath, entry_info->filename); +#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */ + name_len = strlen(entry_info->filename); + + sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename); + + if (name_len < FNAME_WIDTH) { + dot_len = FNAME_WIDTH - FILE_GAP - name_len; + if (dot_len > 0) { + p = name_buff + name_len + 1; + while (dot_len-- > 0) + *p++ = '.'; + } + } else { + name_buff[FNAME_WIDTH] = '\0'; + } + + PUTS(name_buff); +#else + PUTS(entry_info->filename); +#endif + END(HTML_A); + + if (entry_info->size) { +#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */ + if (entry_info->size < 1024) + sprintf(string_buffer, "%6ld bytes", + entry_info->size); + else + sprintf(string_buffer, "%6ld Kb", + entry_info->size / 1024); +#else + if (entry_info->size < 1024) + sprintf(string_buffer, " %lu bytes", + (unsigned long) entry_info->size); + else + sprintf(string_buffer, " %luKb", + (unsigned long) entry_info->size / 1024); +#endif + PUTS(string_buffer); + } else if (entry_info->linkname != 0) { + PUTS(" -> "); + PUTS(entry_info->linkname); + } + + PUTC('\n'); /* end of this entry */ +#endif + + free_entryinfo_struct_contents(entry_info); + } + } + END(HTML_PRE); + END(HTML_BODY); + FREE_TARGET; + HTBTreeAndObject_free(bt); + } + + FREE(lastpath); + + if (WasInterrupted || data_soc != -1) { /* should always be true */ + /* + * Without closing the data socket first, the response(NULL) later may + * hang. Some servers expect the client to fin/ack the close of the + * data connection before proceeding with the conversation on the + * control connection. - kw + */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + data_soc = -1; + } + + if (WasInterrupted || HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + } + return HT_LOADED; +} + +/* + * Setup an FTP connection. + */ +static int setup_connection(const char *name, + HTParentAnchor *anchor) +{ + int retry; /* How many times tried? */ + int status = HT_NO_CONNECTION; + + CTRACE((tfp, "setup_connection(%s)\n", name)); + + /* set use_list to NOT since we don't know what kind of server + * this is yet. And set the type to GENERIC + */ + use_list = FALSE; + server_type = GENERIC_SERVER; + Broken_RETR = FALSE; + +#ifdef INET6 + Broken_EPSV = FALSE; +#endif + + for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */ + status = get_connection(name, anchor); + if (status < 0) { + break; + } + + if (!ftp_local_passive) { + status = get_listen_socket(); + if (status < 0) { + NETCLOSE(control->socket); + control->socket = -1; +#ifdef INET6 + if (have_socket) + (void) close_master_socket(); +#else + close_master_socket(); +#endif /* INET6 */ + /* HT_INTERRUPTED would fall through, if we could interrupt + somehow in the middle of it, which we currently can't. */ + break; + } +#ifdef REPEAT_PORT + /* Inform the server of the port number we will listen on + */ + status = response(port_command); + FREE(port_command); + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n")); + _HTProgress(CONNECTION_INTERRUPTED); + NETCLOSE(control->socket); + control->socket = -1; + close_master_socket(); + status = HT_INTERRUPTED; + break; + } + if (status != 2) { /* Could have timed out */ + if (status < 0) + continue; /* try again - net error */ + status = -status; /* bad reply */ + break; + } + CTRACE((tfp, "HTFTP: Port defined.\n")); +#endif /* REPEAT_PORT */ + } else { /* Tell the server to be passive */ + char *command = NULL; + const char *p = "?"; + int h0, h1, h2, h3, p0, p1; /* Parts of reply */ + +#ifdef INET6 + char dst[LINE_LENGTH + 1]; +#endif + + data_soc = status; + +#ifdef INET6 + /* see RFC 2428 */ + if (Broken_EPSV) + status = 1; + else + status = send_cmd_1(p = "EPSV"); + if (status < 0) /* retry or Bad return */ + continue; + else if (status != 2) { + status = send_cmd_1(p = "PASV"); + if (status < 0) { /* retry or Bad return */ + continue; + } else if (status != 2) { + status = -status; /* bad reply */ + break; + } + } + + if (strcmp(p, "PASV") == 0) { + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); + sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3); + } else if (strcmp(p, "EPSV") == 0) { + char c0, c1, c2, c3; + LY_SOCKADDR ss; + LY_SOCKLEN sslen; + + /* + * EPSV bla (|||port|) + */ + for (p = response_text; *p && !isspace(UCH(*p)); p++) { + ; /* null body */ + } + for ( /*nothing */ ; + *p && *p != '('; + p++) { /*) */ + ; /* null body */ + } + status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3); + if (status != 5) { + fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) p0; + + sslen = (LY_SOCKLEN) sizeof(ss); + if (getpeername(control->socket, SOCKADDR_OF(ss), &sslen) < 0) { + fprintf(tfp, "HTFTP: getpeername(control) failed\n"); + status = HT_NO_CONNECTION; + break; + } + if (getnameinfo(SOCKADDR_OF(ss), + sslen, + dst, + (socklen_t) sizeof(dst), + NULL, 0, NI_NUMERICHOST)) { + fprintf(tfp, "HTFTP: getnameinfo failed\n"); + status = HT_NO_CONNECTION; + break; + } + } +#else + status = send_cmd_1("PASV"); + if (status != 2) { + if (status < 0) + continue; /* retry or Bad return */ + status = -status; /* bad reply */ + break; + } + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); +#endif /* INET6 */ + CTRACE((tfp, "HTFTP: Server is listening on port %d\n", + passive_port)); + + /* Open connection for data: */ + +#ifdef INET6 + HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port); +#else + HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/", + STR_FTP_URL, h0, h1, h2, h3, passive_port); +#endif + status = HTDoConnect(command, "FTP data", passive_port, &data_soc); + FREE(command); + + if (status < 0) { + (void) HTInetStatus(gettext("connect for data")); + NETCLOSE(data_soc); + break; + } + + CTRACE((tfp, "FTP data connected, socket %d\n", data_soc)); + } + status = 0; + break; /* No more retries */ + + } /* for retries */ + CTRACE((tfp, "setup_connection returns %d\n", status)); + return status; +} + +/* Retrieve File from Server + * ------------------------- + * + * On entry, + * name WWW address of a file: document, including hostname + * On exit, + * returns Socket number for file if good. + * <0 if bad. + */ +int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + BOOL isDirectory = NO; + HTAtom *encoding = NULL; + int status, final_status; + int outstanding = 1; /* outstanding control connection responses + + that we are willing to wait for, if we + get to the point of reading data - kw */ + HTFormat format; + + CTRACE((tfp, "HTFTPLoad(%s) %s connection\n", + name, + (ftp_local_passive + ? "passive" + : "normal"))); + + HTReadProgress((off_t) 0, (off_t) 0); + + status = setup_connection(name, anchor); + if (status < 0) + return status; /* Failed with this code */ + + /* Ask for the file: + */ + { + char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *fname = filename; /* Save for subsequent free() */ + char *vmsname = NULL; + BOOL binary; + const char *type = NULL; + char *types = NULL; + char *cp; + + if (server_type == CMS_SERVER) { + /* If the unescaped path has a %2f, reject it as illegal. - FM */ + if (((cp = strstr(filename, "%2")) != NULL) && + TOUPPER(cp[2]) == 'F') { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to illegal escaped slash.\n")); + return -1; + } + } + + if (!*filename) { + StrAllocCopy(filename, "/"); + type = "D"; + } else if ((type = types = strrchr(filename, ';')) != NULL) { + /* + * Check and trim the type= parameter. - FM + */ + if (!strncasecomp((type + 1), "type=", 5)) { + switch (TOUPPER(*(type + 6))) { + case 'D': + *types = '\0'; + type = "D"; + break; + case 'A': + *types = '\0'; + type = "A"; + break; + case 'I': + *types = '\0'; + type = "I"; + break; + default: + type = ""; + break; + } + if (!*filename) { + *filename = '/'; + *(filename + 1) = '\0'; + } + } + if (*type != '\0') { + CTRACE((tfp, "HTFTP: type=%s\n", type)); + } + } + HTUnEscape(filename); + CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename)); + if (filename[1] == '~') { + /* + * Check if translation of HOME as tilde is supported, + * and adjust filename if so. - FM + */ + char *cp2 = NULL; + char *fn = NULL; + + if ((cp2 = StrChr((filename + 1), '/')) != NULL) { + *cp2 = '\0'; + } + status = send_cmd_1("PWD"); + if (status == 2 && response_text[5] == '/') { + status = send_cwd(filename + 1); + if (status == 2) { + StrAllocCopy(fn, (filename + 1)); + if (cp2) { + *cp2 = '/'; + if (fn[strlen(fn) - 1] != '/') { + StrAllocCat(fn, cp2); + } else { + StrAllocCat(fn, (cp2 + 1)); + } + cp2 = NULL; + } + FREE(fname); + fname = filename = fn; + } + } + if (cp2) { + *cp2 = '/'; + } + } + if (strlen(filename) > 3) { + char *cp2; + + if (((cp2 = strrchr(filename, '.')) != NULL && + 0 == strncasecomp(cp2, ".me", 3)) && + (cp2[3] == '\0' || cp2[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix + * server but has the string "read.me", or if it's not a Unix + * server. - FM + */ + if ((server_type != UNIX_SERVER) || + (cp2 > (filename + 3) && + 0 == strncasecomp((cp2 - 4), "read.me", 7))) { + *cp2 = '\0'; + format = HTFileFormat(filename, &encoding, NULL); + *cp2 = '.'; + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + format = HTCharsetFormat(format, anchor, -1); + binary = (BOOL) (encoding != WWW_ENC_8BIT && + encoding != WWW_ENC_7BIT); + if (!binary && + /* + * Force binary if we're in source, download or dump mode and this is + * not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for + * newlines in text files. Can't do this for VM/CMS or we'll get raw + * EBCDIC. - FM + */ + (format_out == WWW_SOURCE || + format_out == WWW_DOWNLOAD || + format_out == WWW_DUMP) && + (server_type != CMS_SERVER)) + binary = TRUE; + if (!binary && type && *type == 'I') { + /* + * Force binary if we had ;type=I - FM + */ + binary = TRUE; + } else if (binary && type && *type == 'A') { + /* + * Force ASCII if we had ;type=A - FM + */ + binary = FALSE; + } + if (binary != control->is_binary) { + /* + * Act on our setting if not already set. - FM + */ + const char *mode = binary ? "I" : "A"; + + status = send_cmd_2("TYPE", mode); + if (status != 2) { + init_help_message_cache(); /* to free memory */ + return ((status < 0) ? status : -status); + } + control->is_binary = binary; + } + switch (server_type) { + /* + * Handle what for Lynx are special case servers, e.g., for which + * we respect RFC 1738, or which have known conflicts in suffix + * mappings. - FM + */ + case VMS_SERVER: + { + char *cp1, *cp2; + BOOL included_device = FALSE; + BOOL found_tilde = FALSE; + + /* Accept only Unix-style filename */ + if (StrChr(filename, ':') != NULL || + StrChr(filename, '[') != NULL) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to non-Unix-style syntax.\n")); + return -1; + } + /* Handle any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) { + int i; + + included_device = TRUE; + for (i = 0; filename[(i + 1)]; i++) + filename[i] = filename[(i + 1)]; + filename[i] = '\0'; + CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename)); + cp = HTVMS_name("", filename); + CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp)); + if ((cp1 = strrchr(cp, ']')) != NULL) { + strcpy(filename, ++cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + *cp1 = '\0'; + status = send_cwd(cp); + if (status != 2) { + char *dotslash = 0; + + if ((cp1 = StrChr(cp, '[')) != NULL) { + *cp1++ = '\0'; + status = send_cwd(cp); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + HTSprintf0(&dotslash, "[.%s", cp1); + status = send_cwd(dotslash); + FREE(dotslash); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } else { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else if ((cp1 = StrChr(cp, ':')) != NULL && + StrChr(cp, '[') == NULL && + StrChr(cp, ']') == NULL) { + cp1++; + if (*cp1 != '\0') { + int cplen = (int) (cp1 - cp); + + strcpy(filename, cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s[000000]", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else { + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + } else if (0 == strcmp(cp, (filename + 1))) { + status = send_cwd(cp); + if (status != 2) { + HTSprintf0(&vmsname, "%s:", cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + /* Trim trailing slash if filename is not the top directory */ + if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/') + filename[strlen(filename) - 1] = '\0'; + +#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */ + if (!included_device) { + /* Get the current default VMS device:[directory] */ + status = send_cmd_1("PWD"); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Go to the VMS account's top directory */ + if ((cp = StrChr(response_text, '[')) != NULL && + (cp1 = strrchr(response_text, ']')) != NULL) { + char *tmp = 0; + unsigned len = 4; + + StrAllocCopy(tmp, cp); + if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) { + len += (cp2 - cp); + } else { + len += (cp1 - cp); + } + tmp[len] = 0; + StrAllocCat(tmp, "]"); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } +#endif /* MAINTAIN_CONNECTION */ + + /* If we want the VMS account's top directory, list it now */ + if (!(strcmp(filename, "/~")) || + (included_device && 0 == strcmp(filename, "000000")) || + (strlen(filename) == 1 && *filename == '/')) { + isDirectory = YES; + status = send_cmd_1("LIST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + /* Otherwise, go to appropriate directory and doctor filename */ + if (!StrNCmp(filename, "/~", 2)) { + filename += 2; + found_tilde = TRUE; + } + CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename)); + if (!included_device && + (cp = StrChr(filename, '/')) != NULL && + (cp1 = strrchr(cp, '/')) != NULL && + (cp1 - cp) > 1) { + char *tmp = 0; + + HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1); + + CTRACE((tfp, "change path '%s'\n", tmp)); + while ((cp2 = strrchr(tmp, '/')) != NULL) + *cp2 = '.'; + CTRACE((tfp, "...to path '%s'\n", tmp)); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + filename = cp1 + 1; + } else { + if (!included_device && !found_tilde) { + filename += 1; + } + } + break; + } + case CMS_SERVER: + { + /* + * If we want the CMS account's top directory, or a base SFS or + * anonymous directory path (i.e., without a slash), list it + * now. FM + */ + if ((strlen(filename) == 1 && *filename == '/') || + ((0 == strncasecomp((filename + 1), "vmsysu:", 7)) && + (cp = StrChr((filename + 1), '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(filename + 1, "anonymou.", 9) && + StrChr(filename + 1, '/') == NULL)) { + if (filename[1] != '\0') { + status = send_cwd(filename + 1); + if (status != 2) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + filename++; + + /* Otherwise, go to appropriate directory and adjust filename */ + while ((cp = StrChr(filename, '/')) != NULL) { + *cp++ = '\0'; + status = send_cwd(filename); + if (status == 2) { + if (*cp == '\0') { + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Clear any messages from the login directory */ + init_help_message_cache(); + /* Big goto! */ + goto listen; + } + filename = cp; + } + } + break; + } + default: + /* Shift for any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) + filename++; + break; + } + /* + * Act on a file or listing request, or try to figure out which we're + * dealing with if we don't know yet. - FM + */ + if (!(type) || (type && *type != 'D')) { + /* + * If we are retrieving a file we will (except for CMS) use + * binary mode, which lets us use the size command supported by + * ftp servers which implement RFC 3659. Knowing the size lets + * us in turn display ETA in the progress message -TD + */ + if (control->is_binary) { + int code; + + status = send_cmd_2("SIZE", filename); + if (status == 2) { +#if !defined(HAVE_LONG_LONG) && defined(GUESS_PRI_off_t) + long size; + + if (sscanf(response_text, "%d %ld", &code, &size) == 2) { + anchor->content_length = (off_t) size; + } +#else + off_t size; + if (sscanf(response_text, "%d %" SCN_off_t, &code, &size) + == 2) { + anchor->content_length = size; + } +#endif + } + } + status = send_cmd_2("RETR", filename); + if (status >= 5) { + int check; + + if (Broken_RETR) { + CTRACE((tfp, "{{reconnecting...\n")); + close_connection(control); + check = setup_connection(name, anchor); + CTRACE((tfp, "...done }}reconnecting\n")); + if (check < 0) + return check; + } + } + } else { + status = 5; /* Failed status set as flag. - FM */ + } + if (status != 1) { /* Failed : try to CWD to it */ + /* Clear any login messages if this isn't the login directory */ + if (strcmp(filename, "/")) + init_help_message_cache(); + + status = send_cwd(filename); + if (status == 2) { /* Succeeded : let's NAME LIST it */ + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + } + } + FREE(fname); + FREE(vmsname); + if (status != 1) { + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (status < 0) + return status; + else + return -status; + } + } + + listen: + if (!ftp_local_passive) { + /* Wait for the connection */ + LY_SOCKADDR soc_A; + LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Raccept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + else +#endif /* SOCKS */ + status = accept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + if (status < 0) { + init_help_message_cache(); /* to free memory */ + return HTInetStatus("accept"); + } + CTRACE((tfp, "TCP: Accepted new socket %d\n", status)); + data_soc = status; + } + + if (isDirectory) { + if (server_type == UNIX_SERVER && !unsure_type && + !strcmp(response_text, + "150 Opening ASCII mode data connection for /bin/dl.\n")) { + CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n")); + server_type = DLS_SERVER; + } + final_status = read_directory(anchor, name, format_out, sink); + if (final_status > 0) { + if (server_type != CMS_SERVER) + if (outstanding-- > 0) { + status = response(NULL); + if (status < 0 || + (status == 2 && !StrNCmp(response_text, "221", 3))) + outstanding = 0; + } + } else { /* HT_INTERRUPTED */ + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + any longer - kw */ + outstanding = 0; + } + + if (data_soc != -1) { /* normally done in read_directory */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + } + status = final_status; + } else { + int rv; + char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + + /* Clear any login messages */ + init_help_message_cache(); + + /* Fake a Content-Encoding for compressed files. - FM */ + HTUnEscape(FileName); + if (!IsUnityEnc(encoding)) { + /* + * We already know from the call to HTFileFormat above that this is + * a compressed file, no need to look at the filename again. - kw + */ + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding)); + format = HTAtom_for("www/compressed"); + + } else { + int rootlen; + CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen); + + if (cft != cftNone) { + FileName[rootlen] = '\0'; + format = HTFileFormat(FileName, &encoding, NULL); + format = HTCharsetFormat(format, anchor, -1); + StrAllocCopy(anchor->content_type, format->name); + format = HTAtom_for("www/compressed"); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); + break; + case cftNone: + break; + } + } + FREE(FileName); + + _HTProgress(gettext("Receiving FTP file.")); + rv = HTParseSocket(format, format_out, anchor, data_soc, sink); + + HTInitInput(control->socket); + /* Reset buffering to control connection DD 921208 */ + + if (rv < 0) { + if (rv == -2) /* weird error, don't expect much response */ + outstanding--; + else if (rv == HT_INTERRUPTED || rv == -1) + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + longer - kw */ + outstanding = 0; + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + } else { + status = 2; /* data_soc already closed in HTCopy - kw */ + } + + if (status < 0 && rv != HT_INTERRUPTED && rv != -1) { + (void) HTInetStatus("close"); /* Comment only */ + } else { + if (rv != HT_LOADED && outstanding--) { + status = response(NULL); /* Pick up final reply */ + if (status != 2 && rv != HT_INTERRUPTED && rv != -1) { + data_soc = -1; /* invalidate it */ + init_help_message_cache(); /* to free memory */ + return HTLoadError(sink, 500, response_text); + } else if (status == 2 && !StrNCmp(response_text, "221", 3)) { + outstanding = 0; + } + } + } + final_status = HT_LOADED; + } + while (outstanding-- > 0 && + (status > 0)) { + status = response(NULL); + if (status == 2 && !StrNCmp(response_text, "221", 3)) + break; + } + data_soc = -1; /* invalidate it */ + CTRACE((tfp, "HTFTPLoad: normal end; ")); + if (control->socket < 0) { + CTRACE((tfp, "control socket is %d\n", control->socket)); + } else { + CTRACE((tfp, "closing control socket %d\n", control->socket)); + status = NETCLOSE(control->socket); + if (status == -1) + HTInetStatus("control connection close"); /* Comment only */ + } + control->socket = -1; + init_help_message_cache(); /* to free memory */ + /* returns HT_LOADED (always for file if we get here) or error */ + return final_status; +} /* open_file_read */ + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ +void HTClearFTPPassword(void) +{ + /* + * Need code to check cached documents from non-anonymous ftp accounts and + * do something to ensure that they no longer can be accessed without a new + * retrieval. - FM + */ + + /* + * Now free the current user entered password, if any. - FM + */ + FREE(user_entered_password); +} + +#endif /* ifndef DISABLE_FTP */ diff --git a/WWW/Library/Implementation/HTFTP.h b/WWW/Library/Implementation/HTFTP.h new file mode 100644 index 0000000..a903bbb --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.h @@ -0,0 +1,70 @@ +/* FTP access module for libwww + FTP ACCESS FUNCTIONS + + This isn't really a valid protocol module -- it is lumped together with HTFile . That + could be changed easily. + + Author: Tim Berners-Lee. Public Domain. Please mail changes to timbl@info.cern.ch + + */ +#ifndef HTFTP_H +#define HTFTP_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#define FILE_BY_NAME 0 +#define FILE_BY_TYPE 1 +#define FILE_BY_SIZE 2 +#define FILE_BY_DATE 3 + extern int HTfileSortMethod; /* specifies the method of sorting */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ + extern char *HTVMS_name(const char *nn, + const char *fn); + +/* + +Retrieve File from Server + + ON EXIT, + + returns Socket number for file if good.<0 if bad. + + */ + extern int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ + extern void HTClearFTPPassword(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/WWW/Library/Implementation/HTFWriter.h b/WWW/Library/Implementation/HTFWriter.h new file mode 100644 index 0000000..015ea15 --- /dev/null +++ b/WWW/Library/Implementation/HTFWriter.h @@ -0,0 +1,30 @@ +/* File Writer for libwww + C FILE WRITER + + It is useful to have both FWriter and Writer for environments in which fdopen() doesn't + exist for example. + + */ +#ifndef HTFWRITE_H +#define HTFWRITE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern HTStream *HTFWriter_new(FILE *fp); + + extern HTStream *HTSaveAndExecute(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + + extern HTStream *HTSaveLocally(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + +#ifdef __cplusplus +} +#endif +#endif /* HTFWRITE_H */ diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c new file mode 100644 index 0000000..8fdaa2c --- /dev/null +++ b/WWW/Library/Implementation/HTFile.c @@ -0,0 +1,3395 @@ +/* + * $LynxId: HTFile.c,v 1.158 2022/07/25 23:52:05 tom Exp $ + * + * File Access HTFile.c + * =========== + * + * This is unix-specific code in general, with some VMS bits. + * These are routines for file access used by browsers. + * Development of this module for Unix DIRED_SUPPORT in Lynx + * regrettably has has been conducted in a manner with now + * creates a major impediment for hopes of adapting Lynx to + * a newer version of the library. + * + * History: + * Feb 91 Written Tim Berners-Lee CERN/CN + * Apr 91 vms-vms access included using DECnet syntax + * 26 Jun 92 (JFG) When running over DECnet, suppressed FTP. + * Fixed access bug for relative names on VMS. + * Sep 93 (MD) Access to VMS files allows sharing. + * 15 Nov 93 (MD) Moved HTVMSname to HTVMSUTILS.C + * 27 Dec 93 (FM) FTP now works with VMS hosts. + * FTP path must be Unix-style and cannot include + * the device or top directory. + */ + +#include + +#ifndef VMS +#if defined(DOSPATH) +#undef LONG_LIST +#define LONG_LIST /* Define this for long style unix listings (ls -l), + the actual style is configurable from lynx.cfg */ +#endif +/* #define NO_PARENT_DIR_REFERENCE */ +/* Define this for no parent links */ +#endif /* !VMS */ + +#if defined(DOSPATH) +#define HAVE_READDIR 1 +#define USE_DIRENT +#endif + +#if defined(USE_DOS_DRIVES) +#include +#endif + +#include /* Implemented here */ + +#ifdef VMS +#include +#endif /* VMS */ + +#if defined (USE_ZLIB) || defined (USE_BZLIB) +#include +#endif + +#define MULTI_SUFFIX ".multi" /* Extension for scanning formats */ + +#include +#include +#ifndef DECNET +#include +#endif /* !DECNET */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef USE_PRETTYSRC +# include +#endif + +#include + +typedef struct _HTSuffix { + char *suffix; + HTAtom *rep; + HTAtom *encoding; + char *desc; + float quality; +} HTSuffix; + +typedef struct { + struct stat file_info; + char sort_tags; + char file_name[1]; /* on the end of the struct, since its length varies */ +} DIRED; + +#ifndef NGROUPS +#ifdef NGROUPS_MAX +#define NGROUPS NGROUPS_MAX +#else +#define NGROUPS 32 +#endif /* NGROUPS_MAX */ +#endif /* NGROUPS */ + +#ifndef GETGROUPS_T +#define GETGROUPS_T int +#endif + +#include /* For directory object building */ + +#define PUTC(c) (*target->isa->put_character)(target, c) +#define PUTS(s) (*target->isa->put_string)(target, s) +#define START(e) (*target->isa->start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element)(target, e, 0) +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \ + (*target->isa->end_element)(target, e, 0) +#define FREE_TARGET (*target->isa->_free)(target) +#define ABORT_TARGET (*targetClass._abort)(target, NULL); + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* + * Controlling globals. + */ +int HTDirAccess = HT_DIR_OK; + +#ifdef DIRED_SUPPORT +int HTDirReadme = HT_DIR_README_NONE; + +#else +int HTDirReadme = HT_DIR_README_TOP; +#endif /* DIRED_SUPPORT */ + +static const char *HTMountRoot = "/Net/"; /* Where to find mounts */ + +#ifdef VMS +static const char *HTCacheRoot = "/WWW$SCRATCH"; /* Where to cache things */ + +#else +static const char *HTCacheRoot = "/tmp/W3_Cache_"; /* Where to cache things */ +#endif /* VMS */ + +static char s_no_suffix[] = "*"; +static char s_unknown_suffix[] = "*.*"; + +/* + * Suffix registration. + */ +static HTList *HTSuffixes = 0; + +static HTSuffix no_suffix = +{ + s_no_suffix, NULL, NULL, NULL, 1.0 +}; + +static HTSuffix unknown_suffix = +{ + s_unknown_suffix, NULL, NULL, NULL, 1.0 +}; + +/* To free up the suffixes at program exit. + * ---------------------------------------- + */ +#ifdef LY_FIND_LEAKS +static void free_suffixes(void); +#endif + +#define FindSearch(filename) strchr(filename, '?') + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else if (*bufp && !(entry && *entry)) { + **bufp = '\0'; + } else if (entry) { + StrAllocCopy(*bufp, entry); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (entry)); + + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + int entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sd", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%d", entry); + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static void LYListFmtParse(const char *fmtstr, + DIRED * data, + char *file, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + char tmp[LY_MAXPATH]; + char type; + +#ifndef NOUSERS + const char *name; +#endif + time_t now; + char *datestr; + +#ifdef S_IFLNK + int len; +#endif +#define SEC_PER_YEAR (60 * 60 * 24 * 365) + +#ifdef _WINDOWS /* 1998/01/06 (Tue) 21:20:53 */ + static const char *pbits[] = + { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", + 0}; + +#define PBIT(a, n, s) pbits[((a) >> (n)) & 0x7] + +#else + static const char *pbits[] = + {"---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", 0}; + static const char *psbits[] = + {"--S", "--s", "-wS", "-ws", + "r-S", "r-s", "rwS", "rws", 0}; + +#define PBIT(a, n, s) (s) ? psbits[((a) >> (n)) & 0x7] : \ + pbits[((a) >> (n)) & 0x7] +#endif +#if defined(S_ISVTX) && !defined(_WINDOWS) + static const char *ptbits[] = + {"--T", "--t", "-wT", "-wt", + "r-T", "r-t", "rwT", "rwt", 0}; + +#define PTBIT(a, s) (s) ? ptbits[(a) & 0x7] : pbits[(a) & 0x7] +#else +#define PTBIT(a, s) PBIT(a, 0, 0) +#endif + + if (data->file_info.st_mode == 0) + fmtstr = " %a"; /* can't stat so just do anchor */ + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + PUTS(start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + PUTS(start); + continue; + + case 'A': + case 'a': /* anchor */ + HTDirEntry(target, tail, data->file_name); + FormatStr(&buf, start, data->file_name); + PUTS(buf); + END(HTML_A); + *buf = '\0'; +#ifdef S_IFLNK + if (c != 'A' && S_ISLNK(data->file_info.st_mode) && + (len = (int) readlink(file, tmp, sizeof(tmp) - 1)) >= 0) { + PUTS(" -> "); + tmp[len] = '\0'; + PUTS(tmp); + } +#endif + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (S_ISDIR(data->file_info.st_mode)) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(file, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + now = time(0); + datestr = ctime(&data->file_info.st_mtime); + if ((now - data->file_info.st_mtime) < SEC_PER_YEAR / 2) + /* + * MMM DD HH:MM + */ + sprintf(tmp, "%.12s", datestr + 4); + else + /* + * MMM DD YYYY + */ + sprintf(tmp, "%.7s %.4s ", datestr + 4, + datestr + 20); + FormatStr(&buf, start, tmp); + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->file_info.st_size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (S_ISDIR(data->file_info.st_mode)) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + FormatSize(&buf, start, ((data->file_info.st_size + 1023) / 1024)); + StrAllocCat(buf, "K"); + break; + + case 'p': /* unix-style permission bits */ + switch (data->file_info.st_mode & S_IFMT) { +#if defined(_MSC_VER) && defined(_S_IFIFO) + case _S_IFIFO: + type = 'p'; + break; +#else + case S_IFIFO: + type = 'p'; + break; +#endif + case S_IFCHR: + type = 'c'; + break; + case S_IFDIR: + type = 'd'; + break; + case S_IFREG: + type = '-'; + break; +#ifdef S_IFBLK + case S_IFBLK: + type = 'b'; + break; +#endif +#ifdef S_IFLNK + case S_IFLNK: + type = 'l'; + break; +#endif +#ifdef S_IFSOCK +# ifdef S_IFIFO /* some older machines (e.g., apollo) have a conflict */ +# if S_IFIFO != S_IFSOCK + case S_IFSOCK: + type = 's'; + break; +# endif +# else + case S_IFSOCK: + type = 's'; + break; +# endif +#endif /* S_IFSOCK */ + default: + type = '?'; + break; + } +#ifdef _WINDOWS + sprintf(tmp, "%c%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_IRWXU)); +#else + sprintf(tmp, "%c%s%s%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_ISUID), + PBIT(data->file_info.st_mode, 3, data->file_info.st_mode & S_ISGID), + PTBIT(data->file_info.st_mode, data->file_info.st_mode & S_ISVTX)); +#endif + FormatStr(&buf, start, tmp); + break; + + case 'o': /* owner */ +#ifndef NOUSERS + name = HTAA_UidToName((int) data->file_info.st_uid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_uid); + } +#endif + break; + + case 'g': /* group */ +#ifndef NOUSERS + name = HTAA_GidToName((int) data->file_info.st_gid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_gid); + } +#endif + break; + + case 'l': /* link count */ + FormatNum(&buf, start, (int) data->file_info.st_nlink); + break; + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + if (buf) + PUTS(buf); + + s++; + } + FREE(buf); + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Define the representation associated with a file suffix. + * -------------------------------------------------------- + * + * Calling this with suffix set to "*" will set the default + * representation. + * Calling this with suffix set to "*.*" will set the default + * representation for unknown suffix files which contain a ".". + * + * The encoding parameter can give a trivial (8bit, 7bit, binary) + * or real (gzip, compress) encoding. + * + * If filename suffix is already defined with the same encoding + * its previous definition is overridden. + */ +void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double value) +{ + HTSuffix *suff; + BOOL trivial_enc = (BOOL) IsUnityEncStr(encoding); + + if (strcmp(suffix, s_no_suffix) == 0) + suff = &no_suffix; + else if (strcmp(suffix, s_unknown_suffix) == 0) + suff = &unknown_suffix; + else { + HTList *cur = HTSuffixes; + + while (NULL != (suff = (HTSuffix *) HTList_nextObject(cur))) { + if (suff->suffix && 0 == strcmp(suff->suffix, suffix) && + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(encoding, HTAtom_name(suff->encoding)) == 0))) + break; + } + if (!suff) { /* Not found -- create a new node */ + suff = typecalloc(HTSuffix); + if (suff == NULL) + outofmem(__FILE__, "HTSetSuffix"); + + if (!HTSuffixes) { + HTSuffixes = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_suffixes); +#endif + } + + HTList_addObject(HTSuffixes, suff); + + StrAllocCopy(suff->suffix, suffix); + } + } + + if (representation) + suff->rep = HTAtom_for(representation); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + * Invariant code removed. + */ + suff->encoding = HTAtom_for(encoding); + + StrAllocCopy(suff->desc, desc); + + suff->quality = (float) value; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free all added suffixes. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_suffixes(void) +{ + HTSuffix *suff = NULL; + + /* + * Loop through all suffixes. + */ + while (!HTList_isEmpty(HTSuffixes)) { + /* + * Free off each item and its members if need be. + */ + suff = (HTSuffix *) HTList_removeLastObject(HTSuffixes); + FREE(suff->suffix); + FREE(suff->desc); + FREE(suff); + } + /* + * Free off the list itself. + */ + HTList_delete(HTSuffixes); + HTSuffixes = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* Make the cache file name for a W3 document. + * ------------------------------------------- + * Make up a suitable name for saving the node in + * + * E.g. /tmp/WWW_Cache_news/1234@cernvax.cern.ch + * /tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTCacheFileName(const char *name) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *result = NULL; + + HTSprintf0(&result, "%s/WWW/%s/%s%s", HTCacheRoot, acc_method, host, path); + + FREE(path); + FREE(acc_method); + FREE(host); + return result; +} + +/* Open a file for write, creating the path. + * ----------------------------------------- + */ +#ifdef NOT_IMPLEMENTED +static int HTCreatePath(const char *path) +{ + return -1; +} +#endif /* NOT_IMPLEMENTED */ + +/* Convert filename from URL-path syntax to local path format + * ---------------------------------------------------------- + * Input name is assumed to be the URL-path of a local file + * URL, i.e. what comes after the "file://localhost". + * '#'-fragments to be treated as such must already be stripped. + * If expand_all is FALSE, unescape only escaped '/'. - kw + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTURLPath_toFile(const char *name, + int expand_all, + int is_remote GCC_UNUSED) +{ + char *path = NULL; + char *result = NULL; + + StrAllocCopy(path, name); + if (expand_all) + HTUnEscape(path); /* Interpret all % signs */ + else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + CTRACE((tfp, "URLPath `%s' means path `%s'\n", name, path)); +#if defined(USE_DOS_DRIVES) + StrAllocCopy(result, is_remote ? path : HTDOS_name(path)); +#else + StrAllocCopy(result, path); +#endif + + FREE(path); + + return result; +} +/* Convert filenames between local and WWW formats. + * ------------------------------------------------ + * Make up a suitable name for saving the node in + * + * E.g. $(HOME)/WWW/news/1234@cernvax.cern.ch + * $(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +/* NOTE: Don't use this function if you know that the input is a URL path + rather than a full URL, use HTURLPath_toFile instead. Otherwise + this function will return the wrong thing for some unusual + paths (like ones containing "//", possibly escaped). - kw +*/ +char *HTnameOfFile_WWW(const char *name, + int WWW_prefix, + int expand_all) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + const char *home; + char *result = NULL; + + if (expand_all) { + HTUnEscape(path); /* Interpret all % signs */ + } else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + if (0 == strcmp(acc_method, "file") /* local file */ + ||!*acc_method) { /* implicitly local? */ + if ((0 == strcasecomp(host, HTHostName())) || + (0 == strcasecomp(host, "localhost")) || !*host) { + CTRACE((tfp, "Node `%s' means path `%s'\n", name, path)); + StrAllocCopy(result, HTSYS_name(path)); + } else if (WWW_prefix) { + HTSprintf0(&result, "%s%s%s", "/Net/", host, path); + CTRACE((tfp, "Node `%s' means file `%s'\n", name, result)); + } else { + StrAllocCopy(result, path); + } + } else if (WWW_prefix) { /* other access */ +#ifdef VMS + if ((home = LYGetEnv("HOME")) == NULL) + home = HTCacheRoot; + else + home = HTVMS_wwwName(home); +#else +#if defined(_WINDOWS) /* 1997/10/16 (Thu) 20:42:51 */ + home = Home_Dir(); +#else + home = LYGetEnv("HOME"); +#endif + if (home == NULL) + home = "/tmp"; +#endif /* VMS */ + HTSprintf0(&result, "%s/WWW/%s/%s%s", home, acc_method, host, path); + } else { + StrAllocCopy(result, path); + } + + FREE(host); + FREE(path); + FREE(acc_method); + + CTRACE((tfp, "HTnameOfFile_WWW(%s,%d,%d) = %s\n", + name, WWW_prefix, expand_all, result)); + + return result; +} + +/* Make a WWW name from a full local path name. + * -------------------------------------------- + * + * Bugs: + * At present, only the names of two network root nodes are hand-coded + * in and valid for the NeXT only. This should be configurable in + * the general case. + */ +char *WWW_nameOfFile(const char *name) +{ + char *result = NULL; + +#ifdef NeXT + if (0 == StrNCmp("/private/Net/", name, 13)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 13); + } else +#endif /* NeXT */ + if (0 == StrNCmp(HTMountRoot, name, 5)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 5); + } else { + HTSprintf0(&result, "%s//%s%s", STR_FILE_URL, HTHostName(), name); + } + CTRACE((tfp, "File `%s'\n\tmeans node `%s'\n", name, result)); + return result; +} + +/* Determine a suitable suffix, given the representation. + * ------------------------------------------------------ + * + * On entry, + * rep is the atomized MIME style representation + * enc is an encoding, trivial (8bit, binary, etc.) or gzip etc. + * + * On exit: + * Returns a pointer to a suitable suffix string if one has been + * found, else "". + */ +const char *HTFileSuffix(HTAtom *rep, + const char *enc) +{ + HTSuffix *suff; + +#ifdef FNAMES_8_3 + HTSuffix *first_found = NULL; +#endif + BOOL trivial_enc; + int n; + int i; + +#define NO_INIT /* don't init anymore since I do it in Lynx at startup */ +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + + trivial_enc = (BOOL) IsUnityEncStr(enc); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + if (suff->rep == rep && +#if defined(VMS) || defined(FNAMES_8_3) + /* Don't return a suffix whose first char is a dot, and which + has more dots or asterisks after that, for + these systems - kw */ + (!suff->suffix || !suff->suffix[0] || suff->suffix[0] != '.' || + (StrChr(suff->suffix + 1, '.') == NULL && + StrChr(suff->suffix + 1, '*') == NULL)) && +#endif + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(enc, HTAtom_name(suff->encoding)) == 0))) { +#ifdef FNAMES_8_3 + if (suff->suffix && (strlen(suff->suffix) <= 4)) { + /* + * If length of suffix (including dot) is 4 or smaller, return + * this one even if we found a longer one earlier - kw + */ + return suff->suffix; + } else if (!first_found) { + first_found = suff; /* remember this one */ + } +#else + return suff->suffix; /* OK -- found */ +#endif + } + } +#ifdef FNAMES_8_3 + if (first_found) + return first_found->suffix; +#endif + return ""; /* Dunno */ +} + +/* + * Trim version from VMS filenames to avoid confusing comparisons. + */ +#ifdef VMS +static const char *VMS_trim_version(const char *filename) +{ + const char *result = filename; + const char *version = StrChr(filename, ';'); + + if (version != 0) { + static char *stripped; + + StrAllocCopy(stripped, filename); + stripped[version - filename] = '\0'; + result = (const char *) stripped; + } + return result; +} +#define VMS_DEL_VERSION(name) name = VMS_trim_version(name) +#else +#define VMS_DEL_VERSION(name) /* nothing */ +#endif + +/* Determine file format from file name. + * ------------------------------------- + * + * This version will return the representation and also set + * a variable for the encoding. + * + * Encoding may be a unity encoding (binary, 8bit, etc.) or + * a content-coding like gzip, compress. + * + * It will handle for example x.txt, x.txt,Z, x.Z + */ +HTFormat HTFileFormat(const char *filename, + HTAtom **pencoding, + const char **pdesc) +{ + HTSuffix *suff; + int n; + int i; + int lf; + char *search; + + VMS_DEL_VERSION(filename); + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + HTFormat result; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTFileFormat(newname, pencoding, pdesc); + free(newname); + return result; + } + + if (pencoding) + *pencoding = NULL; + if (pdesc) + *pdesc = NULL; + if (LYforce_HTML_mode) { + if (pencoding) + *pencoding = WWW_ENC_8BIT; + return WWW_HTML; + } +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + lf = (int) strlen(filename); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcasecomp(suff->suffix, filename + lf - ls)) { + int j; + + if (pencoding) + *pencoding = suff->encoding; + if (pdesc) + *pdesc = suff->desc; + if (suff->rep) { + return suff->rep; /* OK -- found */ + } + for (j = 0; j < n; j++) { /* Got encoding, need representation */ + int ls2; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, j); + ls2 = (int) strlen(suff->suffix); + if ((ls + ls2 <= lf) && + !strncasecomp(suff->suffix, + filename + lf - ls - ls2, ls2)) { + if (suff->rep) { + if (pdesc && !(*pdesc)) + *pdesc = suff->desc; + if (pencoding && IsUnityEnc(*pencoding) && + *pencoding != WWW_ENC_7BIT && + !IsUnityEnc(suff->encoding)) + *pencoding = suff->encoding; + return suff->rep; + } + } + } + + } + } + + /* defaults tree */ + + suff = (StrChr(filename, '.') + ? (unknown_suffix.rep + ? &unknown_suffix + : &no_suffix) + : &no_suffix); + + /* + * Set default encoding unless found with suffix already. + */ + if (pencoding && !*pencoding) { + *pencoding = (suff->encoding + ? suff->encoding + : HTAtom_for("binary")); + } + return suff->rep ? suff->rep : WWW_BINARY; +} + +/* Revise the file format in relation to the Lynx charset. - FM + * ------------------------------------------------------- + * + * This checks the format associated with an anchor for + * an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ +HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl) +{ + char *cp = NULL, *cp1, *cp2, *cp3 = NULL, *cp4; + BOOL chartrans_ok = FALSE; + int chndl = -1; + const char *format_name = format->name; + + FREE(anchor->charset); + if (format_name == 0) + format_name = ""; + StrAllocCopy(cp, format_name); + LYLowerCase(cp); + if (((cp1 = StrChr(cp, ';')) != NULL) && + (cp2 = strstr(cp1, "charset")) != NULL) { + CTRACE((tfp, "HTCharsetFormat: Extended MIME Content-Type is %s\n", + format_name)); + cp2 += 7; + while (*cp2 == ' ' || *cp2 == '=') + cp2++; + StrAllocCopy(cp3, cp2); /* copy to mutilate more */ + for (cp4 = cp3; (*cp4 != '\0' && *cp4 != '"' && + *cp4 != ';' && *cp4 != ':' && + !WHITE(*cp4)); cp4++) { + ; /* do nothing */ + } + *cp4 = '\0'; + cp4 = cp3; + chndl = UCGetLYhndl_byMIME(cp3); + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + *cp1 = '\0'; + format = HTAtom_for(cp); + StrAllocCopy(anchor->charset, cp4); + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_MIME); + } else if (chndl < 0) { + /* + * Got something but we don't recognize it. + */ + 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 */ + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + } + if (chartrans_ok) { + LYUCcharset *p_in = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_MIME); + LYUCcharset *p_out = HTAnchor_setUCInfoStage(anchor, + current_char_set, + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + + if (!p_out) { + /* + * Try again. + */ + p_out = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT); + } + if (!strcmp(p_in->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_HTEXT), + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + if (!strcmp(p_out->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_MIME), + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + } + if (p_in->enc != UCT_ENC_CJK) { + HTCJK = NOCJK; + if (!(p_in->codepoints & + UCT_CP_SUBSETOF_LAT1) && + chndl == current_char_set) { + HTPassEightBitRaw = TRUE; + } + } else if (p_out->enc == UCT_ENC_CJK) { + Set_HTCJK(p_in->MIMEname, p_out->MIMEname); + } + } else { + /* + * Cannot translate. If according to some heuristic the given + * charset and the current display character both are likely to be + * like ISO-8859 in structure, pretend we have some kind of match. + */ + BOOL given_is_8859 = (BOOL) (!StrNCmp(cp4, "iso-8859-", 9) && + isdigit(UCH(cp4[9]))); + BOOL given_is_8859like = (BOOL) (given_is_8859 || + !StrNCmp(cp4, "windows-", 8) || + !StrNCmp(cp4, "cp12", 4) || + !StrNCmp(cp4, "cp-12", 5)); + BOOL given_and_display_8859like = (BOOL) (given_is_8859like && + (strstr(LYchar_set_names[current_char_set], + "ISO-8859") || + strstr(LYchar_set_names[current_char_set], + "windows-"))); + + if (given_and_display_8859like) { + *cp1 = '\0'; + format = HTAtom_for(cp); + } + if (given_is_8859) { + cp1 = &cp4[10]; + while (*cp1 && + isdigit(UCH(*cp1))) + cp1++; + *cp1 = '\0'; + } + if (given_and_display_8859like) { + StrAllocCopy(anchor->charset, cp4); + HTPassEightBitRaw = TRUE; + } + HTAlert(*cp4 ? cp4 : anchor->charset); + } + FREE(cp3); + } else if (cp1 != NULL) { + /* + * No charset parameter is present. Ignore all other parameters, as we + * do when charset is present. - FM + */ + *cp1 = '\0'; + format = HTAtom_for(cp); + } + FREE(cp); + + /* + * Set up defaults, if needed. - FM + */ + if (!chartrans_ok && !anchor->charset && default_LYhndl >= 0) { + HTAnchor_setUCInfoStage(anchor, default_LYhndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + HTAnchor_copyUCInfoStage(anchor, + UCT_STAGE_PARSER, + UCT_STAGE_MIME, + -1); + + return format; +} + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ +void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs) +{ + char *Afn; + char *Aname = NULL; + HTFormat format; + HTAtom *myEnc = NULL; + HTParentAnchor *file_anchor; + const char *file_csname; + int file_cs; + + /* + * Convert filename to URL. Note that it is always supposed to be a + * filename, not maybe-filename-maybe-URL, so we don't use + * LYFillLocalFileURL and LYEnsureAbsoluteURL. - kw + */ + Afn = HTEscape(filename, URL_PATH); + LYLocalFileToURL(&Aname, Afn); + file_anchor = HTAnchor_findSimpleAddress(Aname); + + format = HTFileFormat(filename, &myEnc, pdesc); + format = HTCharsetFormat(format, file_anchor, UCLYhndl_HTFile_for_unspec); + file_cs = HTAnchor_getUCLYhndl(file_anchor, UCT_STAGE_MIME); + file_csname = file_anchor->charset; + if (!file_csname) { + if (file_cs >= 0) + file_csname = LYCharSet_UC[file_cs].MIMEname; + else + file_csname = "display character set"; + } + CTRACE((tfp, "GetFileInfo: '%s' is a%s %s %s file, charset=%s (%d).\n", + filename, + ((myEnc && *HTAtom_name(myEnc) == '8') ? "n" : myEnc ? "" : + *HTAtom_name(format) == 'a' ? "n" : ""), + myEnc ? HTAtom_name(myEnc) : "", + HTAtom_name(format), + file_csname, + file_cs)); + FREE(Afn); + FREE(Aname); + if (pfile_anchor) + *pfile_anchor = file_anchor; + if (pformat) + *pformat = format; + if (pencoding) + *pencoding = myEnc; + if (pcharset) + *pcharset = file_csname; + if (pfile_cs) + *pfile_cs = file_cs; +} + +/* Determine value from file name. + * ------------------------------- + * + */ +float HTFileValue(const char *filename) +{ + HTSuffix *suff; + int n; + int i; + int lf = (int) strlen(filename); + +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcmp(suff->suffix, filename + lf - ls)) { + CTRACE((tfp, "File: Value of %s is %.3f\n", + filename, suff->quality)); + return suff->quality; /* OK -- found */ + } + } + return (float) 0.3; /* Dunno! */ +} + +/* + * Determine compression type from file name, by looking at its suffix. + * Sets as side-effect a pointer to the "dot" that begins the suffix. + */ +CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen) +{ + CompressFileType result = cftNone; + char *search; + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTCompressFileType(newname, dots, rootlen); + free(newname); + } else { + size_t len; + const char *ftype; + + VMS_DEL_VERSION(filename); + len = strlen(filename); + ftype = filename + len; + + if ((len > 3) + && !strcasecomp((ftype - 2), "br") + && StrChr(dots, ftype[-3]) != 0) { + result = cftBrotli; + ftype -= 3; + } else if ((len > 4) + && !strcasecomp((ftype - 3), "bz2") + && StrChr(dots, ftype[-4]) != 0) { + result = cftBzip2; + ftype -= 4; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "gz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftGzip; + ftype -= 3; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "zz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftDeflate; + ftype -= 3; + } else if ((len > 2) + && !strcmp((ftype - 1), "Z") + && StrChr(dots, ftype[-2]) != 0) { + result = cftCompress; + ftype -= 2; + } + + *rootlen = (int) (ftype - filename); + + CTRACE((tfp, "HTCompressFileType(%s) returns %d:%s\n", + filename, (int) result, filename + *rootlen)); + } + return result; +} + +/* + * Determine expected file-suffix from the compression method. + */ +const char *HTCompressTypeToSuffix(CompressFileType method) +{ + const char *result = ""; + + switch (method) { + default: + case cftNone: + result = ""; + break; + case cftGzip: + result = ".gz"; + break; + case cftCompress: + result = ".Z"; + break; + case cftBzip2: + result = ".bz2"; + break; + case cftDeflate: + result = ".zz"; + break; + case cftBrotli: + result = ".br"; + break; + } + return result; +} + +/* + * Determine compression encoding from the compression method. + */ +const char *HTCompressTypeToEncoding(CompressFileType method) +{ + const char *result = NULL; + + switch (method) { + default: + case cftNone: + result = NULL; + break; + case cftGzip: + result = "gzip"; + break; + case cftCompress: + result = "compress"; + break; + case cftBzip2: + result = "bzip2"; + break; + case cftDeflate: + result = "deflate"; + break; + case cftBrotli: + result = "brotli"; + break; + } + return result; +} + +/* + * Check if the token from "Content-Encoding" corresponds to a compression + * type. RFC 2068 (and cut/paste into RFC 2616) lists these: + * gzip + * compress + * deflate + * as well as "identity" (but that does nothing). + */ +CompressFileType HTEncodingToCompressType(const char *coding) +{ + CompressFileType result = cftNone; + + if (coding == NULL) { + result = cftNone; + } else if (!strcasecomp(coding, "gzip") || + !strcasecomp(coding, "x-gzip")) { + result = cftGzip; + } else if (!strcasecomp(coding, "compress") || + !strcasecomp(coding, "x-compress")) { + result = cftCompress; + } else if (!strcasecomp(coding, "bzip2") || + !strcasecomp(coding, "x-bzip2")) { + result = cftBzip2; + } else if (!strcasecomp(coding, "br") || /* actual */ + !strcasecomp(coding, "brotli") || /* expected */ + !strcasecomp(coding, "x-brotli")) { + result = cftBrotli; + } else if (!strcasecomp(coding, "deflate") || + !strcasecomp(coding, "x-deflate")) { + result = cftDeflate; + } + return result; +} + +CompressFileType HTContentTypeToCompressType(const char *ct) +{ + CompressFileType method = cftNone; + + if (ct == NULL) { + method = cftNone; + } else if (!strncasecomp(ct, "application/gzip", 16) || + !strncasecomp(ct, "application/x-gzip", 18)) { + method = cftGzip; + } else if (!strncasecomp(ct, "application/compress", 20) || + !strncasecomp(ct, "application/x-compress", 22)) { + method = cftCompress; + } else if (!strncasecomp(ct, "application/bzip2", 17) || + !strncasecomp(ct, "application/x-bzip2", 19)) { + method = cftBzip2; + } else if (!strncasecomp(ct, "application/br", 14) || + !strncasecomp(ct, "application/brotli", 18) || + !strncasecomp(ct, "application/x-brotli", 20)) { + method = cftBrotli; + } + return method; +} + +/* + * Check the anchor's content_type and content_encoding elements for a gzip or + * Unix compressed file -FM, TD + */ +CompressFileType HTContentToCompressType(HTParentAnchor *anchor) +{ + CompressFileType method = cftNone; + const char *ct = HTAnchor_content_type(anchor); + const char *ce = HTAnchor_content_encoding(anchor); + + if (ct != 0) { + method = HTContentTypeToCompressType(ct); + } else if (ce != 0) { + method = HTEncodingToCompressType(ce); + } + return method; +} + +/* Determine write access to a file. + * --------------------------------- + * + * On exit: + * Returns YES if file can be accessed and can be written to. + * + * Bugs: + * 1. No code for non-unix systems. + * 2. Isn't there a quicker way? + */ +BOOL HTEditable(const char *filename GCC_UNUSED) +{ +#ifndef NO_GROUPS + GETGROUPS_T groups[NGROUPS]; + uid_t myUid; + int ngroups; /* The number of groups */ + struct stat fileStatus; + int i; + + if (stat(filename, &fileStatus)) /* Get details of filename */ + return NO; /* Can't even access file! */ + + ngroups = getgroups(NGROUPS, groups); /* Groups to which I belong */ + myUid = geteuid(); /* Get my user identifier */ + + if (TRACE) { + int i2; + + fprintf(tfp, + "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (", + (unsigned int) fileStatus.st_mode, + (int) fileStatus.st_uid, + (int) fileStatus.st_gid, + (int) myUid, + (int) ngroups); + for (i2 = 0; i2 < ngroups; i2++) + fprintf(tfp, " %d", (int) groups[i2]); + fprintf(tfp, ")\n"); + } + + if (fileStatus.st_mode & 0002) /* I can write anyway? */ + return YES; + + if ((fileStatus.st_mode & 0200) /* I can write my own file? */ + &&(fileStatus.st_uid == myUid)) + return YES; + + if (fileStatus.st_mode & 0020) /* Group I am in can write? */ + { + for (i = 0; i < ngroups; i++) { + if (groups[i] == fileStatus.st_gid) + return YES; + } + } + CTRACE((tfp, "\tFile is not editable.\n")); +#endif /* NO_GROUPS */ + return NO; /* If no excuse, can't do */ +} + +/* Make a save stream. + * ------------------- + * + * The stream must be used for writing back the file. + * @@@ no backup done + */ +HTStream *HTFileSaveStream(HTParentAnchor *anchor) +{ + const char *addr = anchor->address; + char *localname = HTLocalName(addr); + FILE *fp = fopen(localname, BIN_W); + + FREE(localname); + if (!fp) + return NULL; + + return HTFWriter_new(fp); +} + +/* Output one directory entry. + * --------------------------- + */ +void HTDirEntry(HTStructured * target, const char *tail, const char *entry) +{ + char *relative = NULL; + char *stripped = NULL; + char *escaped = NULL; + int len; + + if (entry == NULL) + entry = ""; + StrAllocCopy(escaped, entry); + LYTrimPathSep(escaped); + if (strcmp(escaped, "..") != 0) { + stripped = escaped; + escaped = HTEscape(stripped, URL_XPALPHAS); + if (((len = (int) strlen(escaped)) > 2) && + escaped[(len - 3)] == '%' && + escaped[(len - 2)] == '2' && + TOUPPER(escaped[(len - 1)]) == 'F') { + escaped[(len - 3)] = '\0'; + } + } + + if (isEmpty(tail)) { + /* + * Handle extra slash at end of path. + */ + HTStartAnchor(target, NULL, (escaped[0] != '\0' ? escaped : "/")); + } else { + /* + * If empty tail, gives absolute ref below. + */ + relative = 0; + HTSprintf0(&relative, "%s%s%s", + tail, + (*escaped != '\0' ? "/" : ""), + escaped); + HTStartAnchor(target, NULL, relative); + FREE(relative); + } + FREE(stripped); + FREE(escaped); +} + +static BOOL view_structured(HTFormat format_out) +{ + BOOL result = FALSE; + +#ifdef USE_PRETTYSRC + if (psrc_view + || (format_out == WWW_DUMP)) + result = TRUE; +#else + if (format_out == WWW_SOURCE) + result = TRUE; +#endif + return result; +} + +/* + * Write a DOCTYPE to the given stream if we happen to want to see the + * source view, or are dumping source. This is not needed when the source + * is not visible, since the document is rendered from a HTStructured object. + */ +void HTStructured_doctype(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS(LYNX_DOCTYPE "\n"); +} + +void HTStructured_meta(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS("\n"); +} +/* Output parent directory entry. + * ------------------------------ + * + * This gives the TITLE and H1 header, and also a link + * to the parent directory if appropriate. + * + * On exit: + * Returns TRUE if an "Up to " link was not created + * for a readable local directory because LONG_LIST is defined + * and NO_PARENT_DIR_REFERENCE is not defined, so that the + * calling function should use LYListFmtParse() to create a link + * to the parent directory. Otherwise, it returns FALSE. - FM + */ +BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop) +{ + const char *logical = anchor->address; + char *path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION); + char *current; + char *cp = NULL; + BOOL need_parent_link = FALSE; + int i; + +#if defined(USE_DOS_DRIVES) + BOOL local_link = (strlen(logical) > 18 + && !strncasecomp(logical, "file://localhost/", 17) + && LYIsDosDrive(logical + 17)); + BOOL is_remote = !local_link; + +#else +#define is_remote TRUE +#endif + + /* + * Check tildeIsTop for treating home directory as Welcome (assume the + * tilde is not followed by a username). - FM + */ + if (tildeIsTop && !StrNCmp(path, "/~", 2)) { + if (path[2] == '\0') { + path[1] = '\0'; + } else { + for (i = 0; path[(i + 2)]; i++) { + path[i] = path[(i + 2)]; + } + path[i] = '\0'; + } + } + + /* + * Trim out the ;type= parameter, if present. - FM + */ + if ((cp = strrchr(path, ';')) != NULL) { + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + cp = NULL; + } + current = LYPathLeaf(path); /* last part or "" */ + + { + char *printable = NULL; + +#ifdef DIRED_SUPPORT + printable = HTURLPath_toFile(((!strncasecomp(path, "/%2F", 4)) /* "//" ? */ + ? (path + 1) + : path), + TRUE, + is_remote); + if (0 == strncasecomp(printable, "/vmsysu:", 8) || + 0 == strncasecomp(printable, "/anonymou.", 10)) { + StrAllocCopy(cp, (printable + 1)); + StrAllocCopy(printable, cp); + FREE(cp); + } +#else + StrAllocCopy(printable, current); + HTUnEscape(printable); +#endif /* DIRED_SUPPORT */ + + HTStructured_doctype(target, format_out); + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS(*printable ? printable : WELCOME_MSG); + PUTS(SEGMENT_DIRECTORY); + END(HTML_TITLE); + PUTC('\n'); + HTStructured_meta(target, format_out); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + +#ifdef DIRED_SUPPORT + START(HTML_H2); + PUTS(*printable ? SEGMENT_CURRENT_DIR : ""); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H2); + PUTC('\n'); +#else + START(HTML_H1); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H1); + PUTC('\n'); +#endif /* DIRED_SUPPORT */ + if (((0 == strncasecomp(printable, "vmsysu:", 7)) && + (cp = StrChr(printable, '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(printable, "anonymou.", 9) && + StrChr(printable, '/') == NULL)) { + FREE(printable); + FREE(path); + return (need_parent_link); + } + FREE(printable); + } + +#ifndef NO_PARENT_DIR_REFERENCE + /* + * Make link back to parent directory. + */ + if (current - path > 0 + && LYIsPathSep(current[-1]) + && current[0] != '\0') { /* was a slash AND something else too */ + char *parent = NULL; + char *relative = NULL; + + current[-1] = '\0'; + parent = strrchr(path, '/'); /* penultimate slash */ + + if ((parent && + (!strcmp(parent, "/..") || + !strncasecomp(parent, "/%2F", 4))) || + !strncasecomp(current, "%2F", 3)) { + FREE(path); + return (need_parent_link); + } + + relative = 0; + HTSprintf0(&relative, "%s/..", current); + +#if defined(DOSPATH) || defined(__EMX__) + if (local_link) { + if (parent != 0 && strlen(parent) == 3) { + StrAllocCat(relative, "/."); + } + } else +#endif + +#if !defined (VMS) + { + /* + * On Unix, if it's not ftp and the directory cannot be read, don't + * put out a link. + * + * On VMS, this problem is dealt with internally by + * HTVMSBrowseDir(). + */ + DIR *dp = NULL; + + if (LYisLocalFile(logical)) { + /* + * We need an absolute file path for the opendir. We also need + * to unescape for this test. Don't worry about %2F now, they + * presumably have been dealt with above, and shouldn't appear + * for local files anyway... Assume OS / filesystem will just + * ignore superfluous slashes. - KW + */ + char *fullparentpath = NULL; + + /* + * Path has been shortened above. + */ + StrAllocCopy(fullparentpath, *path ? path : "/"); + + /* + * Guard against weirdness. + */ + if (0 == strcmp(current, "..")) { + StrAllocCat(fullparentpath, "/../.."); + } else if (0 == strcmp(current, ".")) { + StrAllocCat(fullparentpath, "/.."); + } + + HTUnEscape(fullparentpath); + if ((dp = opendir(fullparentpath)) == NULL) { + FREE(fullparentpath); + FREE(relative); + FREE(path); + return (need_parent_link); + } + closedir(dp); + FREE(fullparentpath); +#ifdef LONG_LIST + need_parent_link = TRUE; + FREE(path); + FREE(relative); + return (need_parent_link); +#endif /* LONG_LIST */ + } + } +#endif /* !VMS */ + HTStartAnchor(target, "", relative); + FREE(relative); + + PUTS(SEGMENT_UP_TO); + if (parent) { + if ((0 == strcmp(current, ".")) || + (0 == strcmp(current, ".."))) { + /* + * Should not happen, but if it does, at least avoid giving + * misleading info. - KW + */ + PUTS(".."); + } else { + char *printable = NULL; + + StrAllocCopy(printable, parent + 1); + HTUnEscape(printable); + PUTS(printable); + FREE(printable); + } + } else { + PUTC('/'); + } + END(HTML_A); + PUTC('\n'); + } +#endif /* !NO_PARENT_DIR_REFERENCE */ + + FREE(path); + return (need_parent_link); +} + +#if defined HAVE_READDIR +/* Send README file. + * ----------------- + * + * If a README file exists, then it is inserted into the document here. + */ +static void do_readme(HTStructured * target, const char *localname) +{ + FILE *fp; + char *readme_file_name = NULL; + int ch; + + HTSprintf0(&readme_file_name, "%s/%s", localname, HT_DIR_README_FILE); + + fp = fopen(readme_file_name, "r"); + + if (fp) { + START(HTML_PRE); + while ((ch = fgetc(fp)) != EOF) { + PUTC((char) ch); + } + END(HTML_PRE); + HTDisplayPartial(); + fclose(fp); + } + FREE(readme_file_name); +} + +#define DIRED_BLOK(obj) (((DIRED *)(obj))->sort_tags) +#define DIRED_NAME(obj) (((DIRED *)(obj))->file_name) + +#define NM_cmp(a,b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0)) + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) +static const char *file_type(const char *path) +{ + const char *type; + + while (*path == '.') + ++path; + type = StrChr(path, '.'); + if (type == NULL) + type = ""; + return type; +} +#endif /* LONG_LIST && DIRED_SUPPORT */ + +static int dired_cmp(void *a, void *b) +{ + DIRED *p = (DIRED *) a; + DIRED *q = (DIRED *) b; + int code = p->sort_tags - q->sort_tags; + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) + if (code == 0) { + switch (dir_list_order) { + case ORDER_BY_SIZE: + code = -NM_cmp(p->file_info.st_size, q->file_info.st_size); + break; + case ORDER_BY_DATE: + code = -NM_cmp(p->file_info.st_mtime, q->file_info.st_mtime); + break; + case ORDER_BY_MODE: + code = NM_cmp(p->file_info.st_mode, q->file_info.st_mode); + break; + case ORDER_BY_USER: + code = NM_cmp(p->file_info.st_uid, q->file_info.st_uid); + break; + case ORDER_BY_GROUP: + code = NM_cmp(p->file_info.st_gid, q->file_info.st_gid); + break; + case ORDER_BY_TYPE: + code = AS_cmp(file_type(p->file_name), file_type(q->file_name)); + break; + default: + code = 0; + break; + } + } +#endif /* LONG_LIST && DIRED_SUPPORT */ + if (code == 0) + code = AS_cmp(p->file_name, q->file_name); +#if 0 + CTRACE((tfp, "dired_cmp(%d) ->%d\n\t%c:%s (%s)\n\t%c:%s (%s)\n", + dir_list_order, + code, + p->sort_tags, p->file_name, file_type(p->file_name), + q->sort_tags, q->file_name, file_type(q->file_name))); +#endif + return code; +} + +static int print_local_dir(DIR *dp, char *localname, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTStructured *target; /* HTML object */ + HTBTree *bt; + HTStructuredClass targetClass; + STRUCT_DIRENT *dirbuf; + char *pathname = NULL; + char *tail = NULL; + const char *p; + char *tmpfilename = NULL; + BOOL need_parent_link = FALSE; + BOOL preformatted = FALSE; + int status; + struct stat *actual_info; + +#ifdef DISP_PARTIAL + int num_of_entries = 0; /* lines counter */ +#endif + +#ifdef S_IFLNK + struct stat link_info; +#endif + + CTRACE((tfp, "print_local_dir() started\n")); + + pathname = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + + if ((p = strrchr(pathname, '/')) == NULL) + p = "/"; + StrAllocCopy(tail, (p + 1)); + FREE(pathname); + + if (UCLYhndl_HTFile_for_unspec >= 0) { + HTAnchor_setUCInfoStage(anchor, + UCLYhndl_HTFile_for_unspec, + UCT_STAGE_PARSER, + UCT_SETBY_DEFAULT); + } + + target = HTML_new(anchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* + * The need_parent_link flag will be set if an "Up to " link was + * not created for a readable parent in HTDirTitles() because LONG_LIST is + * defined and NO_PARENT_DIR_REFERENCE is not defined so that need we to + * create the link via an LYListFmtParse() call. - FM + */ + need_parent_link = HTDirTitles(target, anchor, format_out, FALSE); + +#ifdef DIRED_SUPPORT + if (!isLYNXCGI(anchor->address)) { + HTAnchor_setFormat(anchor, WWW_DIRED); + lynx_edit_mode = TRUE; + } +#endif /* DIRED_SUPPORT */ + if (HTDirReadme == HT_DIR_README_TOP) + do_readme(target, localname); + + bt = HTBTree_new(dired_cmp); + + _HTProgress(READING_DIRECTORY); + status = HT_LOADED; /* assume we don't get interrupted */ + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ + DIRED *data = NULL; + +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + /* + * If the entry is not being used, skip it. + */ + continue; +#endif + /* + * Skip self, parent if handled in HTDirTitles() or if + * NO_PARENT_DIR_REFERENCE is not defined, and any dot files if + * no_dotfiles is set or show_dotfiles is not set. - FM + */ + if (!strcmp(dirbuf->d_name, ".") /* self */ || + (!strcmp(dirbuf->d_name, "..") /* parent */ && + need_parent_link == FALSE) || + ((strcmp(dirbuf->d_name, "..")) && + (dirbuf->d_name[0] == '.' && + (no_dotfiles || !show_dotfiles)))) + continue; + + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory, add trailing separator. + */ + LYAddPathSep(&tmpfilename); + + StrAllocCat(tmpfilename, dirbuf->d_name); + data = (DIRED *) malloc(sizeof(DIRED) + strlen(dirbuf->d_name) + 4); + if (data == NULL) { + status = HT_PARTIAL_CONTENT; + break; + } + LYTrimPathSep(tmpfilename); + + actual_info = &(data->file_info); +#ifdef S_IFLNK + if (lstat(tmpfilename, actual_info) < 0) { + actual_info->st_mode = 0; + } else { + if (S_ISLNK(actual_info->st_mode)) { + actual_info = &link_info; + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; + } + } +#else + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; +#endif + + strcpy(data->file_name, dirbuf->d_name); +#ifndef DIRED_SUPPORT + if (S_ISDIR(actual_info->st_mode)) { + data->sort_tags = 'D'; + } else { + data->sort_tags = 'F'; + /* D & F to have first directories, then files */ + } +#else + if (S_ISDIR(actual_info->st_mode)) { + if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + LYAddPathSep0(data->file_name); + } else if (!strcmp(dirbuf->d_name, "..")) { + data->sort_tags = 'A'; + } else { + data->sort_tags = 'D'; + } + } else if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + } else if (dir_list_style == FILES_FIRST) { + data->sort_tags = 'C'; + /* C & D to have first files, then directories */ + } else { + data->sort_tags = 'F'; + } +#endif /* !DIRED_SUPPORT */ + /* + * Sort dirname in the tree bt. + */ + HTBTree_add(bt, data); + +#ifdef DISP_PARTIAL + /* optimize for expensive operation: */ + if (num_of_entries % (partial_threshold > 0 ? + partial_threshold : display_lines) == 0) { + if (HTCheckForInterrupt()) { + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries++; +#endif /* DISP_PARTIAL */ + + } /* end while directory entries left to read */ + + if (status != HT_PARTIAL_CONTENT) + _HTProgress(OPERATION_OK); + else + CTRACE((tfp, "Reading the directory interrupted by user\n")); + + /* + * Run through tree printing out in order. + */ + { + HTBTElement *next_element = HTBTree_next(bt, NULL); + + /* pick up the first element of the list */ + int num_of_entries_output = 0; /* lines counter */ + + char state; + + /* I for initial (.. file), + D for directory file, + F for file */ + +#ifdef DIRED_SUPPORT + char test; +#endif /* DIRED_SUPPORT */ + state = 'I'; + + while (next_element != NULL) { + DIRED *entry; + +#ifndef DISP_PARTIAL + if (num_of_entries_output % HTMAX(display_lines, 10) == 0) { + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } +#endif + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory. + */ + LYAddPathSep(&tmpfilename); + + entry = (DIRED *) (HTBTree_object(next_element)); + /* + * Append the current entry's filename to the path. + */ + StrAllocCat(tmpfilename, entry->file_name); + HTSimplify(tmpfilename, LYIsPathSep(*tmpfilename)); + /* + * Output the directory entry. + */ + if (strcmp(DIRED_NAME(HTBTree_object(next_element)), "..")) { +#ifdef DIRED_SUPPORT + test = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (state != test) { +#ifndef LONG_LIST + if (dir_list_style == FILES_FIRST) { + if (state == 'F') { + END(HTML_DIR); + PUTC('\n'); + } + } else if (dir_list_style != MIXED_STYLE) + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + if (dir_list_style != MIXED_STYLE) { + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + } + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#else + if (state != DIRED_BLOK(HTBTree_object(next_element))) { +#ifndef LONG_LIST + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#endif /* DIRED_SUPPORT */ +#ifndef LONG_LIST + START(HTML_LI); +#endif /* !LONG_LIST */ + } + if (!preformatted) { + START(HTML_PRE); + PUTC('\n'); + preformatted = TRUE; + } +#ifdef LONG_LIST + LYListFmtParse(list_format, entry, tmpfilename, target, tail); +#else + HTDirEntry(target, tail, entry->file_name); + PUTS(entry->file_name); + END(HTML_A); + MAYBE_END(HTML_LI); + PUTC('\n'); +#endif /* LONG_LIST */ + + next_element = HTBTree_next(bt, next_element); + /* pick up the next element of the list; + if none, return NULL */ + + /* optimize for expensive operation: */ +#ifdef DISP_PARTIAL + if (num_of_entries_output % + ((partial_threshold > 0) + ? partial_threshold + : display_lines) == 0) { + /* num_of_entries, num_of_entries_output... */ + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries_output++; +#endif /* DISP_PARTIAL */ + + } /* end while next_element */ + + if (status == HT_LOADED) { + if (state == 'I') { + START(HTML_P); + PUTS("Empty Directory"); + } +#ifndef LONG_LIST + else + END(HTML_DIR); +#endif /* !LONG_LIST */ + } + } /* end printing out the tree in order */ + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + } + END(HTML_BODY); + PUTC('\n'); + + FREE(tmpfilename); + FREE(tail); + HTBTreeAndObject_free(bt); + + if (status == HT_LOADED) { + if (HTDirReadme == HT_DIR_README_BOTTOM) + do_readme(target, localname); + FREE_TARGET; + } else { + ABORT_TARGET; + } + HTFinishDisplayPartial(); + return status; /* document loaded, maybe partial */ +} +#endif /* HAVE_READDIR */ + +#ifndef VMS +int HTStat(const char *filename, + struct stat *data) +{ + int result = -1; + size_t len = strlen(filename); + + if (len != 0 && LYIsPathSep(filename[len - 1])) { + char *temp_name = NULL; + + HTSprintf0(&temp_name, "%s.", filename); + result = HTStat(temp_name, data); + FREE(temp_name); + } else { + result = stat(filename, data); +#ifdef _WINDOWS + /* + * Someone claims that stat() doesn't give the proper result for a + * directory on Windows. + */ + if (result == -1 + && access(filename, 0) == 0) { + data->st_mode = S_IFDIR; + result = 0; + } +#endif + } + return result; +} +#endif + +#if defined(USE_ZLIB) || defined(USE_BZLIB) +static BOOL sniffStream(FILE *fp, char *buffer, size_t needed) +{ + long offset = ftell(fp); + BOOL result = FALSE; + + if (offset >= 0) { + if (fread(buffer, sizeof(char), needed, fp) == needed) { + result = TRUE; + } + if (fseek(fp, offset, SEEK_SET) < 0) { + CTRACE((tfp, "error seeking in stream\n")); + result = FALSE; + } + } + return result; +} +#endif + +#ifdef USE_ZLIB +static BOOL isGzipStream(FILE *fp) +{ + char buffer[3]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "\037\213", sizeof(buffer) - 1)) { + result = TRUE; + } else { + CTRACE((tfp, "not a gzip-stream\n")); + result = FALSE; + } + return result; +} + +/* + * Strictly speaking, DEFLATE has no header bytes. But decode what we can, + * (to eliminate the one "reserved" pattern) and provide a trace. See RFC-1951 + * discussion of BFINAL and BTYPE. + */ +static BOOL isDeflateStream(FILE *fp) +{ + char buffer[3]; + BOOL result = FALSE; + + if (sniffStream(fp, buffer, sizeof(buffer))) { + int bit1 = ((buffer[0] >> 0) & 1); + int bit2 = ((buffer[0] >> 1) & 1); + int bit3 = ((buffer[0] >> 2) & 1); + int btype = ((bit3 << 1) + bit2); + + if (!MemCmp(buffer, "\170\234", sizeof(buffer) - 1)) { + result = TRUE; + CTRACE((tfp, "isDeflate: assume zlib-wrapped deflate\n")); + } else if (btype == 3) { + CTRACE((tfp, "isDeflate: not a deflate-stream\n")); + } else { + CTRACE((tfp, "isDeflate: %send block, %s compression\n", + (bit1 ? "" : "non-"), + (btype == 0 + ? "no" + : (btype == 1 + ? "static Huffman" + : "dynamic Huffman")))); + result = TRUE; + } + } + return result; +} +#endif + +#ifdef USE_BZLIB +static BOOL isBzip2Stream(FILE *fp) +{ + char buffer[6]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "BZh", 3) + && isdigit(UCH(buffer[3])) + && isdigit(UCH(buffer[4]))) { + result = TRUE; + } else { + CTRACE((tfp, "not a bzip2-stream\n")); + result = FALSE; + } + return result; +} +#endif + +#ifdef VMS +#define FOPEN_MODE(bin) "r", "shr=put", "shr=upd" +#define DOT_STRING "._-" /* FIXME: should we check if suffix is after ']' or ':' ? */ +#else +#define FOPEN_MODE(bin) (bin ? BIN_R : "r") +#define DOT_STRING "." +#endif + +#ifdef USE_BROTLI +static FILE *brotli_open(const char *localname, const char *mode) +{ + CTRACE((tfp, "brotli_open file=%s, mode=%s\n", localname, mode)); + return fopen(localname, mode); +} +#endif + +static int decompressAndParse(HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink, + char *nodename GCC_UNUSED, + char *filename, + HTAtom *myEncoding, + HTFormat format, + int *statusp) +{ + HTAtom *encoding = 0; + +#ifdef USE_ZLIB + FILE *zzfp = 0; + gzFile gzfp = 0; +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + BZFILE *bzfp = 0; +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + FILE *brfp = 0; +#endif /* USE_BROTLI */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType internal_decompress = cftNone; + BOOL failed_decompress = NO; +#endif + int rootlen = 0; + char *localname = filename; + int bin; + FILE *fp; + int result = FALSE; + +#ifdef VMS + /* + * Assume that the file is in Unix-style syntax if it contains a '/' after + * the leading one. @@ + */ + localname = (StrChr(localname + 1, '/') + ? HTVMS_name(nodename, localname) + : localname + 1); +#endif /* VMS */ + + bin = HTCompressFileType(filename, ".", &rootlen) != cftNone; + fp = fopen(localname, FOPEN_MODE(bin)); + +#ifdef VMS + /* + * If the file wasn't VMS syntax, then perhaps it is Ultrix. + */ + if (!fp) { + char *ultrixname = 0; + + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", localname)); + HTSprintf0(&ultrixname, "%s::\"%s\"", nodename, filename); + fp = fopen(ultrixname, FOPEN_MODE(bin)); + if (!fp) { + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", ultrixname)); + } + FREE(ultrixname); + } +#endif /* VMS */ + CTRACE((tfp, "HTLoadFile: Opening `%s' gives %p\n", localname, (void *) fp)); + if (fp) { /* Good! */ + if (HTEditable(localname)) { + HTAtom *put = HTAtom_for("PUT"); + HTList *methods = HTAnchor_methods(anchor); + + if (HTList_indexOf(methods, put) == (-1)) { + HTList_addObject(methods, put); + } + } + /* + * Fake a Content-Encoding for compressed files. - FM + */ + if (!IsUnityEnc(myEncoding)) { + /* + * We already know from the call to HTFileFormat that + * this is a compressed file, no need to look at the filename + * again. - kw + */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType method = HTEncodingToCompressType(HTAtom_name(myEncoding)); +#endif + +#define isDOWNLOAD(m) (strcmp(format_out->name, STR_DOWNLOAD) && (method == m)) +#ifdef USE_ZLIB + if (isDOWNLOAD(cftGzip)) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } else if (isDOWNLOAD(cftDeflate)) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } else +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + if (isDOWNLOAD(cftBzip2)) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } else +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + if (isDOWNLOAD(cftBrotli)) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } else +#endif /* USE_BROTLI */ + { + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(myEncoding)); + format = HTAtom_for("www/compressed"); + } + } else { + CompressFileType cft = HTCompressFileType(localname, DOT_STRING, &rootlen); + + if (cft != cftNone) { + char *cp = NULL; + + StrAllocCopy(cp, localname); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, &encoding, NULL); + FREE(cp); + format = HTCharsetFormat(format, anchor, + UCLYhndl_HTFile_for_unspec); + StrAllocCopy(anchor->content_type, format->name); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + format = HTAtom_for("www/compressed"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); +#ifdef USE_BZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } +#else /* USE_BZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BZLIB */ + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); +#ifdef USE_BROTLI + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } +#else /* USE_BROTLI */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BROTLI */ + break; + case cftNone: + break; + } + } +#if defined(USE_ZLIB) || defined(USE_BZLIB) + if (internal_decompress != cftNone) { + switch (internal_decompress) { +#ifdef USE_ZLIB + case cftDeflate: + failed_decompress = (BOOLEAN) (zzfp == NULL); + break; + case cftCompress: + case cftGzip: + failed_decompress = (BOOLEAN) (gzfp == NULL); + break; +#endif +#ifdef USE_BZLIB + case cftBzip2: + failed_decompress = (BOOLEAN) (bzfp == NULL); + break; +#endif +#ifdef USE_BROTLI + case cftBrotli: + failed_decompress = (BOOLEAN) (brfp == NULL); + break; +#endif + default: + failed_decompress = YES; + break; + } + if (failed_decompress) { + *statusp = HTLoadError(NULL, + -(HT_ERROR), + FAILED_OPEN_COMPRESSED_FILE); + } else { + char *sugfname = NULL; + + if (anchor->SugFname) { + StrAllocCopy(sugfname, anchor->SugFname); + } else { + char *anchor_path = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + char *lastslash; + + HTUnEscape(anchor_path); + lastslash = strrchr(anchor_path, '/'); + if (lastslash) + StrAllocCopy(sugfname, lastslash + 1); + FREE(anchor_path); + } + FREE(anchor->content_encoding); + if (sugfname && *sugfname) + HTCheckFnameForCompression(&sugfname, anchor, + TRUE); + if (sugfname && *sugfname) + StrAllocCopy(anchor->SugFname, sugfname); + FREE(sugfname); +#ifdef USE_BROTLI + if (brfp) + *statusp = HTParseBrFile(format, format_out, + anchor, + brfp, sink); +#endif +#ifdef USE_BZLIB + if (bzfp) + *statusp = HTParseBzFile(format, format_out, + anchor, + bzfp, sink); +#endif +#ifdef USE_ZLIB + if (gzfp) + *statusp = HTParseGzFile(format, format_out, + anchor, + gzfp, sink); + else if (zzfp) + *statusp = HTParseZzFile(format, format_out, + anchor, + zzfp, sink); +#endif + } + } else +#endif /* USE_ZLIB || USE_BZLIB */ + { + *statusp = HTParseFile(format, format_out, anchor, fp, sink); + } + if (fp != 0) { + fclose(fp); + fp = 0; + } + result = TRUE; + } /* If successful open */ + return result; +} + +/* Load a document. + * ---------------- + * + * On entry: + * addr must point to the fully qualified hypertext reference. + * This is the physical address of the file + * + * On exit: + * returns <0 Error has occurred. + * HTLOADED OK + * + */ +int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + char *filename = NULL; + char *acc_method = NULL; + HTFormat format; + char *nodename = NULL; + char *newname = NULL; /* Simplified name of file */ + HTAtom *myEncoding = NULL; /* enc of this file, may be gzip etc. */ + int status = -1; + +#ifndef DISABLE_FTP + char *ftp_newhost; +#endif + +#ifdef VMS + struct stat stat_info; +#endif /* VMS */ + + /* + * Reduce the filename to a basic form (hopefully unique!). + */ + StrAllocCopy(newname, addr); + filename = HTParse(newname, "", PARSE_PATH | PARSE_PUNCTUATION); + nodename = HTParse(newname, "", PARSE_HOST); + + /* + * If access is ftp, or file is on another host, invoke ftp now. + */ + acc_method = HTParse(newname, "", PARSE_ACCESS); + if (strcmp("ftp", acc_method) == 0 || + (!LYSameHostname("localhost", nodename) && + !LYSameHostname(nodename, HTHostName()))) { + status = -1; + FREE(newname); + FREE(filename); + FREE(nodename); + FREE(acc_method); +#ifndef DISABLE_FTP + ftp_newhost = HTParse(addr, "", PARSE_HOST); + if (strcmp(ftp_lasthost, ftp_newhost)) + ftp_local_passive = ftp_passive; + + status = HTFTPLoad(addr, anchor, format_out, sink); + + if (ftp_passive == ftp_local_passive) { + if ((status >= 400) || (status < 0)) { + ftp_local_passive = (BOOLEAN) !ftp_passive; + status = HTFTPLoad(addr, anchor, format_out, sink); + } + } + + free(ftp_lasthost); + ftp_lasthost = ftp_newhost; +#endif /* DISABLE_FTP */ + return status; + } else { + FREE(newname); + FREE(acc_method); + } +#if defined(VMS) || defined(USE_DOS_DRIVES) + HTUnEscape(filename); +#endif /* VMS */ + + /* + * Determine the format and encoding mapped to any suffix. + */ + if (anchor->content_type && anchor->content_encoding) { + /* + * If content_type and content_encoding are BOTH already set in the + * anchor object, we believe it and don't try to derive format and + * encoding from the filename. - kw + */ + format = HTAtom_for(anchor->content_type); + myEncoding = HTAtom_for(anchor->content_encoding); + } else { + int default_UCLYhndl = UCLYhndl_HTFile_for_unspec; + + if (force_old_UCLYhndl_on_reload) { + force_old_UCLYhndl_on_reload = FALSE; + default_UCLYhndl = forced_UCLYhdnl; + } + + format = HTFileFormat(filename, &myEncoding, NULL); + + /* + * Check the format for an extended MIME charset value, and act on it + * if present. Otherwise, assume what is indicated by the last + * parameter (fallback will effectively be UCLYhndl_for_unspec, by + * default ISO-8859-1). - kw + */ + format = HTCharsetFormat(format, anchor, default_UCLYhndl); + } + +#ifdef VMS + /* + * Check to see if the 'filename' is in fact a directory. If it is create + * a new hypertext object containing a list of files and subdirectories + * contained in the directory. All of these are links to the directories + * or files listed. + */ + if (HTStat(filename, &stat_info) == -1) { + CTRACE((tfp, "HTLoadFile: Can't stat %s\n", filename)); + } else { + if (S_ISDIR(stat_info.st_mode)) { + if (HTDirAccess == HT_DIR_FORBID) { + FREE(filename); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", filename, HT_DIR_ENABLE_FILE); + if (HTStat(enable_file_name, &stat_info) == -1) { + FREE(filename); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + FREE(filename); + FREE(nodename); + return HTVMSBrowseDir(addr, anchor, format_out, sink); + } + } + + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + filename, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(filename); + return status; + } + FREE(filename); + +#else /* not VMS: */ + + FREE(filename); + + /* + * For unix, we try to translate the name into the name of a transparently + * mounted file. + * + * Not allowed in secure (HTClientHost) situations. TBL 921019 + */ +#ifndef NO_UNIX_IO + /* Need protection here for telnet server but not httpd server. */ + + if (!HTSecure) { /* try local file system */ + char *localname = HTLocalName(addr); + struct stat dir_info; + +#ifdef HAVE_READDIR + /* + * Multiformat handling. + * + * If needed, scan directory to find a good file. Bug: We don't stat + * the file to find the length. + */ + if ((strlen(localname) > strlen(MULTI_SUFFIX)) && + (0 == strcmp(localname + strlen(localname) - strlen(MULTI_SUFFIX), + MULTI_SUFFIX))) { + DIR *dp = 0; + BOOL forget_multi = NO; + + STRUCT_DIRENT *dirbuf; + float best = (float) NO_VALUE_FOUND; /* So far best is bad */ + HTFormat best_rep = NULL; /* Set when rep found */ + HTAtom *best_enc = NULL; + char *best_name = NULL; /* Best dir entry so far */ + + char *base = strrchr(localname, '/'); + size_t baselen = 0; + + if (!base || base == localname) { + forget_multi = YES; + } else { + *base++ = '\0'; /* Just got directory name */ + baselen = strlen(base) - strlen(MULTI_SUFFIX); + base[baselen] = '\0'; /* Chop off suffix */ + + dp = opendir(localname); + } + if (forget_multi || !dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 500, FAILED_DIR_SCAN); + } + + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + continue; /* if the entry is not being used, skip it */ +#endif + if (strlen(dirbuf->d_name) > baselen && /* Match? */ + !StrNCmp(dirbuf->d_name, base, baselen)) { + HTAtom *enc; + HTFormat rep = HTFileFormat(dirbuf->d_name, &enc, NULL); + float filevalue = HTFileValue(dirbuf->d_name); + float value = HTStackValue(rep, format_out, + filevalue, + 0L /* @@@@@@ */ ); + + if (value <= 0.0) { + int rootlen = 0; + const char *atomname = NULL; + CompressFileType cft = + HTCompressFileType(dirbuf->d_name, ".", &rootlen); + char *cp = NULL; + + enc = NULL; + if (cft != cftNone) { + StrAllocCopy(cp, dirbuf->d_name); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, NULL, NULL); + FREE(cp); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + switch (cft) { + case cftCompress: + atomname = "application/x-compressed"; + break; + case cftGzip: + atomname = "application/x-gzip"; + break; + case cftDeflate: + atomname = "application/x-deflate"; + break; + case cftBzip2: + atomname = "application/x-bzip2"; + break; + case cftBrotli: + atomname = "application/x-brotli"; + break; + case cftNone: + break; + } + + if (atomname != NULL) { + value = HTStackValue(format, format_out, + filevalue, 0L); + if (value <= 0.0) { + format = HTAtom_for(atomname); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + if (value <= 0.0) { + format = HTAtom_for("www/compressed"); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + } + } + if (value < NO_VALUE_FOUND) { + CTRACE((tfp, + "HTLoadFile: value of presenting %s is %f\n", + HTAtom_name(rep), value)); + if (value > best) { + best_rep = rep; + best_enc = enc; + best = value; + StrAllocCopy(best_name, dirbuf->d_name); + } + } /* if best so far */ + } + /* if match */ + } /* end while directory entries left to read */ + closedir(dp); + + if (best_rep) { + format = best_rep; + myEncoding = best_enc; + base[-1] = '/'; /* Restore directory name */ + base[0] = '\0'; + StrAllocCat(localname, best_name); + FREE(best_name); + } else { /* If not found suitable file */ + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_NO_REPRESENTATION); + } + /*NOTREACHED */ + } + /* if multi suffix */ + /* + * Check to see if the 'localname' is in fact a directory. If it is + * create a new hypertext object containing a list of files and + * subdirectories contained in the directory. All of these are links + * to the directories or files listed. NB This assumes the existence + * of a type 'STRUCT_DIRENT', which will hold the directory entry, and + * a type 'DIR' which is used to point to the current directory being + * read. + */ +#if defined(USE_DOS_DRIVES) + if (strlen(localname) == 2 && LYIsDosDrive(localname)) + LYAddPathSep(&localname); +#endif + if (HTStat(localname, &dir_info) == -1) /* get file information */ + { + /* if can't read file information */ + CTRACE((tfp, "HTLoadFile: can't stat %s\n", localname)); + + } else { /* Stat was OK */ + + if (S_ISDIR(dir_info.st_mode)) { + /* + * If localname is a directory. + */ + DIR *dp; + struct stat file_info; + + CTRACE((tfp, "%s is a directory\n", localname)); + + /* + * Check directory access. Selective access means only those + * directories containing a marker file can be browsed. + */ + if (HTDirAccess == HT_DIR_FORBID) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", localname, HT_DIR_ENABLE_FILE); + if (stat(enable_file_name, &file_info) != 0) { + FREE(localname); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + CTRACE((tfp, "Opening directory %s\n", localname)); + dp = opendir(localname); + if (!dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_DIR_UNREADABLE); + } + + /* + * Directory access is allowed and possible. + */ + + status = print_local_dir(dp, localname, + anchor, format_out, sink); + closedir(dp); + FREE(localname); + FREE(nodename); + return status; /* document loaded, maybe partial */ + + } + /* end if localname is a directory */ + if (S_ISREG(dir_info.st_mode)) { +#ifdef LONG_MAX + if (dir_info.st_size <= LONG_MAX) +#endif + anchor->content_length = (long) dir_info.st_size; + } + + } /* end if file stat worked */ + +/* End of directory reading section +*/ +#endif /* HAVE_READDIR */ + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + localname, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(localname); + return status; + } + FREE(localname); + } /* local unix file system */ +#endif /* !NO_UNIX_IO */ +#endif /* VMS */ + +#ifndef DECNET + /* + * Now, as transparently mounted access has failed, we try FTP. + */ + { + /* + * Deal with case-sensitivity differences on VMS versus Unix. + */ +#ifdef VMS + if (strcasecomp(nodename, HTHostName()) != 0) +#else + if (strcmp(nodename, HTHostName()) != 0) +#endif /* VMS */ + { + status = -1; + FREE(nodename); + if (StrNCmp(addr, "file://localhost", 16)) { + /* never go to ftp site when URL + * is file://localhost + */ +#ifndef DISABLE_FTP + status = HTFTPLoad(addr, anchor, format_out, sink); +#endif /* DISABLE_FTP */ + } + return status; + } + FREE(nodename); + } +#endif /* !DECNET */ + + /* + * All attempts have failed. + */ + { + CTRACE((tfp, "Can't open `%s', errno=%d\n", addr, SOCKET_ERRNO)); + + return HTLoadError(sink, 403, FAILED_FILE_UNREADABLE); + } +} + +static const char *program_paths[pp_Last]; + +/* + * Given a program number, return its path + */ +const char *HTGetProgramPath(ProgramPaths code) +{ + const char *result = NULL; + + if (code > ppUnknown && code < pp_Last) + result = program_paths[code]; + return result; +} + +/* + * Store a program's path. The caller must allocate the string used for 'path', + * since HTInitProgramPaths() may free it. + */ +void HTSetProgramPath(ProgramPaths code, const char *path) +{ + if (code > ppUnknown && code < pp_Last) { + program_paths[code] = isEmpty(path) ? 0 : path; + } +} + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ +void HTInitProgramPaths(BOOL init) +{ + ProgramPaths code; + int n; + const char *path; + const char *test; + + for (n = (int) ppUnknown + 1; n < (int) pp_Last; ++n) { + switch (code = (ProgramPaths) n) { +#ifdef BROTLI_PATH + case ppBROTLI: + path = BROTLI_PATH; + break; +#endif +#ifdef BZIP2_PATH + case ppBZIP2: + path = BZIP2_PATH; + break; +#endif +#ifdef CHMOD_PATH + case ppCHMOD: + path = CHMOD_PATH; + break; +#endif +#ifdef COMPRESS_PATH + case ppCOMPRESS: + path = COMPRESS_PATH; + break; +#endif +#ifdef COPY_PATH + case ppCOPY: + path = COPY_PATH; + break; +#endif +#ifdef CSWING_PATH + case ppCSWING: + path = CSWING_PATH; + break; +#endif +#ifdef GZIP_PATH + case ppGZIP: + path = GZIP_PATH; + break; +#endif +#ifdef INFLATE_PATH + case ppINFLATE: + path = INFLATE_PATH; + break; +#endif +#ifdef INSTALL_PATH + case ppINSTALL: + path = INSTALL_PATH; + break; +#endif +#ifdef MKDIR_PATH + case ppMKDIR: + path = MKDIR_PATH; + break; +#endif +#ifdef MV_PATH + case ppMV: + path = MV_PATH; + break; +#endif +#ifdef RLOGIN_PATH + case ppRLOGIN: + path = RLOGIN_PATH; + break; +#endif +#ifdef RM_PATH + case ppRM: + path = RM_PATH; + break; +#endif +#ifdef RMDIR_PATH + case ppRMDIR: + path = RMDIR_PATH; + break; +#endif +#ifdef SETFONT_PATH + case ppSETFONT: + path = SETFONT_PATH; + break; +#endif +#ifdef TAR_PATH + case ppTAR: + path = TAR_PATH; + break; +#endif +#ifdef TELNET_PATH + case ppTELNET: + path = TELNET_PATH; + break; +#endif +#ifdef TN3270_PATH + case ppTN3270: + path = TN3270_PATH; + break; +#endif +#ifdef TOUCH_PATH + case ppTOUCH: + path = TOUCH_PATH; + break; +#endif +#ifdef UNCOMPRESS_PATH + case ppUNCOMPRESS: + path = UNCOMPRESS_PATH; + break; +#endif +#ifdef UNZIP_PATH + case ppUNZIP: + path = UNZIP_PATH; + break; +#endif +#ifdef UUDECODE_PATH + case ppUUDECODE: + path = UUDECODE_PATH; + break; +#endif +#ifdef ZCAT_PATH + case ppZCAT: + path = ZCAT_PATH; + break; +#endif +#ifdef ZIP_PATH + case ppZIP: + path = ZIP_PATH; + break; +#endif + default: + path = NULL; + break; + } + test = HTGetProgramPath(code); + if (test != NULL && test != path) { + free(DeConst(test)); + } + if (init) { + HTSetProgramPath(code, path); + } + } +} + +/* + * Protocol descriptors + */ +#ifdef GLOBALDEF_IS_MACRO +#define _HTFILE_C_1_INIT { "ftp", HTLoadFile, 0 } +GLOBALDEF(HTProtocol, HTFTP, _HTFILE_C_1_INIT); +#define _HTFILE_C_2_INIT { "file", HTLoadFile, HTFileSaveStream } +GLOBALDEF(HTProtocol, HTFile, _HTFILE_C_2_INIT); +#else +GLOBALDEF HTProtocol HTFTP = +{"ftp", HTLoadFile, 0}; +GLOBALDEF HTProtocol HTFile = +{"file", HTLoadFile, HTFileSaveStream}; +#endif /* GLOBALDEF_IS_MACRO */ diff --git a/WWW/Library/Implementation/HTFile.h b/WWW/Library/Implementation/HTFile.h new file mode 100644 index 0000000..0bdfb79 --- /dev/null +++ b/WWW/Library/Implementation/HTFile.h @@ -0,0 +1,368 @@ +/* + * $LynxId: HTFile.h,v 1.35 2021/07/29 22:54:21 tom Exp $ + * File access in libwww + * FILE ACCESS + * + * These are routines for local file access used by WWW browsers and servers. + * Implemented by HTFile.c. + * + * If the file is not a local file, then we pass it on to HTFTP in case it + * can be reached by FTP. + */ +#ifndef HTFILE_H +#define HTFILE_H + +#include +#include + +#ifndef HTML_H +#include /* SCW */ +#endif /* HTML_H */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Controlling globals + * + * These flags control how directories and files are represented as + * hypertext, and are typically set by the application from command + * line options, etc. + */ extern int HTDirAccess; + /* Directory access level */ + +#define HT_DIR_FORBID 0 /* Altogether forbidden */ +#define HT_DIR_SELECTIVE 1 /* If HT_DIR_ENABLE_FILE exists */ +#define HT_DIR_OK 2 /* Any accessible directory */ + +#define HT_DIR_ENABLE_FILE ".www_browsable" /* If exists, can browse */ + + extern int HTDirReadme; /* Include readme files in listing? */ + + /* Values: */ +#define HT_DIR_README_NONE 0 /* No */ +#define HT_DIR_README_TOP 1 /* Yes, first */ +#define HT_DIR_README_BOTTOM 2 /* Yes, at the end */ + +#define HT_DIR_README_FILE "README" + +/* + * Convert filenames between local and WWW formats + */ + extern char *HTURLPath_toFile(const char *name, int expand_all, int is_remote); + extern char *HTnameOfFile_WWW(const char *name, int WWW_prefix, int expand_all); + +#define HTLocalName(name) HTnameOfFile_WWW(name,TRUE,TRUE) +#define HTfullURL_toFile(name) HTnameOfFile_WWW(name,FALSE,TRUE) +#define HTpartURL_toFile(name) HTnameOfFile_WWW(name,FALSE,FALSE) + +/* + * Make a WWW name from a full local path name + */ + extern char *WWW_nameOfFile(const char *name); + +/* + * Generate the name of a cache file + */ + extern char *HTCacheFileName(const char *name); + +/* + * Generate fragments of HTML for source-view: + */ + extern void HTStructured_doctype(HTStructured * target, HTFormat format_out); + + extern void HTStructured_meta(HTStructured * target, HTFormat format_out); +/* + * Output directory titles + * + * This is (like the next one) used by HTFTP. It is common code to generate + * the title and heading 1 and the parent directory link for any anchor. + * + * changed to return TRUE if parent directory link was generated, + * FALSE otherwise - KW + */ + extern BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop); + +/* + * Check existence. + */ + extern int HTStat(const char *filename, + struct stat *data); + +/* Load a document. + * ---------------- + */ + extern int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * Output a directory entry + * + * This is used by HTFTP.c for example -- it is a common routine for + * generating a linked directory entry. + */ + extern void HTDirEntry(HTStructured * target, /* in which to put the linked text */ const char *tail, /* last part of directory name */ + const char *entry); /* name of this entry */ + +/* + * HTSetSuffix: Define the representation for a file suffix + * + * This defines a mapping between local file suffixes and file content + * types and encodings. + * + * ON ENTRY, + * + * suffix includes the "." if that is important (normally, yes!) + * + * representation is MIME-style content-type + * + * encoding is MIME-style content-transfer-encoding + * (8bit, 7bit, etc) or HTTP-style content-encoding + * (gzip, compress etc.) + * + * quality an a priori judgement of the quality of such files + * (0.0..1.0) + * + * HTSetSuffix5 has one more parameter for a short description of the type + * which is otherwise derived from the representation: + * + * desc is a short textual description, or NULL + * + * Examples: HTSetSuffix(".ps", "application/postscript", "8bit", 1.0); + * Examples: HTSetSuffix(".psz", "application/postscript", "gzip", 1.0); + * A MIME type could also indicate a non-trivial encoding on its own + * ("application/x-compressed-tar"), but in that case don't use encoding + * to also indicate it but use "binary" etc. + */ + extern void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double quality); + +#define HTSetSuffix(suff,rep,enc,q) HTSetSuffix5(suff, rep, enc, NULL, q) + +/* + * HTFileFormat: Get Representation and Encoding from file name. + * + * ON EXIT, + * + * return The represntation it imagines the file is in. + * + * *pEncoding The encoding (binary, 7bit, etc). See HTSetSuffix. + */ + extern HTFormat HTFileFormat(const char *filename, + HTAtom **pEncoding, + const char **pDesc); + +/* + * HTCharsetFormat: Revise the file format in relation to the Lynx charset. + * + * This checks the format associated with an anchor for + * for an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ + extern HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl); + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ + extern void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs); + +/* + * Determine file value from file name. + */ + extern float HTFileValue(const char *filename); + +/* + * Known compression types. + */ + typedef enum { + cftNone + ,cftCompress + ,cftGzip + ,cftBzip2 + ,cftDeflate + ,cftBrotli + } CompressFileType; + +/* + * Determine compression type from file name, by looking at its suffix. + */ + extern CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen); + +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTEncodingToCompressType(const char *encoding); +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTContentTypeToCompressType(const char *ct); +/* + * Determine compression type from the content-type and/or content-encoding. + */ + extern CompressFileType HTContentToCompressType(HTParentAnchor *anchor); +/* + * Determine compression encoding from the compression method. + */ + extern const char *HTCompressTypeToEncoding(CompressFileType method); +/* + * Determine expected file-suffix from the compression method. + */ + extern const char *HTCompressTypeToSuffix(CompressFileType method); +/* + * Determine write access to a file. + * + * ON EXIT, + * + * return value YES if file can be accessed and can be written to. + * + * BUGS + * + * Isn't there a quicker way? + */ + +#if defined(HAVE_CONFIG_H) + +#ifndef HAVE_GETGROUPS +#define NO_GROUPS +#endif + +#else + +#ifdef VMS +#define NO_GROUPS +#endif /* VMS */ +#ifdef NO_UNIX_IO +#define NO_GROUPS +#endif /* NO_UNIX_IO */ +#ifdef PCNFS +#define NO_GROUPS +#endif /* PCNFS */ +#ifdef NOUSERS +#define NO_GROUPS +#endif /* PCNFS */ + +#endif /* HAVE_CONFIG_H */ + + extern BOOL HTEditable(const char *filename); + +/* Make a save stream. + * ------------------- + */ + extern HTStream *HTFileSaveStream(HTParentAnchor *anchor); + +/* + * Determine a suitable suffix, given the representation. + * + * ON ENTRY, + * + * rep is the atomized MIME style representation + * enc is an encoding (8bit, binary, gzip, compress,..) + * + * ON EXIT, + * + * returns a pointer to a suitable suffix string if one has + * been found, else NULL. + */ + extern const char *HTFileSuffix(HTAtom *rep, + const char *enc); + +/* + * Enumerate external programs that lynx may assume exists. Unlike those + * given in download scripts, etc., lynx would really like to know their + * absolute paths, for better security. + */ + typedef enum { + ppUnknown = 0 + ,ppBROTLI + ,ppBZIP2 + ,ppCHMOD + ,ppCOMPRESS + ,ppCOPY + ,ppCSWING + ,ppGZIP + ,ppINFLATE + ,ppINSTALL + ,ppMKDIR + ,ppMV + ,ppRLOGIN + ,ppRM + ,ppRMDIR + ,ppSETFONT + ,ppTAR + ,ppTELNET + ,ppTN3270 + ,ppTOUCH + ,ppUNCOMPRESS + ,ppUNZIP + ,ppUUDECODE + ,ppZCAT + ,ppZIP + ,pp_Last + } ProgramPaths; + +/* + * Given a program number, return its path + */ + extern const char *HTGetProgramPath(ProgramPaths code); + +/* + * Store a program's path + */ + extern void HTSetProgramPath(ProgramPaths code, + const char *path); + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ + extern void HTInitProgramPaths(BOOL init); + +/* + * The Protocols + */ +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFTP); + extern GLOBALREF (HTProtocol, HTFile); + +#else + GLOBALREF HTProtocol HTFTP, HTFile; +#endif /* GLOBALREF_IS_MACRO */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFILE_H */ diff --git a/WWW/Library/Implementation/HTFinger.c b/WWW/Library/Implementation/HTFinger.c new file mode 100644 index 0000000..566761a --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.c @@ -0,0 +1,418 @@ +/* + * $LynxId: HTFinger.c,v 1.31 2013/11/28 11:27:50 tom Exp $ + * + * FINGER ACCESS HTFinger.c + * ============= + * Authors: + * ARB Andrew Brooks + * + * History: + * 21 Apr 94 First version (ARB, from HTNews.c by TBL) + * 12 Mar 96 Made the URL and command buffering secure from + * stack modifications, beautified the HTLoadFinger() + * and response() functions, and added support for the + * following URL formats for sending a "", "/w", + * "username[@host]", or "/w username[@host]" command + * to the server: + * finger://host + * finger://host/ + * finger://host/%2fw + * finger://host/%2fw%20username[@host] + * finger://host/w/username[@host] + * finger://host/username[@host] + * finger://host/username[@host]/w + * finger://username@host + * finger://username@host/ + * finger://username@host/w + * 15 Mar 96 Added support for port 79 gtype 0 gopher URLs + * relayed from HTLoadGopher. - FM + */ + +#include + +#ifndef DISABLE_FINGER + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FINGER_PORT 79 /* See rfc742 */ +#define BIG 1024 /* Bug */ + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) +#define NEXT_CHAR HTGetCharacter() + +/* Module-wide variables +*/ +static int finger_fd; /* Socket for FingerHost */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* The output sink */ +static HTStructuredClass targetClass; /* Copy of fn addresses */ + +/* Initialisation for this module + * ------------------------------ + */ +static BOOL initialized = NO; +static BOOL initialize(void) +{ + finger_fd = -1; /* Disconnected */ + return YES; +} + +/* Start anchor element + * -------------------- + */ +static void start_anchor(const char *href) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + { + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = (BOOL) (i == HTML_A_HREF); + } + ((const char **) value)[HTML_A_HREF] = href; + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + +} + +/* Send Finger Command line to remote host & Check Response + * -------------------------------------------------------- + * + * On entry, + * command points to the command to be sent, including CRLF, or is null + * pointer if no command to be sent. + * On exit, + * Negative status indicates transmission error, socket closed. + * Positive status is a Finger status. + */ + +static int response(char *command, + char *sitename, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + int length = (int) strlen(command); + int ch, i; + char line[BIG], *l, *cmd = NULL; + char *p = line, *href = NULL; + + if (length == 0) + return (-1); + + /* Set up buffering. + */ + HTInitInput(finger_fd); + + /* Send the command. + */ + CTRACE((tfp, "HTFinger command to be sent: %s", command)); + status = (int) NETWRITE(finger_fd, (char *) command, (unsigned) length); + if (status < 0) { + CTRACE((tfp, "HTFinger: Unable to send command. Disconnecting.\n")); + NETCLOSE(finger_fd); + finger_fd = -1; + return status; + } + /* if bad status */ + /* Make a hypertext object with an anchor list. + */ + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* Create the results report. + */ + CTRACE((tfp, "HTFinger: Reading finger information\n")); + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS("Finger server on "); + PUTS(sitename); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + PUTS("Finger server on "); + START(HTML_EM); + PUTS(sitename); + END(HTML_EM); + PUTS(": "); + StrAllocCopy(cmd, command); + for (i = ((int) strlen(cmd) - 1); i >= 0; i--) { + if (cmd[i] == LF || cmd[i] == CR) { + cmd[i] = '\0'; + } else { + break; + } + } + PUTS(cmd); + FREE(cmd); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + while ((ch = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFinger: Interrupted in HTGetCharacter, apparently.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + goto end_html; + } + + if (ch != LF) { + *p = (char) ch; /* Put character in line */ + if (p < &line[BIG - 1]) { + p++; + } + } else { + *p = '\0'; /* Terminate line */ + /* + * OK we now have a line. + * Load it as 'l' and parse it. + */ + p = l = line; + while (*l) { + if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) && + StrNCmp(l, "snews://", 8) && + StrNCmp(l, "nntp://", 7) && + StrNCmp(l, "snewspost:", 10) && + StrNCmp(l, "snewsreply:", 11) && + StrNCmp(l, "newspost:", 9) && + StrNCmp(l, "newsreply:", 10) && + StrNCmp(l, "ftp://", 6) && + StrNCmp(l, "file:/", 6) && + StrNCmp(l, "finger://", 9) && + StrNCmp(l, "http://", 7) && + StrNCmp(l, "https://", 8) && + StrNCmp(l, "wais://", 7) && + StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) && + StrNCmp(l, "cso://", 6) && + StrNCmp(l, "gopher://", 9)) + PUTC(*l++); + else { + StrAllocCopy(href, l); + start_anchor(strtok(href, " \r\n\t,>)\"")); + while (*l && !StrChr(" \r\n\t,>)\"", *l)) + PUTC(*l++); + END(HTML_A); + FREE(href); + } + } + PUTC('\n'); + } + } + NETCLOSE(finger_fd); + finger_fd = -1; + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + return (0); +} + +/* Load by name HTLoadFinger + * ============ + */ +int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream) +{ + static char empty[1]; + + char *username, *sitename; /* Fields extracted from URL */ + char *slash, *at_sign; /* Fields extracted from URL */ + char *command, *str, *param; /* Buffers */ + int port; /* Port number from URL */ + int status; /* tcp return */ + int result = HT_LOADED; + BOOL IsGopherURL = FALSE; + const char *p1 = arg; + + CTRACE((tfp, "HTFinger: Looking for %s\n", (arg ? arg : "NULL"))); + + if (!(arg && *arg)) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* Ignore if no name */ + } + + if (!initialized) + initialized = initialize(); + if (!initialized) { + HTAlert(gettext("Could not set up finger connection.")); + return HT_NOT_LOADED; /* FAIL */ + } + + /* Set up the host and command fields. + */ + if (!strncasecomp(arg, "finger://", 9)) { + p1 = arg + 9; /* Skip "finger://" prefix */ + } else if (!strncasecomp(arg, "gopher://", 9)) { + p1 = arg + 9; /* Skip "gopher://" prefix */ + IsGopherURL = TRUE; + } + + param = 0; + sitename = StrAllocCopy(param, p1); + if (param == 0) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; + } else if ((slash = StrChr(sitename, '/')) != NULL) { + *slash++ = '\0'; + HTUnEscape(slash); + if (IsGopherURL) { + if (*slash != '0') { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } + *slash++ = '\0'; + } + } + + if ((at_sign = StrChr(sitename, '@')) != NULL) { + if (IsGopherURL) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } else { + *at_sign++ = '\0'; + username = sitename; + sitename = at_sign; + HTUnEscape(username); + } + } else if (slash) { + username = slash; + } else { + username = empty; + } + + if (*sitename == '\0') { + HTAlert(gettext("Could not load data (no sitename in finger URL)")); + result = HT_NOT_LOADED; /* Ignore if no name */ + } else if (HTParsePort(sitename, &port) != NULL) { + if (port != 79) { + HTAlert(gettext("Invalid port number - will only use port 79!")); + result = HT_NOT_LOADED; /* Ignore if wrong port */ + } + } + + if (result == HT_LOADED) { + /* Load the string for making a connection/ + */ + str = 0; + HTSprintf0(&str, "lose://%s/", sitename); + + /* Load the command for the finger server. + */ + command = 0; + if (at_sign && slash) { + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else if (at_sign) { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if (*username == '/') { + if ((slash = StrChr((username + 1), '/')) != NULL) { + *slash = ' '; + } + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '/') { + if (*username + 2 != '\0') { + *(username + 1) = ' '; + } else { + *(username + 1) = '\0'; + } + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '\0') { + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((slash = StrChr(username, '/')) != NULL) { + *slash++ = '\0'; + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + + /* Now, let's get a stream setup up from the FingerHost: + * CONNECTING to finger host + */ + CTRACE((tfp, "HTFinger: doing HTDoConnect on '%s'\n", str)); + status = HTDoConnect(str, "finger", FINGER_PORT, &finger_fd); + CTRACE((tfp, "HTFinger: Done DoConnect; status %d\n", status)); + + if (status == HT_INTERRUPTED) { + /* Interrupt cleanly */ + CTRACE((tfp, + "HTFinger: Interrupted on connect; recovering cleanly.\n")); + HTProgress(CONNECTION_INTERRUPTED); + result = HT_NOT_LOADED; + } else if (status < 0) { + NETCLOSE(finger_fd); + finger_fd = -1; + CTRACE((tfp, "HTFinger: Unable to connect to finger host.\n")); + HTAlert(gettext("Could not access finger host.")); + result = HT_NOT_LOADED; /* FAIL */ + } else { + CTRACE((tfp, "HTFinger: Connected to finger host '%s'.\n", str)); + + /* Send the command, and process response if successful. + */ + if (response(command, sitename, anAnchor, format_out, stream) != 0) { + HTAlert(gettext("No response from finger server.")); + result = HT_NOT_LOADED; + } + } + FREE(str); + FREE(command); + } + FREE(param); + return result; +} + +#ifdef GLOBALDEF_IS_MACRO +#define _HTFINGER_C_1_INIT { "finger", HTLoadFinger, NULL } +GLOBALDEF(HTProtocol, HTFinger, _HTFINGER_C_1_INIT); +#else +GLOBALDEF HTProtocol HTFinger = +{"finger", HTLoadFinger, NULL}; +#endif /* GLOBALDEF_IS_MACRO */ + +#endif /* not DISABLE_FINGER */ diff --git a/WWW/Library/Implementation/HTFinger.h b/WWW/Library/Implementation/HTFinger.h new file mode 100644 index 0000000..071d43b --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.h @@ -0,0 +1,30 @@ +/* Finger protocol module for the WWW library */ +/* History: + * 21 Apr 94 Andrew Brooks + */ + +#ifndef HTFINGER_H +#define HTFINGER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFinger); + +#else + GLOBALREF HTProtocol HTFinger; +#endif /* GLOBALREF_IS_MACRO */ + + extern int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream); + +#ifdef __cplusplus +} +#endif +#endif /* HTFINGER_H */ diff --git a/WWW/Library/Implementation/HTFormat.c b/WWW/Library/Implementation/HTFormat.c new file mode 100644 index 0000000..a830387 --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.c @@ -0,0 +1,2181 @@ +/* + * $LynxId: HTFormat.c,v 1.96 2022/03/31 23:39:38 tom Exp $ + * + * Manage different file formats HTFormat.c + * ============================= + * + * Bugs: + * Not reentrant. + * + * Assumes the incoming stream is ASCII, rather than a local file + * format, and so ALWAYS converts from ASCII on non-ASCII machines. + * Therefore, non-ASCII machines can't read local files. + * + */ + +#define HTSTREAM_INTERNAL 1 + +#include + +/* Implements: +*/ +#include + +static float HTMaxSecs = 1e10; /* No effective limit */ + +#ifdef UNIX +#ifdef NeXT +#define PRESENT_POSTSCRIPT "open %s; /bin/rm -f %s\n" +#else +#define PRESENT_POSTSCRIPT "(ghostview %s ; /bin/rm -f %s)&\n" + /* Full pathname would be better! */ +#endif /* NeXT */ +#endif /* UNIX */ + +#include +#include +#include +#include +#include +#include +#include +#include +/* Streams and structured streams which we use: +*/ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef DISP_PARTIAL +#include +#endif + +#ifdef USE_BROTLI +#include +#endif + +BOOL HTOutputSource = NO; /* Flag: shortcut parser to stdout */ + +/* this version used by the NetToText stream */ +struct _HTStream { + const HTStreamClass *isa; + BOOL had_cr; + HTStream *sink; +}; + +/* Presentation methods + * -------------------- + */ +HTList *HTPresentations = NULL; +HTPresentation *default_presentation = NULL; + +/* + * To free off the presentation list. + */ +#ifdef LY_FIND_LEAKS +static void HTFreePresentations(void); +#endif + +/* Define a presentation system command for a content-type + * ------------------------------------------------------- + */ +void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetPresentation"); + + assert(representation != NULL); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetPresentation rep=%s, command=%s, test=%s, qual=%f\n", + NonNull(representation), + NonNull(command), + NonNull(testcommand), + quality)); + + pres->rep = HTAtom_for(representation); + pres->rep_out = WWW_PRESENT; /* Fixed for now ... :-) */ + pres->converter = HTSaveAndExecute; /* Fixed for now ... */ + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = 0; + pres->accept_opt = media; + + pres->command = NULL; + StrAllocCopy(pres->command, command); + + pres->testcommand = NULL; + StrAllocCopy(pres->testcommand, testcommand); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + if (strcmp(representation, "*") == 0) { + FREE(default_presentation); + default_presentation = pres; + } else { + HTList_addObject(HTPresentations, pres); + } +} + +/* Define a built-in function for a content-type + * --------------------------------------------- + */ +void HTSetConversion(const char *representation_in, + const char *representation_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetConversion"); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetConversion rep_in=%s, rep_out=%s, qual=%f\n", + NonNull(representation_in), + NonNull(representation_out), + quality)); + + pres->rep = HTAtom_for(representation_in); + pres->rep_out = HTAtom_for(representation_out); + pres->converter = converter; + pres->command = NULL; + pres->testcommand = NULL; + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = TRUE; + pres->accept_opt = media; + + /* + * Memory Leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + HTList_addObject(HTPresentations, pres); +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free the presentation list. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * Made to clean up Lynx's bad leakage. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void HTFreePresentations(void) +{ + HTPresentation *pres = NULL; + + /* + * Loop through the list. + */ + while (!HTList_isEmpty(HTPresentations)) { + /* + * Free off each item. May also need to free off it's items, but not + * sure as of yet. + */ + pres = (HTPresentation *) HTList_removeLastObject(HTPresentations); + FREE(pres->command); + FREE(pres->testcommand); + FREE(pres); + } + /* + * Free the list itself. + */ + HTList_delete(HTPresentations); + HTPresentations = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* File buffering + * -------------- + * + * The input file is read using the macro which can read from + * a socket or a file. + * The input buffer size, if large will give greater efficiency and + * release the server faster, and if small will save space on PCs etc. + */ +#define INPUT_BUFFER_SIZE 4096 /* Tradeoff */ +static char input_buffer[INPUT_BUFFER_SIZE]; +static char *input_pointer; +static char *input_limit; +static int input_file_number; + +/* Set up the buffering + * + * These routines are public because they are in fact needed by + * many parsers, and on PCs and Macs we should not duplicate + * the static buffer area. + */ +void HTInitInput(int file_number) +{ + input_file_number = file_number; + input_pointer = input_limit = input_buffer; +} + +int interrupted_in_htgetcharacter = 0; +int HTGetCharacter(void) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + do { + if (input_pointer >= input_limit) { + int status = NETREAD(input_file_number, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFormat: Interrupted in HTGetCharacter\n")); + interrupted_in_htgetcharacter = 1; + return EOF; + } + CTRACE((tfp, "HTFormat: File read error %d\n", status)); + return EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(UCH(ch)); +} + +#ifdef USE_SSL +int HTGetSSLCharacter(void *handle) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + if (!handle) + return (char) EOF; + do { + if (input_pointer >= input_limit) { + int status = SSL_read((SSL *) handle, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return (char) EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTFormat: Interrupted in HTGetSSLCharacter\n")); + interrupted_in_htgetcharacter = 1; + return (char) EOF; + } + CTRACE((tfp, "HTFormat: SSL_read error %d\n", status)); + return (char) EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(ch); +} +#endif /* USE_SSL */ + +/* Match maintype to any MIME type starting with maintype, for example: + * image/gif should match image + */ +static int half_match(char *trial_type, char *target) +{ + char *cp = StrChr(trial_type, '/'); + + /* if no '/' or no '*' */ + if (!cp || *(cp + 1) != '*') + return 0; + + CTRACE((tfp, "HTFormat: comparing %s and %s for half match\n", + trial_type, target)); + + /* main type matches */ + if (!StrNCmp(trial_type, target, ((cp - trial_type) - 1))) + return 1; + + return 0; +} + +/* + * Evaluate a deferred mailcap test command, i.e.,. one that substitutes the + * document's charset or other values in %{name} format. + */ +static BOOL failsMailcap(HTPresentation *pres, HTParentAnchor *anchor) +{ + if (pres->testcommand != NULL && + anchor != NULL && + anchor->content_type_params != NULL) { + if (LYTestMailcapCommand(pres->testcommand, + anchor->content_type_params) != 0) + return TRUE; + } + return FALSE; +} + +#define WWW_WILDCARD_REP_OUT HTAtom_for("*") + +/* Look up a presentation + * ---------------------- + * + * If fill_in is NULL, only look for an exact match. + * If a wildcard match is made, *fill_in is used to store + * a possibly modified presentation, and a pointer to it is + * returned. For an exact match, a pointer to the presentation + * in the HTPresentations list is returned. Returns NULL if + * nothing found. - kw + * + */ +static HTPresentation *HTFindPresentation(HTFormat rep_in, + HTFormat rep_out, + HTPresentation *fill_in, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTFindPresentation" + HTAtom *wildcard = NULL; /* = HTAtom_for("*"); lookup when needed - kw */ + int n; + int i; + HTPresentation *pres; + HTPresentation *match; + HTPresentation *strong_wildcard_match = 0; + HTPresentation *weak_wildcard_match = 0; + HTPresentation *last_default_match = 0; + HTPresentation *strong_subtype_wildcard_match = 0; + + CTRACE((tfp, THIS_FUNC ": Looking up presentation for %s to %s\n", + HTAtom_name(rep_in), HTAtom_name(rep_out))); + + n = HTList_count(HTPresentations); + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + return pres; + + } else if (!fill_in) { + continue; + } else { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_wildcard_match) + strong_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + } else if (!fill_in) { + continue; + + } else if (half_match(HTAtom_name(pres->rep), + HTAtom_name(rep_in))) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_subtype_wildcard_match) + strong_subtype_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong subtype wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + if (pres->rep == WWW_SOURCE) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!weak_wildcard_match) + weak_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, + THIS_FUNC ": found weak wildcard match: %s\n", + HTAtom_name(pres->rep_out))); + + } else if (!last_default_match) { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + last_default_match = pres; + /* otherwise use the first one */ + } + } + } + } + + match = (strong_subtype_wildcard_match + ? strong_subtype_wildcard_match + : (strong_wildcard_match + ? strong_wildcard_match + : (weak_wildcard_match + ? weak_wildcard_match + : last_default_match))); + + if (match) { + *fill_in = *match; /* Specific instance */ + fill_in->rep = rep_in; /* yuk */ + fill_in->rep_out = rep_out; /* yuk */ + return fill_in; + } + + return NULL; +#undef THIS_FUNC +} + +/* Create a filter stack + * --------------------- + * + * If a wildcard match is made, a temporary HTPresentation + * structure is made to hold the destination format while the + * new stack is generated. This is just to pass the out format to + * MIME so far. Storing the format of a stream in the stream might + * be a lot neater. + * + */ +HTStream *HTStreamStack(HTFormat rep_in, + HTFormat rep_out, + HTStream *sink, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTStreamStack" + HTPresentation temp; + HTPresentation *match; + HTStream *result; + + CTRACE((tfp, THIS_FUNC ": Constructing stream stack for %s to %s (%s)\n", + HTAtom_name(rep_in), + HTAtom_name(rep_out), + NONNULL(anchor->content_type_params))); + + if (rep_out == rep_in) { + result = sink; + + } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) { + if (match == &temp) { + CTRACE((tfp, THIS_FUNC ": Using %s\n", HTAtom_name(temp.rep_out))); + } else { + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(match->rep), + HTAtom_name(match->rep_out))); + } + result = (*match->converter) (match, anchor, sink); + } else { + result = NULL; + } + if (TRACE) { + if (result && result->isa && result->isa->name) { + CTRACE((tfp, THIS_FUNC ": Returning \"%s\"\n", result->isa->name)); + } else if (result) { + CTRACE((tfp, THIS_FUNC ": Returning *unknown* stream!\n")); + } else { + CTRACE((tfp, THIS_FUNC ": Returning NULL!\n")); + CTRACE_FLUSH(tfp); /* a crash may be imminent... - kw */ + } + } + return result; +#undef THIS_FUNC +} + +/* Put a presentation near start of list + * ------------------------------------- + * + * Look up a presentation (exact match only) and, if found, reorder + * it to the start of the HTPresentations list. - kw + */ +void HTReorderPresentation(HTFormat rep_in, + HTFormat rep_out) +{ + HTPresentation *match; + + if ((match = HTFindPresentation(rep_in, rep_out, NULL, NULL))) { + HTList_removeObject(HTPresentations, match); + HTList_addObject(HTPresentations, match); + } +} + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ +void HTFilterPresentations(void) +{ + int i, j; + int n = HTList_count(HTPresentations); + HTPresentation *p, *q; + BOOL matched; + char *s, *t; + + CTRACE((tfp, "HTFilterPresentations (AcceptMedia %#x)\n", LYAcceptMedia)); + for (i = 0; i < n; i++) { + p = (HTPresentation *) HTList_objectAt(HTPresentations, i); + s = HTAtom_name(p->rep); + + p->get_accept = FALSE; + if ((LYAcceptMedia & p->accept_opt) != 0 + && p->rep_out == WWW_PRESENT + && p->rep != WWW_SOURCE + && strcasecomp(s, "www/mime") + && strcasecomp(s, "www/compressed") + && p->quality <= 1.0 && p->quality >= 0.0) { + matched = TRUE; + for (j = 0; j < i; j++) { + q = (HTPresentation *) HTList_objectAt(HTPresentations, j); + t = HTAtom_name(q->rep); + + if (!strcasecomp(s, t)) { + matched = FALSE; + CTRACE((tfp, " match %s %s\n", s, t)); + break; + } + } + p->get_accept = matched; + } + } +} + +/* Find the cost of a filter stack + * ------------------------------- + * + * Must return the cost of the same stack which HTStreamStack would set up. + * + * On entry, + * length The size of the data to be converted + */ +float HTStackValue(HTFormat rep_in, + HTFormat rep_out, + double initial_value, + long int length) +{ + HTAtom *wildcard = WWW_WILDCARD_REP_OUT; + + CTRACE((tfp, "HTFormat: Evaluating stream stack for %s worth %.3f to %s\n", + HTAtom_name(rep_in), initial_value, HTAtom_name(rep_out))); + + if (rep_out == WWW_SOURCE || rep_out == rep_in) + return 0.0; + + { + int n = HTList_count(HTPresentations); + int i; + HTPresentation *pres; + + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in && + (pres->rep_out == rep_out || pres->rep_out == wildcard)) { + float value = (float) (initial_value * pres->quality); + + if (HTMaxSecs > 0.0) + value = (value + - ((float) length * pres->secs_per_byte + + pres->secs) + / HTMaxSecs); + return value; + } + } + } + + return (float) -1e30; /* Really bad */ + +} + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDisplay() - it works!. + * + */ +void HTDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + if (display_partial) { + /* + * HText_getNumOfLines() = "current" number of complete lines received + * NumOfLines_partial = number of lines at the moment of last repaint. + * (we update NumOfLines_partial only when we repaint the display.) + * + * display_partial could only be enabled in HText_new() so a new + * HTMainText object available - all HText_ functions use it, lines + * counter HText_getNumOfLines() in particular. + * + * Otherwise HTMainText holds info from the previous document and we + * may repaint it instead of the new one: prev doc scrolled to the + * first line (=Newline_partial) is not good looking :-) 23 Aug 1998 + * Leonid Pauzner + * + * So repaint the page only when necessary: + */ + int Newline_partial = LYGetNewline(); + + if (((Newline_partial + display_lines) - 1 > NumOfLines_partial) + /* current page not complete... */ + && (partial_threshold > 0 ? + ((Newline_partial + partial_threshold) - 1 <= + HText_getNumOfLines()) : + ((Newline_partial + display_lines) - 1 <= HText_getNumOfLines())) + /* + * Originally we rendered by increments of 2 lines, + * but that got annoying on slow network connections. + * Then we switched to full-pages. Now it's configurable. + * If partial_threshold <= 0, then it's a full page + */ + ) { + if (LYMainLoop_pageDisplay(Newline_partial)) + NumOfLines_partial = HText_getNumOfLines(); + } + } +#else /* nothing */ +#endif /* DISP_PARTIAL */ +} + +/* Put this as early as possible, OK just after HTDisplayPartial() */ +void HTFinishDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + /* + * End of incremental rendering stage here. + */ + display_partial = FALSE; +#endif /* DISP_PARTIAL */ +} + +/* Push data from a socket down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream, i.e., containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -2 Unexpected disconnect before any data received. + * -1 Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -2 socket still open, target stream still valid. + * -1 socket still open, target aborted. + * otherwise socket closed, target stream still valid. + */ +int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle GCC_UNUSED, + HTStream *sink) +{ + HTStreamClass targetClass; + BOOL suppress_readprogress = NO; + off_t limit = anchor ? anchor->content_length : 0; + off_t bytes = 0; + off_t header_length = 0; + int rv = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push binary from socket down sink + * + * This operation could be put into a main event loop + */ + HTReadProgress(bytes, (off_t) 0); + for (;;) { + int status; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + (*targetClass._abort) (sink, NULL); + rv = -1; + goto finished; + } + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } +#ifdef USE_SSL + if (handle) + status = SSL_read((SSL *) handle, input_buffer, INPUT_BUFFER_SIZE); + else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#endif /* USE_SSL */ + if (status <= 0) { + if (status == 0) { + break; + } else if (status == HT_INTERRUPTED) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } else if (SOCKET_ERRNO == ENOTCONN || +#ifdef _WINDOWS /* 1997/11/10 (Mon) 16:57:18 */ + SOCKET_ERRNO == ETIMEDOUT || +#endif + SOCKET_ERRNO == ECONNRESET || + SOCKET_ERRNO == EPIPE) { + /* + * Arrrrgh, HTTP 0/1 compatibility problem, maybe. + */ + if (bytes <= 0) { + /* + * Don't have any data, so let the calling function decide + * what to do about it. - FM + */ + rv = -2; + goto finished; + } else { +#ifdef UNIX + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. + * I don't know about all the different TCP stacks for VMS + * etc., so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected server disconnect."); + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); +#else /* !UNIX */ + /* + * Treat what we've gotten already as the complete + * transmission. - FM + */ + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); + status = 0; +#endif /* UNIX */ + } +#ifdef UNIX + } else { /* status < 0 and other errno */ + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. I + * don't know about all the different TCP stacks for VMS etc., + * so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected read error."); + if (bytes) { + (void) NETCLOSE(file_number); + rv = HT_LOADED; + } else { + (*targetClass._abort) (sink, NULL); + rv = -1; + } + goto finished; +#endif + } + break; + } + + /* + * Suppress ReadProgress messages when collecting a redirection + * message, at least initially (unless/until anchor->content_type gets + * changed, probably by the MIME message parser). That way messages + * put up by the HTTP module or elsewhere can linger in the statusline + * for a while. - kw + */ + suppress_readprogress = (BOOL) (anchor && anchor->content_type && + !strcmp(anchor->content_type, + "message/x-http-redirection")); +#ifdef NOT_ASCII + { + char *p; + + for (p = input_buffer; p < input_buffer + status; p++) { + *p = FROMASCII(*p); + } + } +#endif /* NOT_ASCII */ + + header_length = anchor != 0 ? anchor->header_length : 0; + + (*targetClass.put_block) (sink, input_buffer, status); + if (anchor != 0 && anchor->inHEAD) { + if (!suppress_readprogress) { + statusline(gettext("Reading headers...")); + } + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes\n", + CAST_off_t (anchor->header_length))); + } else { + /* + * If header-length is increased at this point, that is due to + * HTMIME, which detects the end of the server headers. There + * may be additional (non-header) data in that block. + */ + if (anchor != 0 && (anchor->header_length > header_length)) { + int header = (int) (anchor->header_length - header_length); + + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes " + "(%d extra vs %d total)\n", + CAST_off_t (anchor->header_length), + header, status)); + if (status > header) { + bytes += (status - header); + } + } else { + bytes += status; + } + if (!suppress_readprogress) { + HTReadProgress(bytes, limit); + } + HTDisplayPartial(); + } + + /* a few buggy implementations do not close the connection properly + * and will hang if we try to read past the declared content-length. + */ + if (limit > 0 && bytes >= limit) + break; + } /* next bufferload */ + if (anchor != 0) { + CTRACE((tfp, "HTCopy copied %" + PRI_off_t " actual, %" + PRI_off_t " limit\n", CAST_off_t (bytes), CAST_off_t (limit))); + anchor->actual_length = bytes; + } + + _HTProgress(TRANSFER_COMPLETE); + (void) NETCLOSE(file_number); + rv = HT_LOADED; + + finished: + HTFinishDisplayPartial(); + return (rv); +} + +/* Push data from a file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open, target stream still valid. + */ +int HTFileCopy(FILE *fp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* Push binary from socket down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = (int) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, fp); + if (status == 0) { /* EOF or error */ + if (ferror(fp) == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTFormat: Read error, read returns %d\n", + ferror(fp))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) 0); + /* Suppress last screen update in partial mode - a regular update under + * control of mainloop() should follow anyway. - kw + */ +#ifdef DISP_PARTIAL + if (display_partial && bytes != HTMainAnchor->content_length) + HTDisplayPartial(); +#endif + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifdef USE_SOURCE_CACHE +/* Push data from an HTChunk down a stream + * --------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) and target (sink) assumed valid. + * + * Return values: + * HT_LOADED All data sent. + * HT_INTERRUPTED Interruption after some data read. + * + * State of memory and target stream on return: + * always chunk unchanged, target stream still valid. + */ +int HTMemCopy(HTChunk *chunk, HTStream *sink) +{ + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + + targetClass = *(sink->isa); + HTReadProgress(bytes = 0, (off_t) 0); + for (; chunk != NULL; chunk = chunk->next) { + + /* Push the data down the stream a piece at a time, in case we're + * running a large document on a slow machine. + */ + (*targetClass.put_block) (sink, chunk->data, chunk->size); + bytes += chunk->size; + + HTReadProgress(bytes, (off_t) 0); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } + + HTFinishDisplayPartial(); + return rv; +} +#endif + +#ifdef USE_ZLIB +/* Push data from a gzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open (should have gzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp still open, target stream still valid. + */ +static int HTGzFileCopy(gzFile gzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int gzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate gzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = gzread(gzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTGzFileCopy: Read error, gzread returns %d\n", + status)); + CTRACE((tfp, "gzerror : %s\n", + gzerror(gzfp, &gzerrnum))); + if (TRACE) { + if (gzerrnum == Z_ERRNO) + perror("gzerror "); + } + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifndef HAVE_ZERROR +#define zError(s) LynxZError(s) +static const char *zError(int status) +{ + static char result[80]; + + sprintf(result, "zlib error %d", status); + return result; +} +#endif + +/* Push data from a deflate file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. The code is + * loosely based on the inflate.c file from w3m. + * + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open (should have deflated content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp still open, target stream still valid. + */ +static int HTZzFileCopy(FILE *zzfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTZzFileCopy" + static char dummy_head[1 + 1] = + { + 0x8 + 0x7 * 0x10, + (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, + }; + + z_stream s; + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + char output_buffer[INPUT_BUFFER_SIZE]; + int status; + int flush; + int retry = 0; + int len = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + memset(&s, 0, sizeof(s)); + status = inflateInit(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateInit() %s\n", zError(status))); + exit_immediately(EXIT_FAILURE); + } + s.avail_in = 0; + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + flush = Z_NO_FLUSH; + + /* read and inflate deflate'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + if (s.avail_in == 0) { + s.next_in = (Bytef *) input_buffer; + s.avail_in = (uInt) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, zzfp); + len = (int) s.avail_in; + } + status = inflate(&s, flush); + if (status == Z_STREAM_END || status == Z_BUF_ERROR) { + len = (int) sizeof(output_buffer) - (int) s.avail_out; + if (len > 0) { + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + } + rv = HT_LOADED; + break; + } else if (status == Z_DATA_ERROR && !retry++) { + status = inflateReset(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateReset() %s\n", zError(status))); + rv = -1; + break; + } + s.next_in = (Bytef *) dummy_head; + s.avail_in = sizeof(dummy_head); + (void) inflate(&s, flush); + s.next_in = (Bytef *) input_buffer; + s.avail_in = (unsigned) len; + continue; + } else if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflate() %s\n", zError(status))); + rv = bytes ? HT_PARTIAL_CONTENT : -1; + break; + } else if (s.avail_out == 0) { + len = sizeof(output_buffer); + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = bytes ? HT_INTERRUPTED : -1; + break; + } + } + retry = 1; + } /* next bufferload */ + + inflateEnd(&s); + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* Push data from a bzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int bzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate bzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = BZ2_bzread(bzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTBzFileCopy: Read error, bzread returns %d\n", + status)); + CTRACE((tfp, "bzerror : %s\n", + BZ2_bzerror(bzfp, &bzerrnum))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* Push data from a brotli file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBrFileCopy(FILE *brfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTBrFileCopy" + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + BrotliDecoderResult status2 = BROTLI_DECODER_RESULT_ERROR; + + char *brotli_buffer = NULL; + char *normal_buffer = NULL; + size_t brotli_size; + size_t brotli_limit = 0; + size_t brotli_offset = brotli_limit; + size_t normal_size; + size_t normal_limit = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate brotli'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + /* + * first, read all of the brotli'd file into memory, to work with the + * library's limitations. + */ + for (;;) { + size_t input_chunk = INPUT_BUFFER_SIZE; + + brotli_offset = brotli_limit; + brotli_limit += input_chunk; + brotli_buffer = realloc(brotli_buffer, brotli_limit); + if (brotli_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + status = (int) fread(brotli_buffer + brotli_offset, sizeof(char), + input_chunk, brfp); + + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, THIS_FUNC ": Read error, fread returns %d\n", status)); + if (bytes) { + if (!feof(brfp)) + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + bytes += status; + } + + /* + * next, unless we encountered an error (and have no data), try + * decompressing with increasing output buffer sizes until the brotli + * library succeeds. + */ + if (bytes > 0) { + do { + if (normal_limit == 0) + normal_limit = (10 * brotli_limit) + INPUT_BUFFER_SIZE; + else + normal_limit *= 2; + normal_buffer = realloc(normal_buffer, normal_limit); + if (normal_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + brotli_size = (size_t) bytes; + normal_size = normal_limit; + status2 = BrotliDecoderDecompress(brotli_size, + (uint8_t *) brotli_buffer, + &normal_size, + (uint8_t *) normal_buffer); + /* + * brotli library should return + * BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, + * but actually returns + * BROTLI_DECODER_RESULT_ERROR + * + * Accommodate possible improvements... + */ + } while (status2 != BROTLI_DECODER_RESULT_SUCCESS); + } + + /* + * finally, pump that data into the output stream. + */ + if (status2 == BROTLI_DECODER_RESULT_SUCCESS) { + CTRACE((tfp, THIS_FUNC ": decompressed %ld -> %ld (1:%.1f)\n", + brotli_size, normal_size, + (double) normal_size / (double) brotli_size)); + (*targetClass.put_block) (sink, normal_buffer, (int) normal_size); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + } + } + free(brotli_buffer); + free(normal_buffer); + + /* next bufferload */ + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_BZLIB */ + +/* Push data from a socket down a stream STRIPPING CR + * -------------------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the socket. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + */ +void HTCopyNoCR(HTParentAnchor *anchor GCC_UNUSED, + int file_number, + HTStream *sink) +{ + HTStreamClass targetClass; + int character; + + /* Push the data, ignoring CRLF, down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push text from telnet socket down sink + * + * @@@@@ To push strings could be faster? (especially is we cheat and + * don't ignore CR! :-} + */ + HTInitInput(file_number); + for (;;) { + character = HTGetCharacter(); + if (character == EOF) + break; + (*targetClass.put_character) (sink, (char) character); + } +} + +/* Parse a socket given format and file number + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -501 Stream stack failed (cannot present or convert). + * -2 Unexpected disconnect before any data received. + * -1 Stream stack failed (cannot present or convert), or + * Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -501 socket still open, target stream NULL. + * -2 socket still open, target freed. + * -1 socket still open, target stream aborted or NULL. + * otherwise socket closed, target stream freed. + */ +int HTParseSocket(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + return -1; + } + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat: %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); /* returns -501 */ + FREE(buffer); + } else { + /* + * Push the data, don't worry about CRLF we can strip them later. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTCopy(anchor, file_number, NULL, stream); + if (rv != -1 && rv != HT_INTERRUPTED) + (*targetClass._free) (stream); + } + return rv; + /* Originally: full: HT_LOADED; partial: HT_INTERRUPTED; no bytes: -1 */ +} + +/* Parse a file given format and file pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to \n for unix + * when the format is textual. + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open; target freed, aborted, or NULL. + */ +int HTParseFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + if (fp == NULL) { + result = HT_LOADED; + } else { + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" + * rather than on content-type. @@@ When we handle encoding. The + * current method smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTFileCopy(fp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + } + return result; +} + +#ifdef USE_SOURCE_CACHE +/* Parse a document in memory given format and memory block pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) assumed valid, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * HT_LOADED All data sent. + * + * State of memory and target stream on return: + * always chunk unchanged; target freed, aborted, or NULL. + */ +int HTParseMem(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + if (!stream || !stream->isa) { + char *buffer = 0; + + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseMem): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } else { + + /* Push the data down the stream + */ + targetClass = *(stream->isa); + (void) HTMemCopy(chunk, stream); + (*targetClass._free) (stream); + result = HT_LOADED; + } + return result; +} +#endif + +#ifdef USE_ZLIB +static int HTCloseGzFile(gzFile gzfp) +{ + int gzres; + + if (gzfp == NULL) + return 0; + gzres = gzclose(gzfp); + if (TRACE) { + if (gzres == Z_ERRNO) { + perror("gzclose "); + } else if (gzres != Z_OK) { + CTRACE((tfp, "gzclose : error number %d\n", gzres)); + } + } + return (gzres); +} + +/* HTParseGzFile + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp closed; target freed, aborted, or NULL. + */ +int HTParseGzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseGzFile(gzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTGzFileCopy(gzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseGzFile(gzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} + +/* HTParseZzFile + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp closed; target freed, aborted, or NULL. + */ +int HTParseZzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(zzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTZzFileCopy(zzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(zzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +static void HTCloseBzFile(BZFILE * bzfp) +{ + if (bzfp) + BZ2_bzclose(bzfp); +} + +/* HTParseBzFile + * + * State of file and target stream on entry: + * bzFile (bzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp closed; target freed, aborted, or NULL. + */ +int HTParseBzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseBzFile(bzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseBzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBzFileCopy(bzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseBzFile(bzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* HTParseBrFile + * + * State of file and target stream on entry: + * FILE* (brfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always brfp closed; target freed, aborted, or NULL. + */ +int HTParseBrFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTParseBrFile" + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(brfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in " THIS_FUNC "): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBrFileCopy(brfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(brfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +#undef THIS_FUNC +} +#endif /* USE_BROTLI */ + +/* Converter stream: Network Telnet to internal character text + * ----------------------------------------------------------- + * + * The input is assumed to be in ASCII, with lines delimited + * by (13,10) pairs, These pairs are converted into (CR,LF) + * pairs in the local representation. The (CR,LF) sequence + * when found is changed to a '\n' character, the internal + * C representation of a new line. + */ + +static void NetToText_put_character(HTStream *me, int net_char) +{ + char c = (char) FROMASCII(net_char); + + if (me->had_cr) { + if (c == LF) { + me->sink->isa->put_character(me->sink, '\n'); /* Newline */ + me->had_cr = NO; + return; + } else { + me->sink->isa->put_character(me->sink, CR); /* leftover */ + } + } + me->had_cr = (BOOL) (c == CR); + if (!me->had_cr) + me->sink->isa->put_character(me->sink, c); /* normal */ +} + +static void NetToText_put_string(HTStream *me, const char *s) +{ + const char *p; + + for (p = s; *p; p++) + NetToText_put_character(me, *p); +} + +static void NetToText_put_block(HTStream *me, const char *s, int l) +{ + const char *p; + + for (p = s; p < (s + l); p++) + NetToText_put_character(me, *p); +} + +static void NetToText_free(HTStream *me) +{ + (me->sink->isa->_free) (me->sink); /* Close rest of pipe */ + FREE(me); +} + +static void NetToText_abort(HTStream *me, HTError e) +{ + me->sink->isa->_abort(me->sink, e); /* Abort rest of pipe */ + FREE(me); +} + +/* The class structure +*/ +static HTStreamClass NetToTextClass = +{ + "NetToText", + NetToText_free, + NetToText_abort, + NetToText_put_character, + NetToText_put_string, + NetToText_put_block +}; + +/* The creation method +*/ +HTStream *HTNetToText(HTStream *sink) +{ + HTStream *me = typecalloc(HTStream); + + if (me == NULL) + outofmem(__FILE__, "NetToText"); + + me->isa = &NetToTextClass; + + me->had_cr = NO; + me->sink = sink; + return me; +} + +static HTStream HTBaseStreamInstance; /* Made static */ + +/* + * ERROR STREAM + * ------------ + * There is only one error stream shared by anyone who wants a + * generic error returned from all stream methods. + */ +static void HTErrorStream_put_character(HTStream *me GCC_UNUSED, int c GCC_UNUSED) +{ + LYCancelDownload = TRUE; +} + +static void HTErrorStream_put_string(HTStream *me GCC_UNUSED, const char *s) +{ + if (s && *s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_write(HTStream *me GCC_UNUSED, const char *s, int l) +{ + if (l && s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_free(HTStream *me GCC_UNUSED) +{ + return; +} + +static void HTErrorStream_abort(HTStream *me GCC_UNUSED, HTError e GCC_UNUSED) +{ + return; +} + +static const HTStreamClass HTErrorStreamClass = +{ + "ErrorStream", + HTErrorStream_free, + HTErrorStream_abort, + HTErrorStream_put_character, + HTErrorStream_put_string, + HTErrorStream_write +}; + +HTStream *HTErrorStream(void) +{ + CTRACE((tfp, "ErrorStream. Created\n")); + HTBaseStreamInstance.isa = &HTErrorStreamClass; /* The rest is random */ + return &HTBaseStreamInstance; +} diff --git a/WWW/Library/Implementation/HTFormat.h b/WWW/Library/Implementation/HTFormat.h new file mode 100644 index 0000000..835daef --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.h @@ -0,0 +1,588 @@ +/* + * $LynxId: HTFormat.h,v 1.42 2022/04/01 07:54:14 tom Exp $ + * + * HTFormat: The format manager in the WWW Library + * MANAGE DIFFERENT DOCUMENT FORMATS + * + * Here we describe the functions of the HTFormat module which handles conversion between + * different data representations. (In MIME parlance, a representation is known as a + * content-type. In WWW the term "format" is often used as it is shorter). + * + * This module is implemented by HTFormat.c. This hypertext document is used to generate + * the HTFormat.h include file. Part of the WWW library. + */ +#ifndef HTFORMAT_H +#define HTFORMAT_H + +#include +#include +#include +#include + +#ifdef USE_SOURCE_CACHE +#include +#endif + +#ifdef USE_BZLIB +#include +#endif + +#ifdef USE_ZLIB +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + + These macros (which used to be constants) define some basic internally + referenced representations. The www/xxx ones are of course not MIME + standard. + + www/source is an output format which leaves the input untouched. It is + useful for diagnostics, and for users who want to see the original, whatever + it is. + + */ +/* Internal ones */ +#define STR_SOURCE "www/source" + extern HTAtom *WWW_SOURCE; /* calculated once, heavy used */ + +/* + + www/present represents the user's perception of the document. If you + convert to www/present, you present the material to the user. + + */ +#define STR_PRESENT "www/present" +#define WWW_PRESENT HTAtom_for(STR_PRESENT) /* The user's perception */ + +#define WWW_DEBUG HTAtom_for("www/debug") +/* + + WWW_DEBUG represents the user's perception of debug information, for example + sent as a HTML document in a HTTP redirection message. + + */ + +/* + + The message/rfc822 format means a MIME message or a plain text message with + no MIME header. This is what is returned by an HTTP server. + + */ +#define WWW_MIME HTAtom_for("www/mime") /* A MIME message */ + +/* + For parsing only the header. - kw + */ +#define WWW_MIME_HEAD HTAtom_for("message/x-rfc822-head") + +/* + + www/print is like www/present except it represents a printed copy. + + */ +#define WWW_PRINT HTAtom_for("www/print") /* A printed copy */ + +/* + + www/unknown is a really unknown type. Some default action is appropriate. + + */ +#define WWW_UNKNOWN HTAtom_for("www/unknown") + +#ifdef DIRED_SUPPORT +/* + www/dired signals directory edit mode. +*/ +#define WWW_DIRED HTAtom_for("www/dired") +#endif + +/* + * Miscellaneous internal MIME types. + */ +#define STR_DOWNLOAD "www/download" +#define WWW_DOWNLOAD HTAtom_for(STR_DOWNLOAD) + +#define STR_DUMP "www/dump" +#define WWW_DUMP HTAtom_for(STR_DUMP) + +/* + + These are regular MIME types. HTML is assumed to be added by the W3 code. + application/octet-stream was mistakenly application/binary in earlier libwww + versions (pre 2.11). + + */ +#define STR_BINARY "application/octet-stream" +#define STR_PLAINTEXT "text/plain" +#define STR_HTML "text/html" + +#define WWW_BINARY HTAtom_for(STR_BINARY) +#define WWW_PLAINTEXT HTAtom_for(STR_PLAINTEXT) +#define WWW_HTML HTAtom_for(STR_HTML) + +#define WWW_POSTSCRIPT HTAtom_for("application/postscript") +#define WWW_RICHTEXT HTAtom_for("application/rtf") +#define WWW_AUDIO HTAtom_for("audio/basic") + + typedef HTAtom *HTEncoding; + +/* + * The following are values for the MIME types: + */ +#define WWW_ENC_7BIT HTAtom_for("7bit") +#define WWW_ENC_8BIT HTAtom_for("8bit") +#define WWW_ENC_BINARY HTAtom_for("binary") + +/* + * We also add + */ +#define WWW_ENC_COMPRESS HTAtom_for("compress") + +/* + * Does a string designate a real encoding, or is it just + * a "dummy" as for example 7bit, 8bit, and binary? + */ +#define IsUnityEncStr(senc) \ + ((senc)==NULL || *(senc)=='\0' || !strcmp(senc,"identity") ||\ + !strcmp(senc,"8bit") || !strcmp(senc,"binary") || !strcmp(senc,"7bit")) + +#define IsUnityEnc(enc) \ + ((enc)==NULL || (enc)==HTAtom_for("identity") ||\ + (enc)==WWW_ENC_8BIT || (enc)==WWW_ENC_BINARY || (enc)==WWW_ENC_7BIT) + +/* + +The HTPresentation and HTConverter types + + This HTPresentation structure represents a possible conversion algorithm + from one format to another. It includes a pointer to a conversion routine. + The conversion routine returns a stream to which data should be fed. See + also HTStreamStack which scans the list of registered converters and calls + one. See the initialisation module for a list of conversion routines. + + */ + typedef struct _HTPresentation HTPresentation; + + typedef HTStream *HTConverter (HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + + struct _HTPresentation { + HTAtom *rep; /* representation name atomized */ + HTAtom *rep_out; /* resulting representation */ + HTConverter *converter; /* routine to gen the stream stack */ + char *command; /* MIME-format command string */ + char *testcommand; /* MIME-format test string */ + float quality; /* Between 0 (bad) and 1 (good) */ + float secs; + float secs_per_byte; + off_t maxbytes; + BOOL get_accept; /* list in "Accept:" for GET */ + int accept_opt; /* matches against LYAcceptMedia */ + }; + +/* + + The list of presentations is kept by this module. It is also scanned by + modules which want to know the set of formats supported. for example. + + */ + extern HTList *HTPresentations; + +/* + + The default presentation is used when no other is appropriate + + */ + extern HTPresentation *default_presentation; + +/* + * Options used for "Content-Type" string + */ + typedef enum { + contentBINARY = 0 + ,contentTEXT + ,contentHTML + } ContentType; + +/* + * Options used for "Accept:" string + */ + typedef enum { + /* make the components powers of two so we can add them */ + mediaINT = 1 /* internal types predefined in HTInit.c */ + ,mediaEXT = 2 /* external types predefined in HTInit.c */ + ,mediaCFG = 4 /* types, e.g., viewers, from lynx.cfg */ + ,mediaUSR = 8 /* user's mime-types, etc. */ + ,mediaSYS = 16 /* system's mime-types, etc. */ + /* these are useful flavors for the options menu */ + ,mediaOpt1 = mediaINT + ,mediaOpt2 = mediaINT + mediaCFG + ,mediaOpt3 = mediaINT + mediaCFG + mediaUSR + ,mediaOpt4 = mediaINT + mediaCFG + mediaUSR + mediaSYS + /* this is the flavor from pre-2.8.6 */ + ,mediaALL = mediaINT + mediaEXT + mediaCFG + mediaUSR + mediaSYS + } AcceptMedia; + +/* + * Options used for "Accept-Encoding:" string + */ + typedef enum { + encodingNONE = 0 + ,encodingGZIP = 1 + ,encodingDEFLATE = 2 + ,encodingCOMPRESS = 4 + ,encodingBZIP2 = 8 + ,encodingBROTLI = 16 + ,encodingALL = (encodingGZIP + + encodingDEFLATE + + encodingCOMPRESS + + encodingBZIP2 + + encodingBROTLI) + } AcceptEncoding; + +/* + +HTSetPresentation: Register a system command to present a format + + ON ENTRY, + + rep is the MIME - style format name + + command is the MAILCAP - style command template + + testcommand is the MAILCAP - style testcommand template + + quality A degradation faction 0..1.0 + + secs A limit on the time user will wait (0.0 for infinity) + secs_per_byte + + maxbytes A limit on the length acceptable as input (0 infinite) + + media Used in filtering presentation types for "Accept:" + + */ + extern void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTSetConversion: Register a conversion routine + + ON ENTRY, + + rep_in is the content-type input + + rep_out is the resulting content-type + + converter is the routine to make the stream to do it + + */ + + extern void HTSetConversion(const char *rep_in, + const char *rep_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTStreamStack: Create a stack of streams + + This is the routine which actually sets up the conversion. It currently + checks only for direct conversions, but multi-stage conversions are forseen. + It takes a stream into which the output should be sent in the final format, + builds the conversion stack, and returns a stream into which the data in the + input format should be fed. The anchor is passed because hypertxet objects + load information into the anchor object which represents them. + + */ + extern HTStream *HTStreamStack(HTFormat format_in, + HTFormat format_out, + HTStream *stream_out, + HTParentAnchor *anchor); + +/* +HTReorderPresentation: put presentation near head of list + + Look up a presentation (exact match only) and, if found, reorder it to the + start of the HTPresentations list. - kw + */ + + extern void HTReorderPresentation(HTFormat format_in, + HTFormat format_out); + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ + extern void HTFilterPresentations(void); + +/* + +HTStackValue: Find the cost of a filter stack + + Must return the cost of the same stack which HTStreamStack would set up. + + ON ENTRY, + + format_in The format of the data to be converted + + format_out The format required + + initial_value The intrinsic "value" of the data before conversion on a scale + from 0 to 1 + + length The number of bytes expected in the input format + + */ + extern float HTStackValue(HTFormat format_in, + HTFormat rep_out, + double initial_value, + long int length); + +#define NO_VALUE_FOUND -1e20 /* returned if none found */ + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDispaly() - it works!. + * + */ + extern void HTDisplayPartial(void); + + extern void HTFinishDisplayPartial(void); + +/* + +HTCopy: Copy a socket to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. + + */ + extern int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle, + HTStream *sink); + +/* + +HTFileCopy: Copy a file to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseFile + + */ + extern int HTFileCopy(FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTMemCopy: Copy a memory chunk to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseMem + + */ + extern int HTMemCopy(HTChunk *chunk, + HTStream *sink); +#endif + +/* + +HTCopyNoCR: Copy a socket to a stream, stripping CR characters. + + It is slower than HTCopy . + + */ + + extern void HTCopyNoCR(HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +Clear input buffer and set file number + + This routine and the one below provide simple character input from sockets. + (They are left over from the older architecture and may not be used very + much.) The existence of a common routine and buffer saves memory space in + small implementations. + + */ + extern void HTInitInput(int file_number); + +/* + +Get next character from buffer + + */ + extern int interrupted_in_htgetcharacter; + extern int HTGetCharacter(void); + +/* + +HTParseSocket: Parse a socket given its format + + This routine is called by protocol modules to load an object. uses + HTStreamStack and the copy routines above. Returns HT_LOADED if successful, + <0 if not. + + */ + extern int HTParseSocket(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +HTParseFile: Parse a File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + + */ + extern int HTParseFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTParseMem: Parse a document in memory + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTMemCopy. Returns HT_LOADED if successful, can also + return <0 for failure. + + */ + extern int HTParseMem(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink); +#endif + +#ifdef USE_ZLIB +/* +HTParseGzFile: Parse a gzip'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTGzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseGzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink); + +/* +HTParseZzFile: Parse a deflate'd File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTZzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseZzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink); + +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* +HTParseBzFile: Parse a bzip2'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink); + +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* +HTParseBrFile: Parse a brotli'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBrFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBrFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink); + +#endif /* USE_BROTLI */ + +/* + +HTNetToText: Convert Net ASCII to local representation + + This is a filter stream suitable for taking text from a socket and passing + it into a stream which expects text in the local C representation. It does + ASCII and newline conversion. As usual, pass its output stream to it when + creating it. + + */ + extern HTStream *HTNetToText(HTStream *sink); + +/* + +HTFormatInit: Set up default presentations and conversions + + These are defined in HTInit.c or HTSInit.c if these have been replaced. If + you don't call this routine, and you don't define any presentations, then + this routine will automatically be called the first time a conversion is + needed. However, if you explicitly add some conversions (eg using + HTLoadRules) then you may want also to explicitly call this to get the + defaults as well. + + */ + extern void HTFormatInit(void); + +/* + +Epilogue + + */ + extern BOOL HTOutputSource; /* Flag: shortcut parser */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFORMAT_H */ diff --git a/WWW/Library/Implementation/HTGopher.c b/WWW/Library/Implementation/HTGopher.c new file mode 100644 index 0000000..0314179 --- /dev/null +++ b/WWW/Library/Implementation/HTGopher.c @@ -0,0 +1,2071 @@ +/* + * $LynxId: HTGopher.c,v 1.77 2022/04/01 00:18:09 tom Exp $ + * + * GOPHER ACCESS HTGopher.c + * ============= + * + * History: + * 26 Sep 90 Adapted from other accesses (News, HTTP) TBL + * 29 Nov 91 Downgraded to C, for portable implementation. + * 10 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Added a + * form-based CSO/PH gateway. Can be invoked via a + * "cso://host[:port]/" or "gopher://host:105/2" + * URL. If a gopher URL is used with a query token + * ('?'), the old ISINDEX procedure will be used + * instead of the form-based gateway. + * 15 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Pass + * port 79, gtype 0 gopher URLs to the finger + * gateway. + */ + +#define HTSTREAM_INTERNAL 1 + +#include /* Coding convention macros */ +#include /* For HTFileFormat() */ + +#ifndef DISABLE_GOPHER +#include +#include +#include +#include +#include + +/* + * Implements. + */ +#include + +#define GOPHER_PORT 70 /* See protocol spec */ +#define CSO_PORT 105 /* See protocol spec */ +#define BIG 1024 /* Bug */ +#define LINE_LENGTH 256 /* Bug */ + +/* + * Gopher entity types. + */ +#define GOPHER_TEXT '0' +#define GOPHER_MENU '1' +#define GOPHER_CSO '2' +#define GOPHER_ERROR '3' +#define GOPHER_MACBINHEX '4' +#define GOPHER_PCBINARY '5' +#define GOPHER_UUENCODED '6' +#define GOPHER_INDEX '7' +#define GOPHER_TELNET '8' +#define GOPHER_BINARY '9' +#define GOPHER_GIF 'g' +#define GOPHER_HTML 'h' /* HTML */ +#define GOPHER_CHTML 'H' /* HTML */ +#define GOPHER_SOUND 's' +#define GOPHER_WWW 'w' /* W3 address */ +#define GOPHER_IMAGE 'I' +#define GOPHER_TN3270 'T' +#define GOPHER_INFO 'i' +#define GOPHER_DUPLICATE '+' +#define GOPHER_PLUS_IMAGE ':' /* Addition from Gopher Plus */ +#define GOPHER_PLUS_MOVIE ';' +#define GOPHER_PLUS_SOUND '<' +#define GOPHER_PLUS_PDF 'P' + +#include + +/* + * Hypertext object building machinery. + */ +#include + +#include +#include +#include + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) + +#define NEXT_CHAR HTGetCharacter() + +/* + * Module-wide variables. + */ +static int s; /* Socket for gopher or CSO host */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* the new gopher hypertext */ +static HTStructuredClass targetClass; /* Its action routines */ + +struct _HTStream { + HTStreamClass *isa; /* For form-based CSO gateway - FM */ +}; + +typedef struct _CSOfield_info { /* For form-based CSO gateway - FM */ + struct _CSOfield_info *next; + char *name; + char *attributes; + char *description; + int id; + int lookup; + int indexed; + int url; + int max_size; + int defreturn; + int explicit_return; + int reserved; + int gpublic; + char name_buf[16]; /* Avoid malloc if we can */ + char desc_buf[32]; /* Avoid malloc if we can */ + char attr_buf[80]; /* Avoid malloc if we can */ +} CSOfield_info; + +static CSOfield_info *CSOfields = NULL; /* For form-based CSO gateway - FM */ + +typedef struct _CSOformgen_context { /* For form-based CSO gateway - FM */ + const char *host; + const char *seek; + CSOfield_info *fld; + int port; + int cur_line; + int cur_off; + int rep_line; + int rep_off; + int public_override; + int field_select; +} CSOformgen_context; + +/* Matrix of allowed characters in filenames + * ========================================= + */ +static BOOL acceptable_html[256]; +static BOOL acceptable_file[256]; +static BOOL acceptable_inited = NO; + +static void init_acceptable(void) +{ + unsigned int i; + const char *good = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$"; + + for (i = 0; i < 256; i++) { + acceptable_html[i] = NO; + acceptable_file[i] = NO; + } + for (; *good; good++) { + acceptable_html[(unsigned int) *good] = YES; + acceptable_file[(unsigned int) *good] = YES; + } + for (good = ";?=#"; *good; ++good) { + acceptable_html[(unsigned int) *good] = YES; + } + acceptable_inited = YES; +} + +/* Decode one hex character + * ======================== + */ +static const char hex[17] = "0123456789abcdef"; + +static char from_hex(int c) +{ + return (char) ((c >= '0') && (c <= '9') ? c - '0' + : (c >= 'A') && (c <= 'F') ? c - 'A' + 10 + : (c >= 'a') && (c <= 'f') ? c - 'a' + 10 + : 0); +} + +/* Paste in an Anchor + * ================== + * + * The title of the destination is set, as there is no way + * of knowing what the title is when we arrive. + * + * On entry, + * HT is in append mode. + * text points to the text to be put into the file, 0 terminated. + * addr points to the hypertext reference address 0 terminated. + */ +BOOLEAN HT_Is_Gopher_URL = FALSE; + +static void write_anchor(const char *text, const char *addr) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = 0; + present[HTML_A_HREF] = YES; + ((const char **) value)[HTML_A_HREF] = addr; + present[HTML_A_TITLE] = YES; + ((const char **) value)[HTML_A_TITLE] = text; + + CTRACE((tfp, "HTGopher: adding URL: %s\n", addr)); + + HT_Is_Gopher_URL = TRUE; /* tell HTML.c that this is a Gopher URL */ + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + + PUTS(text); + END(HTML_A); +} + +/* Parse a Gopher Menu document + * ============================ + */ +static void parse_menu(const char *arg GCC_UNUSED, + HTParentAnchor *anAnchor) +{ + char gtype; + char this_type; + int ich; + char line[BIG]; + char *name = NULL, *selector = NULL; /* Gopher menu fields */ + char *host = NULL; + char *port; + char *p = line; + const char *title; + int bytes = 0; + int BytesReported = 0; + char buffer[128]; + BOOL *valid_chars; + +#define TAB '\t' +#define HEX_ESCAPE '%' + + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + PUTC('\n'); /* newline after HTML_PRE forces split-line */ + this_type = GOPHER_ERROR; + while ((ich = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTGopher: Interrupted in HTGetCharacter, apparently.\n")); + goto end_html; + } + + if ((char) ich != LF) { + const char *ss = NULL; + + /* + * Help the -source output to look like the HTML equivalent of the + * Gopher menu. + */ + if (dump_output_immediately + && HTOutputFormat == WWW_DUMP) { + if (ich == '<') { + ss = "<"; + } else if (ich == '>') { + ss = ">"; + } else if (ich == '&') { + ss = "&"; + } + } + if (ss != NULL) { + if ((p + 5) < &line[BIG - 1]) { + while (*ss != '\0') { + *p++ = *ss++; + } + } + } else { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } + + } else { + *p++ = '\0'; /* Terminate line */ + bytes += (int) (p - line); /* add size */ + p = line; /* Scan it to parse it */ + port = 0; /* Flag "not parsed" */ + CTRACE((tfp, "HTGopher: Menu item: %s\n", line)); + gtype = *p++; + + if (bytes > BytesReported + 1024) { + sprintf(buffer, TRANSFERRED_X_BYTES, bytes); + HTProgress(buffer); + BytesReported = bytes; + } + + /* Break on line with a dot by itself */ + if ((gtype == '.') && ((*p == '\r') || (*p == 0))) + break; + + if (gtype && *p) { + name = p; + selector = StrChr(name, TAB); + if (selector) { + *selector++ = '\0'; /* Terminate name */ + /* + * Gopher+ Type=0+ objects can be binary, and will + * have 9 or 5 beginning their selector. Make sure + * we don't trash the terminal by treating them as + * text. - FM + */ + if (gtype == GOPHER_TEXT && (*selector == GOPHER_BINARY || + *selector == GOPHER_PCBINARY)) + gtype = *selector; + host = StrChr(selector, TAB); + if (host) { + *host++ = '\0'; /* Terminate selector */ + port = StrChr(host, TAB); + if (port) { + char *junk; + + port[0] = ':'; /* delimit host a la W3 */ + junk = StrChr(port, TAB); + if (junk) + *junk = '\0'; /* Chop port */ + if ((port[1] == '0') && (!port[2])) + port[0] = '\0'; /* 0 means none */ + } /* no port */ + } /* host ok */ + } /* selector ok */ + } + /* gtype and name ok */ + /* Nameless files are a separator line */ + if (name != NULL && gtype == GOPHER_TEXT) { + int i = (int) strlen(name) - 1; + + while (name[i] == ' ' && i >= 0) + name[i--] = '\0'; + if (i < 0) + gtype = GOPHER_INFO; + } + + if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */ + PUTS("(HTML) "); + write_anchor(name, selector); + + } else if (gtype == GOPHER_INFO) { + /* Information or separator line */ + PUTS(" "); + PUTS(name); + + } else if (port && /* Other types need port */ + (gtype != GOPHER_DUPLICATE || + this_type != GOPHER_ERROR)) { + char *address = 0; + const char *format = *selector ? "%s//%s@%s/" : "%s//%s/"; + + if (gtype == GOPHER_TELNET) { + PUTS(" (TEL) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TELNET_URL, selector, host); + } else if (gtype == GOPHER_TN3270) { + PUTS("(3270) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TN3270_URL, selector, host); + } else { /* If parsed ok */ + char *r; + + switch (gtype) { + case GOPHER_TEXT: + PUTS("(FILE) "); + break; + case GOPHER_MENU: + PUTS(" (DIR) "); + break; + case GOPHER_DUPLICATE: + PUTS(" (+++) "); + break; + case GOPHER_CSO: + PUTS(" (CSO) "); + break; + case GOPHER_PCBINARY: + PUTS(" (BIN) "); + break; + case GOPHER_UUENCODED: + PUTS(" (UUE) "); + break; + case GOPHER_INDEX: + PUTS(" (?) "); + break; + case GOPHER_BINARY: + PUTS(" (BIN) "); + break; + case GOPHER_GIF: + case GOPHER_IMAGE: + case GOPHER_PLUS_IMAGE: + PUTS(" (IMG) "); + break; + case GOPHER_SOUND: + case GOPHER_PLUS_SOUND: + PUTS(" (SND) "); + break; + case GOPHER_MACBINHEX: + PUTS(" (HQX) "); + break; + case GOPHER_HTML: + case GOPHER_CHTML: + PUTS("(HTML) "); + break; + case 'm': + PUTS("(MIME) "); + break; + case GOPHER_PLUS_MOVIE: + PUTS(" (MOV) "); + break; + case GOPHER_PLUS_PDF: + PUTS(" (PDF) "); + break; + default: + PUTS("(UNKN) "); + break; + } + + if (gtype != GOPHER_DUPLICATE) + this_type = gtype; + + HTSprintf0(&address, "//%s/%c", host, this_type); + if (gtype == GOPHER_HTML) { + valid_chars = acceptable_html; + if (*selector == '/') + ++selector; + } else { + valid_chars = acceptable_file; + } + + for (r = selector; *r; r++) { /* Encode selector string */ + if (valid_chars[UCH(*r)]) { + HTSprintf(&address, "%c", *r); + } else { + HTSprintf(&address, "%c%c%c", + HEX_ESCAPE, /* Means hex coming */ + hex[(TOASCII(*r)) >> 4], + hex[(TOASCII(*r)) & 15]); + } + } + } + /* Error response from Gopher doesn't deserve to + be a hyperlink. */ + if (strcmp(address, "gopher://error.host:1/0")) + write_anchor(name, address); + else + PUTS(name); + FREE(address); + } else { /* parse error */ + CTRACE((tfp, "HTGopher: Bad menu item (type %d, port %s).\n", + gtype, NonNull(port))); + PUTS(line); + + } /* parse error */ + + PUTC('\n'); + p = line; /* Start again at beginning of line */ + + } /* if end of line */ + + } /* Loop over characters */ + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + + return; +} + +/* Parse a Gopher CSO document from an ISINDEX query. + * ================================================== + * + * Accepts an open socket to a CSO server waiting to send us + * data and puts it on the screen in a reasonable manner. + * + * Perhaps this data can be automatically linked to some + * other source as well??? + * + * Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu + * on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret, + * secret@dxcern.cern.ch . + */ +static void parse_cso(const char *arg, + HTParentAnchor *anAnchor) +{ + int ich; + char line[BIG]; + char *p = line; + char *first_colon, *second_colon, last_char = '\0'; + const char *title; + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_CSO_SEARCH_RESULTS); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(GOPHER_SEARCH_RESULTS); + } + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + /* + * Start grabbing chars from the network. + */ + while ((ich = NEXT_CHAR) != EOF) { + if ((char) ich != LF) { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } else { + *p = '\0'; /* Terminate line */ + p = line; /* Scan it to parse it */ + /* + * OK we now have a line in 'p'. Lets parse it and print it. + */ + + /* + * Break on line that begins with a 2. It's the end of data. + */ + if (*p == '2') + break; + + /* + * Lines beginning with 5 are errors. Print them and quit. + */ + if (*p == '5') { + START(HTML_H2); + PUTS(p + 4); + END(HTML_H2); + break; + } + + if (*p == '-') { + /* + * Data lines look like -200:#: + * where # is the search result number and can be multiple + * digits (infinite?). + * Find the second colon and check the digit to the left of it + * to see if they are different. If they are then a different + * person is starting. Make this line an

. + */ + + /* + * Find the second_colon. + */ + second_colon = NULL; + first_colon = StrChr(p, ':'); + if (first_colon != NULL) { + second_colon = StrChr(first_colon + 1, ':'); + } + + if (second_colon != NULL) { /* error check */ + + if (*(second_colon - 1) != last_char) + /* print separator */ + { + END(HTML_PRE); + START(HTML_H2); + } + + /* + * Right now the record appears with the alias (first line) + * as the header and the rest as
 text.
+		     *
+		     * It might look better with the name as the header and the
+		     * rest as a 
    with
  • tags. I'm not sure whether the + * name field comes in any special order or if its even + * required in a record, so for now the first line is the + * header no matter what it is (it's almost always the + * alias). + * + * A
    with the first line as the
    and the rest as + * some form of
    might good also? + */ + + /* + * Print data. + */ + PUTS(second_colon + 1); + PUTC('\n'); + + if (*(second_colon - 1) != last_char) + /* end separator */ + { + END(HTML_H2); + START(HTML_PRE); + } + + /* + * Save the char before the second colon for comparison on + * the next pass. + */ + last_char = *(second_colon - 1); + + } /* end if second_colon */ + } /* end if *p == '-' */ + } /* if end of line */ + + } /* Loop over characters */ + + /* end the text block */ + PUTC('\n'); + END(HTML_PRE); + PUTC('\n'); + FREE_TARGET; + + return; /* all done */ +} /* end of procedure */ + +/* Display a Gopher CSO ISINDEX cover page. + * ======================================== + */ +static void display_cso(const char *arg, + HTParentAnchor *anAnchor) +{ + const char *title; + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_CSO_INDEX); + END(HTML_TITLE); + PUTC('\n'); + START(HTML_ISINDEX); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(INDEX_SEGMENT); + } + END(HTML_H1); + PUTS(GOPHER_CSO_INDEX_SUBTITLE); + START(HTML_P); + PUTS(GOPHER_CSO_SOLICIT_KEYWORDS); + START(HTML_P); + PUTS(SEGMENT_KEYWORDS_WILL); + PUTS(SEGMENT_PERSONS_DB_NAME); + + if (!HTAnchor_title(anAnchor)) + HTAnchor_setTitle(anAnchor, arg); + + FREE_TARGET; + return; +} + +/* Display a Gopher Index document. + * ================================ + */ +static void display_index(const char *arg, + HTParentAnchor *anAnchor) +{ + const char *title; + + START(HTML_HEAD); + PUTC('\n'); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_INDEX_TITLE); + END(HTML_TITLE); + PUTC('\n'); + START(HTML_ISINDEX); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(INDEX_SEGMENT); + } + END(HTML_H1); + PUTS(GOPHER_INDEX_SUBTITLE); + START(HTML_P); + PUTS(GOPHER_SOLICIT_KEYWORDS); + + if (!HTAnchor_title(anAnchor)) + HTAnchor_setTitle(anAnchor, arg); + + FREE_TARGET; + return; +} + +/* De-escape a selector into a command. + * ==================================== + * + * The % hex escapes are converted. Otherwise, the string is copied. + */ +static void de_escape(char *command, const char *selector) +{ + const char *p = selector; + char *q; + + if (command == NULL) + outofmem(__FILE__, "HTLoadGopher"); + + q = command; + while (*p) { /* Decode hex */ + if (*p == HEX_ESCAPE) { + char c; + unsigned int b; + + p++; + c = *p++; + b = UCH(from_hex(c)); + c = *p++; + if (!c) + break; /* Odd number of chars! */ + *q++ = (char) FROMASCII((b << 4) + UCH(from_hex(c))); + } else { + *q++ = *p++; /* Record */ + } + } + *q = '\0'; /* Terminate command */ +} + +/* Free the CSOfields structures. - FM + * =================================== + */ +static void free_CSOfields(void) +{ + CSOfield_info *cur = CSOfields; + CSOfield_info *prev; + + while (cur) { + if (cur->name != cur->name_buf) + FREE(cur->name); + if (cur->attributes != cur->attr_buf) + FREE(cur->attributes); + if (cur->description != cur->desc_buf) + FREE(cur->description); + prev = cur; + cur = cur->next; + FREE(prev); + } + + return; +} + +/* Interpret CSO/PH form template keys. - FM + * ========================================= + */ +static void interpret_cso_key(const char *key, + char *buf, + size_t bufsize, + int *length, + CSOformgen_context * ctx, + HTStream *Target) +{ + CSOfield_info *fld; + + if ((fld = ctx->fld) != 0) { + /* + * Most substitutions only recognized inside of loops. + */ + int error = 0; + + if (0 == StrNCmp(key, "$(FID)", 6)) { + sprintf(buf, "%d", fld->id); + } else if (0 == StrNCmp(key, "$(FDESC)", 8)) { + sprintf(buf, "%.2046s", fld->description); + } else if (0 == StrNCmp(key, "$(FDEF)", 7)) { + strcpy(buf, fld->defreturn ? " checked" : ""); + } else if (0 == StrNCmp(key, "$(FNDX)", 7)) { + strcpy(buf, fld->indexed ? "*" : ""); + } else if (0 == StrNCmp(key, "$(FSIZE)", 8)) { + sprintf(buf, " size=%d maxlength=%d", + fld->max_size > 55 ? 55 : fld->max_size, + fld->max_size); + } else if (0 == StrNCmp(key, "$(FSIZE2)", 9)) { + sprintf(buf, " maxlength=%d", fld->max_size); + } else { + error = 1; + } + if (!error) { + *length = (int) strlen(buf); + return; + } + } + buf[0] = '\0'; + if (0 == StrNCmp(key, "$(NEXTFLD)", 10)) { + if (!ctx->fld) + fld = CSOfields; + else + fld = ctx->fld->next; + switch (ctx->field_select) { + case 0: + /* + * 'Query' fields, public and lookup attributes. + */ + for (; fld; fld = fld->next) + if (fld->gpublic && (fld->lookup == 1)) + break; + break; + case 1: + /* + * 'Query' fields, accept lookup attribute. + */ + for (; fld; fld = fld->next) + if (fld->lookup == 1) + break; + break; + case 2: + /* + * 'Return' fields, public only. + */ + for (; fld; fld = fld->next) + if (fld->gpublic) + break; + break; + case 3: + /* + * All fields. + */ + break; + } + if (fld) { + ctx->cur_line = ctx->rep_line; + ctx->cur_off = ctx->rep_off; + } + ctx->fld = fld; + + } else if ((0 == StrNCmp(key, "$(QFIELDS)", 10)) || + (0 == StrNCmp(key, "$(RFIELDS)", 10))) { + /* + * Begin iteration sequence. + */ + ctx->rep_line = ctx->cur_line; + ctx->rep_off = ctx->cur_off; + ctx->fld = (CSOfield_info *) 0; + ctx->seek = "$(NEXTFLD)"; + ctx->field_select = (key[2] == 'Q') ? 0 : 2; + if (ctx->public_override) + ctx->field_select++; + + } else if (0 == StrNCmp(key, "$(NAMEFLD)", 10)) { + /* + * Special, locate name field. Flag lookup so QFIELDS will skip it. + */ + for (fld = CSOfields; fld; fld = fld->next) + if (strcmp(fld->name, "name") == 0 || + strcmp(fld->name, "Name") == 0) { + if (fld->lookup) + fld->lookup = 2; + break; + } + ctx->fld = fld; + } else if (0 == StrNCmp(key, "$(HOST)", 7)) { + strcpy(buf, ctx->host); + } else if (0 == StrNCmp(key, "$(PORT)", 7)) { + sprintf(buf, "%d", ctx->port); + } else { + /* + * No match, dump key to buffer so client sees it for debugging. + */ + size_t out = 0; + + while (*key && (*key != ')')) { + buf[out++] = (*key++); + if (out > bufsize - 2) { + buf[out] = '\0'; + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + out = 0; + } + } + buf[out++] = ')'; + buf[out] = '\0'; + } + *length = (int) strlen(buf); + return; +} + +/* Parse the elements in a CSO/PH fields structure. - FM + * ===================================================== + */ +static int parse_cso_field_info(CSOfield_info *blk) +{ + char *info, *max_spec; + + /* + * Initialize all fields to default values. + */ + blk->indexed = blk->lookup = blk->reserved = blk->max_size = blk->url = 0; + blk->defreturn = blk->explicit_return = blk->gpublic = 0; + + /* + * Search for keywords in info string and set values. Attributes are + * converted to all lower-case for comparison. + */ + info = blk->attributes; + LYLowerCase(info); + if (strstr(info, "indexed ")) + blk->indexed = 1; + if (strstr(info, "default ")) + blk->defreturn = 1; + if (strstr(info, "public ")) + blk->gpublic = 1; + if (strstr(info, "lookup ")) + blk->lookup = 1; + if (strstr(info, "url ")) { + blk->url = 1; + blk->defreturn = 1; + } + max_spec = strstr(info, "max "); + if (max_spec) { + sscanf(&max_spec[4], "%d", &blk->max_size); + } else { + blk->max_size = 32; + } + + return 0; +} + +/* Parse a reply from a CSO/PH fields request. - FM + * ================================================ + */ +static int parse_cso_fields(char *buf, + size_t size) +{ + int ich; + char *p = buf; + int i, code = 0, prev_code; + size_t alen; + char *indx, *name; + CSOfield_info *last, *newf; + + last = CSOfields = (CSOfield_info *) 0; + prev_code = -2555; + buf[0] = '\0'; + + /* + * Start grabbing chars from the network. + */ + while ((ich = NEXT_CHAR) != EOF) { + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n")); + free_CSOfields(); + buf[0] = '\0'; + return HT_INTERRUPTED; + } + + if ((char) ich != LF) { + *p = (char) ich; /* Put character in buffer */ + if (p < &buf[size - 1]) { + p++; + } + } else { + *p = '\0'; /* Terminate line */ + p = buf; /* Scan it to parse it */ + + /* OK we now have a line in 'p' lets parse it. + */ + + /* + * Break on line that begins with a 2. It's the end of data. + */ + if (*p == '2') + break; + + /* + * Lines beginning with 5 are errors. Print them and quit. + */ + if (*p == '5') { + return 5; + } + + if (*p == '-') { + /* + * Data lines look like -200:#: + * where # is the search result number and can be multiple + * digits (infinite?). + */ + + /* + * Check status, ignore any non-success. + */ + if (p[1] != '2') + continue; + + /* + * Parse fields within returned line into status, ndx, name, + * data. + */ + indx = NULL; + name = NULL; + for (i = 0; p[i]; i++) { + if (p[i] == ':') { + p[i] = '\0'; + if (!indx) { + indx = (char *) &p[i + 1]; + code = atoi(indx); + } else if (!name) { + name = (char *) &p[i + 1]; + } else { + i++; + break; + } + } + } + /* + * Add data to field structure. + */ + if (name) { + if (code == prev_code) { + /* + * Remaining data are description. Save in current + * info block. + */ + if (last != NULL) { + alen = strlen((char *) &p[i]) + 1; + if (alen > sizeof(last->desc_buf)) { + if (last->description != last->desc_buf) + FREE(last->description); + if (!(last->description = (char *) malloc(alen))) { + outofmem(__FILE__, "HTLoadCSO"); + } + } + strcpy(last->description, (char *) &p[i]); + } + } else { + /* + * Initialize new block, append to end of list to + * preserve order. + */ + newf = typecalloc(CSOfield_info); + + if (!newf) { + outofmem(__FILE__, "HTLoadCSO"); + } + + if (last) + last->next = newf; + else + CSOfields = newf; + last = newf; + + newf->next = (CSOfield_info *) 0; + newf->name = newf->name_buf; + alen = strlen(name) + 1; + if (alen > sizeof(newf->name_buf)) { + if (!(newf->name = (char *) malloc(alen))) { + outofmem(__FILE__, "HTLoadCSO"); + } + } + strcpy(newf->name, name); + + newf->attributes = newf->attr_buf; + alen = strlen((char *) &p[i]) + 2; + if (alen > sizeof(newf->attr_buf)) { + if (!(newf->attributes = (char *) malloc(alen))) { + outofmem(__FILE__, "HTLoadCSO"); + } + } + strcpy(newf->attributes, (char *) &p[i]); + strcpy((char *) &newf->attributes[alen - 2], " "); + newf->description = newf->desc_buf; + newf->desc_buf[0] = '\0'; + newf->id = atoi(indx); + /* + * Scan for keywords. + */ + parse_cso_field_info(newf); + } + prev_code = code; + } else + break; + } /* end if *p == '-' */ + } /* if end of line */ + + } /* Loop over characters */ + + /* end the text block */ + + if (buf[0] == '\0') { + return -1; /* no response */ + } + buf[0] = '\0'; + return 0; /* all done */ +} /* end of procedure */ + +/* Generate a form for submitting CSO/PH searches. - FM + * ==================================================== + */ +static int generate_cso_form(char *host, + int port, + char *buf, + size_t bufsize, + HTStream *Target) +{ + int i, j, length; + size_t out; + int full_flag = 1; + const char *key; + const char *line; + CSOformgen_context ctx; + static const char *ctemplate[] = + { + "\n\nCSO/PH Query Form for $(HOST)\n\n", + "

    CSO/PH Query Form for $(HOST)

    ", + "To search the database for a name, fill in one or more of the fields", + "in the form below and activate the 'Submit query' button. At least", + "one of the entered fields must be flagged as indexed.", + "
    ", + "[ | ", + " ]", + "

    ", + "
    Search parameters (* indicates indexed field):", + "
    ", + "$(NAMEFLD)
    \n
    $(FDESC)$(FNDX)", + "
    Last: ", + "
    First: ", + "$(QFIELDS)
    $(FDESC)$(FNDX)", + "
    \n$(NEXTFLD)", + "
    ", + "
    \n

    ", + "
    Output format:", + "
    Returned data option:
    ", + "$(RFIELDS) $(FDESC)
    ", + "$(NEXTFLD) ", + "

    \n\n", + (char *) 0 + }; + + memset(&ctx, 0, sizeof(ctx)); + ctx.host = host; + ctx.seek = (char *) 0; + ctx.port = port; + ctx.fld = (CSOfield_info *) 0; + ctx.public_override = full_flag; + /* + * Parse the strings in the template array to produce HTML document to send + * to client. First line is skipped for 'full' lists. + */ + out = 0; + buf[out] = '\0'; + for (i = 0; ctemplate[i]; i++) { + /* + * Search the current string for substitution, flagged by $( + */ + for (line = ctemplate[i], j = 0; line[j]; j++) { + if ((line[j] == '$') && (line[j + 1] == '(')) { + /* + * Command detected, flush output buffer and find closing ')' + * that delimits the command. + */ + buf[out] = '\0'; + if (out > 0) + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + for (key = &line[j]; line[j + 1] && (line[j] != ')'); j++) { + ; + } + /* + * Save context, interpret command and restore updated context. + */ + ctx.cur_line = i; + ctx.cur_off = j; + interpret_cso_key(key, buf, bufsize, &length, &ctx, Target); + i = ctx.cur_line; + j = ctx.cur_off; + line = ctemplate[i]; + out = (size_t) length; + + if (ctx.seek) { + /* + * Command wants us to skip (forward) to indicated token. + * Start at current position. + */ + size_t slen = strlen(ctx.seek); + + for (; ctemplate[i]; i++) { + for (line = ctemplate[i]; line[j]; j++) { + if (line[j] == '$') + if (0 == StrNCmp(ctx.seek, &line[j], slen)) { + if (j == 0) + j = (int) strlen(ctemplate[--i]) - 1; + else + --j; + line = ctemplate[i]; + ctx.seek = (char *) 0; + break; + } + } + if (!ctx.seek) + break; + j = 0; + } + if (ctx.seek) { + char *temp = 0; + + HTSprintf0(&temp, GOPHER_CSO_SEEK_FAILED, ctx.seek); + (*Target->isa->put_block) (Target, temp, (int) strlen(temp)); + FREE(temp); + } + } + } else { + /* + * Non-command text, add to output buffer. + */ + buf[out++] = line[j]; + if (out > (bufsize - 3)) { + buf[out] = '\0'; + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + out = 0; + } + } + } + buf[out++] = '\n'; + buf[out] = '\0'; + } + if (out > 0) + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + + return 0; +} + +/* Generate a results report for CSO/PH form-based searches. - FM + * ============================================================== + */ +static int generate_cso_report(HTStream *Target) +{ + int ich; + char line[BIG]; + char *buf = 0; + char *p = line, *href = NULL; + int i, prev_ndx, ndx; + char *rcode, *ndx_str, *fname, *fvalue, *l; + CSOfield_info *fld; + BOOL stop = FALSE; + + /* + * Read lines until non-negative status. + */ + prev_ndx = -100; + /* + * Start grabbing chars from the network. + */ + while (!stop && (ich = NEXT_CHAR) != EOF) { + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + goto end_CSOreport; + } + + if ((char) ich != LF) { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) { + p++; + } + } else { + *p = '\0'; /* Terminate line */ + /* + * OK we now have a line. Load it as 'p' and parse it. + */ + p = line; + if (p[0] != '-' && p[0] != '1') { + stop = TRUE; + } + rcode = (p[0] == '-') ? &p[1] : p; + ndx_str = fname = NULL; + for (i = 0; p[i] != '\0'; i++) { + if (p[i] == ':') { + p[i] = '\0'; + fname = &p[i + 1]; + if (ndx_str) { + break; + } + ndx_str = fname; + } + } + if (ndx_str) { + ndx = atoi(ndx_str); + if (prev_ndx != ndx) { + if (prev_ndx != -100) { + HTSprintf0(&buf, "
    \n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + if (ndx == 0) { + HTSprintf0(&buf, + "
    Information/status
    \n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } else { + HTSprintf0(&buf, + "
    Entry %d:
    \n", ndx); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + prev_ndx = ndx; + } + } else { + HTSprintf0(&buf, "
    %s\n", rcode); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + continue; + } + if ((*rcode >= '2') && (*rcode <= '5') && (fname != ndx_str)) { + while (*fname == ' ') { + fname++; /* trim leading spaces */ + } + for (fvalue = fname; *fvalue; fvalue++) { + if (*fvalue == ':') { + *fvalue++ = '\0'; + i = (int) strlen(fname) - 1; + while (i >= 0 && fname[i] == ' ') { + fname[i--] = '\0'; /* trim trailing */ + } + break; + } + } + if (fvalue) { + while (*fvalue == ' ') { + fvalue++; /* trim leading spaces */ + } + } + if (*fname) { + for (fld = CSOfields; fld; fld = fld->next) { + if (!strcmp(fld->name, fname)) { + if (fld->description) { + fname = fld->description; + } + break; + } + } + if (fld && fld->url) { + HTSprintf0(&buf, + "
    %s
    %s\n", + fname, fvalue, fvalue); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } else { + HTSprintf0(&buf, "
    %s
    ", fname); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + buf[0] = '\0'; + l = fvalue; + while (*l) { + if (*l == '<') { + StrAllocCat(buf, "<"); + l++; + } else if (*l == '>') { + StrAllocCat(buf, ">"); + l++; + } else if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) && + StrNCmp(l, "snews://", 8) && + StrNCmp(l, "nntp://", 7) && + StrNCmp(l, "snewspost:", 10) && + StrNCmp(l, "snewsreply:", 11) && + StrNCmp(l, "newspost:", 9) && + StrNCmp(l, "newsreply:", 10) && + StrNCmp(l, "ftp://", 6) && + StrNCmp(l, "file:/", 6) && + StrNCmp(l, "finger://", 9) && + StrNCmp(l, "http://", 7) && + StrNCmp(l, "https://", 8) && + StrNCmp(l, "wais://", 7) && + StrNCmp(l, STR_MAILTO_URL, + LEN_MAILTO_URL) && + StrNCmp(l, "cso://", 6) && + StrNCmp(l, "gopher://", 9)) { + HTSprintf(&buf, "%c", *l++); + } else { + StrAllocCat(buf, ")\"")); + StrAllocCat(buf, "\">"); + while (*l && !StrChr(" \r\n\t,>)\"", *l)) { + HTSprintf(&buf, "%c", *l++); + } + StrAllocCat(buf, ""); + FREE(href); + } + } + StrAllocCat(buf, "\n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + } else { + HTSprintf0(&buf, "
    "); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + buf[0] = '\0'; + l = fvalue; + while (*l) { + if (*l == '<') { + StrAllocCat(buf, "<"); + l++; + } else if (*l == '>') { + StrAllocCat(buf, ">"); + l++; + } else if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) && + StrNCmp(l, "snews://", 8) && + StrNCmp(l, "nntp://", 7) && + StrNCmp(l, "snewspost:", 10) && + StrNCmp(l, "snewsreply:", 11) && + StrNCmp(l, "newspost:", 9) && + StrNCmp(l, "newsreply:", 10) && + StrNCmp(l, "ftp://", 6) && + StrNCmp(l, "file:/", 6) && + StrNCmp(l, "finger://", 9) && + StrNCmp(l, "http://", 7) && + StrNCmp(l, "https://", 8) && + StrNCmp(l, "wais://", 7) && + StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) && + StrNCmp(l, "cso://", 6) && + StrNCmp(l, "gopher://", 9)) { + HTSprintf(&buf, "%c", *l++); + } else { + StrAllocCat(buf, ")\"")); + StrAllocCat(buf, "\">"); + while (*l && !StrChr(" \r\n\t,>)\"", *l)) { + HTSprintf(&buf, "%c", *l++); + } + StrAllocCat(buf, ""); + FREE(href); + } + } + StrAllocCat(buf, "\n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + } else { + HTSprintf0(&buf, "
    %s\n", fname); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + } + } + end_CSOreport: + if (prev_ndx != -100) { + HTSprintf0(&buf, "
    \n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + } + FREE(buf); + return 0; +} + +/* CSO/PH form-based search gateway - FM HTLoadCSO + * ===================================== + */ +static int HTLoadCSO(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + static const char end_form[] = "\n\n"; + char *host, *cp, *data; + int port = CSO_PORT; + int status; /* tcp return */ + bstring *command = NULL; + bstring *content = NULL; + int len, i, j, start, finish, flen, ndx; + int return_type, has_indexed; + CSOfield_info *fld; + char buf[2048]; + HTFormat format_in = WWW_HTML; + HTStream *Target = NULL; + + if (!acceptable_inited) + init_acceptable(); + + if (!arg) + return -3; /* Bad if no name specified */ + if (!*arg) + return -2; /* Bad if name had zero length */ + CTRACE((tfp, "HTLoadCSO: Looking for %s\n", arg)); + + /* + * Set up a socket to the server for the data. + */ + status = HTDoConnect(arg, "cso", CSO_PORT, &s); + if (status == HT_INTERRUPTED) { + /* + * Interrupt cleanly. + */ + CTRACE((tfp, + "HTLoadCSO: Interrupted on connect; recovering cleanly.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + return HT_NOT_LOADED; + } + if (status < 0) { + CTRACE((tfp, "HTLoadCSO: Unable to connect to remote host for `%s'.\n", + arg)); + return HTInetStatus("connect"); + } + + HTInitInput(s); /* Set up input buffering */ + + HTBprintf(&command, "fields%c%c", CR, LF); + if (TRACE) { + CTRACE((tfp, "HTLoadCSO: Connected, writing command `")); + trace_bstring(command); + CTRACE((tfp, "' to socket %d\n", s)); + } + _HTProgress(GOPHER_SENDING_CSO_REQUEST); + status = (int) NETWRITE(s, BStrData(command), BStrLen(command)); + BStrFree(command); + if (status < 0) { + CTRACE((tfp, "HTLoadCSO: Unable to send command.\n")); + return HTInetStatus("send"); + } + _HTProgress(GOPHER_SENT_CSO_REQUEST); + + /* + * Now read the data from the socket. + */ + status = parse_cso_fields(buf, sizeof(buf)); + if (status) { + NETCLOSE(s); + if (status == HT_INTERRUPTED) { + _HTProgress(CONNECTION_INTERRUPTED); + } else if (buf[0] != '\0') { + HTAlert(buf); + } else { + HTAlert(FAILED_NO_RESPONSE); + } + return HT_NOT_LOADED; + } + Target = HTStreamStack(format_in, + format_out, + sink, anAnchor); + if (Target == NULL) { + char *temp = 0; + + HTSprintf0(&temp, CANNOT_CONVERT_I_TO_O, + HTAtom_name(format_in), HTAtom_name(format_out)); + HTAlert(temp); + FREE(temp); + NETCLOSE(s); + return HT_NOT_LOADED; + } + host = HTParse(arg, "", PARSE_HOST); + if ((cp = HTParsePort(host, &port)) != NULL) { + if (port == CSO_PORT) { + *cp = '\0'; + } + } + anAnchor->safe = TRUE; + if (isBEmpty(anAnchor->post_data)) { + generate_cso_form(host, port, buf, sizeof(buf), Target); + (*Target->isa->_free) (Target); + FREE(host); + NETCLOSE(s); + free_CSOfields(); + return HT_LOADED; + } + + HTBprintf(&command, + "\n\nCSO/PH Results on %s\n\n\n", + host); + (*Target->isa->put_block) (Target, BStrData(command), BStrLen(command)); + BStrFree(command); + FREE(host); + + BStrCopy(content, anAnchor->post_data); + assert(content != NULL); + + if (BStrData(content)[BStrLen(content) - 1] != '&') + BStrCat0(content, "&"); + + data = BStrData(content); + len = BStrLen(content); + for (i = 0; i < len; i++) { + if (data[i] == '+') { + data[i] = ' '; + } + } + + data = BStrData(content); + HTUnEscape(data); /* FIXME: could it have embedded null? */ + len = BStrLen(content); + + return_type = 0; + has_indexed = 0; + start = 0; + for (i = 0; i < len; i++) { + if (!data[i] || data[i] == '&') { + /* + * Value parsed. Unescape characters and look for first '=' to + * delimit field name from value. + */ + flen = i - start; + finish = start + flen; + data[finish] = '\0'; + for (j = start; j < finish; j++) { + if (data[j] == '=') { + /* + * data[start..j-1] is field name, + * [j+1..finish-1] is value. + */ + if ((data[start + 1] == '_') && + ((data[start] == 'r') || (data[start] == 'q'))) { + /* + * Decode fields number and lookup field info. + */ + sscanf(&data[start + 2], "%d=", &ndx); + for (fld = CSOfields; fld; fld = fld->next) { + if (ndx == fld->id) { + if ((j + 1) >= finish) + break; /* ignore nulls */ + if (data[start] == 'q') { + /* + * Append field to query line. + */ + if (fld->lookup) { + if (fld->indexed) + has_indexed = 1; + if (isBEmpty(command)) { + BStrCopy0(command, "query "); + } else { + BStrCat0(command, " "); + } + HTBprintf(&command, "%s=\"%s\"", + fld->name, &data[j + 1]); + } else { + strcpy(buf, + "Warning: non-lookup field ignored
    \n"); + (*Target->isa->put_block) (Target, + buf, + (int) + strlen(buf)); + } + } else if (data[start] == 'r') { + fld->explicit_return = 1; + } + break; + } + } + } else if (!StrNCmp(&data[start], "return=", 7)) { + if (!strcmp(&data[start + 7], "all")) { + return_type = 1; + } else if (!strcmp(&data[start + 7], "selected")) { + return_type = 2; + } + } + } + } + start = i + 1; + } + } + BStrFree(content); + if (isBEmpty(command) || !has_indexed) { + NETCLOSE(s); + strcpy(buf, + "Error: At least one indexed field value must be specified!\n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + strcpy(buf, "\n\n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + (*Target->isa->_free) (Target); + free_CSOfields(); + BStrFree(command); + return HT_LOADED; + } + /* + * Append return fields. + */ + if (return_type == 1) { + BStrCat0(command, " return all"); + } else if (return_type == 2) { + BStrCat0(command, " return"); + for (fld = CSOfields; fld; fld = fld->next) { + if (fld->explicit_return) { + HTBprintf(&command, " %s", fld->name); + } + } + } + HTBprintf(&command, "%c%c", CR, LF); + strcpy(buf, "

    \nCSO/PH command: "); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + (*Target->isa->put_block) (Target, BStrData(command), BStrLen(command)); + strcpy(buf, "

    \n"); + (*Target->isa->put_block) (Target, buf, (int) strlen(buf)); + if (TRACE) { + CTRACE((tfp, "HTLoadCSO: Writing command `")); + trace_bstring(command); + CTRACE((tfp, "' to socket %d\n", s)); + } + status = (int) NETWRITE(s, BStrData(command), BStrLen(command)); + BStrFree(command); + if (status < 0) { + CTRACE((tfp, "HTLoadCSO: Unable to send command.\n")); + free_CSOfields(); + return HTInetStatus("send"); + } + generate_cso_report(Target); + NETCLOSE(s); + (*Target->isa->put_block) (Target, end_form, (int) sizeof(end_form) - 1); + (*Target->isa->_free) (Target); + FREE(host); + free_CSOfields(); + return HT_LOADED; +} + +static char *link_to_URL(const char *arg) +{ + char *result; + char *next; + char *temp = 0; + + StrAllocCopy(temp, arg); + HTUnEscape(temp); + result = temp; + + /* skip past method://host */ + if ((next = strstr(result, "://")) != 0) { + result = next + 3; + } + if ((next = strchr(result, '/')) != 0) { + result = next + 1; + } + /* check if the selector is the special html one */ + if (!strncmp(result, "hURL:", (size_t) 5)) { + result += 5; + next = result; + result = temp; + while ((*temp++ = *next++) != 0) ; + } else { + FREE(temp); + result = 0; + } + return result; +} + +/* Load by name. HTLoadGopher + * ============= + * + */ +static int HTLoadGopher(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + char *hURL; + char *command; /* The whole command */ + int status; /* tcp return */ + char gtype; /* Gopher Node type */ + char *selector; /* Selector string */ + + if (!acceptable_inited) + init_acceptable(); + + if (!arg) + return -3; /* Bad if no name specified */ + if (!*arg) + return -2; /* Bad if name had zero length */ + CTRACE((tfp, "HTGopher: Looking for %s\n", arg)); + + /* + * If it's a port 105 GOPHER_CSO gtype with no ISINDEX token ('?'), use the + * form-based CSO gateway (otherwise, return an ISINDEX cover page or do + * the ISINDEX search). - FM + */ + { + size_t len; + + if ((len = strlen(arg)) > 5) { + if (0 == strcmp((const char *) &arg[len - 6], ":105/2")) { + /* Use CSO gateway. */ + CTRACE((tfp, "HTGopher: Passing to CSO/PH gateway.\n")); + return HTLoadCSO(arg, anAnchor, format_out, sink); + } + } + } + + /* + * If it's a port 79/0[/...] URL, use the finger gateway. - FM + */ + if (strstr(arg, ":79/0") != NULL) { +#ifndef DISABLE_FINGER + CTRACE((tfp, "HTGopher: Passing to finger gateway.\n")); + return HTLoadFinger(arg, anAnchor, format_out, sink); +#else /* finger is disabled */ + HTAlert(COULD_NOT_ACCESS_DOCUMENT); + return HT_NOT_LOADED; +#endif /* DISABLE_FINGER */ + } + + /* + * Get entity type, and selector string. + */ + { + char *p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); + + gtype = '1'; /* Default = menu */ + selector = p1; + if ((*selector++ == '/') && (*selector)) { /* Skip first slash */ + gtype = *selector++; /* Pick up gtype */ + } + if (gtype == GOPHER_INDEX) { + char *query; + + /* + * Search is allowed. + */ + HTAnchor_setIndex(anAnchor, anAnchor->address); + query = StrChr(selector, '?'); /* Look for search string */ + if (!query || !query[1]) { /* No search required */ + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; + display_index(arg, anAnchor); /* Display "cover page" */ + return HT_LOADED; /* Local function only */ + } + *query++ = '\0'; /* Skip '?' */ + command = + (char *) malloc(strlen(selector) + 1 + strlen(query) + 2 + 1); + if (command == NULL) + outofmem(__FILE__, "HTLoadGopher"); + + de_escape(command, selector); /* Bug fix TBL 921208 */ + + strcat(command, "\t"); + + { /* Remove plus signs 921006 */ + char *p; + + for (p = query; *p; p++) { + if (*p == '+') + *p = ' '; + } + } + + de_escape(&command[strlen(command)], query); /* bug fix LJM 940415 */ + } else if (gtype == GOPHER_CSO) { + char *query; + + /* + * Search is allowed. + */ + query = StrChr(selector, '?'); /* Look for search string */ + if (!query || !query[1]) { /* No search required */ + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; + display_cso(arg, anAnchor); /* Display "cover page" */ + return HT_LOADED; /* Local function only */ + } + HTAnchor_setIndex(anAnchor, anAnchor->address); + *query++ = '\0'; /* Skip '?' */ + command = (char *) malloc(strlen("query") + 1 + + strlen(query) + 2 + 1); + if (command == NULL) + outofmem(__FILE__, "HTLoadGopher"); + + de_escape(command, selector); /* Bug fix TBL 921208 */ + + strcpy(command, "query "); + + { /* Remove plus signs 921006 */ + char *p; + + for (p = query; *p; p++) { + if (*p == '+') + *p = ' '; + } + } + de_escape(&command[strlen(command)], query); /* bug fix LJM 940415 */ + + } else { /* Not index */ + command = (char *) malloc(strlen(selector) + 2 + 1); + if (command == NULL) + outofmem(__FILE__, "HTLoadGopher"); + + de_escape(command, selector); + } + FREE(p1); + } + + { + char *p = command + strlen(command); + + *p++ = CR; /* Macros to be correct on Mac */ + *p++ = LF; + *p = '\0'; + } + /* + * Check for link to URL + */ + if ((hURL = link_to_URL(arg)) != 0) { + CTRACE((tfp, "gopher found link to URL '%s'\n", hURL)); + free(hURL); + } + + /* + * Set up a socket to the server for the data. + */ + status = HTDoConnect(arg, "gopher", GOPHER_PORT, &s); + if (status == HT_INTERRUPTED) { + /* + * Interrupt cleanly. + */ + CTRACE((tfp, "HTGopher: Interrupted on connect; recovering cleanly.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + FREE(command); + return HT_NOT_LOADED; + } + if (status < 0) { + CTRACE((tfp, "HTGopher: Unable to connect to remote host for `%s'.\n", + arg)); + FREE(command); + return HTInetStatus("connect"); + } + + HTInitInput(s); /* Set up input buffering */ + + CTRACE((tfp, "HTGopher: Connected, writing command `%s' to socket %d\n", + command, s)); + +#ifdef NOT_ASCII + { + char *p; + + for (p = command; *p; p++) { + *p = TOASCII(*p); + } + } +#endif + + _HTProgress(GOPHER_SENDING_REQUEST); + + status = (int) NETWRITE(s, command, (int) strlen(command)); + FREE(command); + if (status < 0) { + CTRACE((tfp, "HTGopher: Unable to send command.\n")); + return HTInetStatus("send"); + } + + _HTProgress(GOPHER_SENT_REQUEST); + + /* + * Now read the data from the socket. + */ + switch (gtype) { + + case GOPHER_TEXT: + HTParseSocket(WWW_PLAINTEXT, format_out, anAnchor, s, sink); + break; + + case GOPHER_HTML: + case GOPHER_CHTML: + HTParseSocket(WWW_HTML, format_out, anAnchor, s, sink); + break; + + case GOPHER_GIF: + case GOPHER_IMAGE: + case GOPHER_PLUS_IMAGE: + HTParseSocket(HTAtom_for("image/gif"), + format_out, anAnchor, s, sink); + break; + + case GOPHER_MENU: + case GOPHER_INDEX: + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; + parse_menu(arg, anAnchor); + break; + + case GOPHER_CSO: + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; + parse_cso(arg, anAnchor); + break; + + case GOPHER_SOUND: + case GOPHER_PLUS_SOUND: + HTParseSocket(WWW_AUDIO, format_out, anAnchor, s, sink); + break; + + case GOPHER_PLUS_MOVIE: + HTParseSocket(HTAtom_for("video/mpeg"), format_out, anAnchor, s, sink); + break; + + case GOPHER_PLUS_PDF: + HTParseSocket(HTAtom_for("application/pdf"), format_out, anAnchor, + s, sink); + break; + + default: + { + HTAtom *encoding = 0; + const char *desc = 0; + HTFormat format = HTFileFormat(arg, &encoding, &desc); + + /* + * Ignore WWW_BINARY (since that is returned by HTFileFormat when + * it does not have a representation), but otherwise use the + * result. + */ + if (format != WWW_BINARY) { + HTParseSocket(format, format_out, anAnchor, s, sink); + break; + } + } + /* FALL-THRU */ + + case GOPHER_MACBINHEX: + case GOPHER_PCBINARY: + case GOPHER_UUENCODED: + case GOPHER_BINARY: + /* + * Specifying WWW_UNKNOWN forces dump to local disk. + */ + HTParseSocket(WWW_UNKNOWN, format_out, anAnchor, s, sink); + break; + + } /* switch(gtype) */ + + NETCLOSE(s); + return HT_LOADED; +} + +#ifdef GLOBALDEF_IS_MACRO +#define _HTGOPHER_C_1_INIT { "gopher", HTLoadGopher, NULL } +GLOBALDEF(HTProtocol, HTGopher, _HTGOPHER_C_1_INIT); +#define _HTCSO_C_1_INIT { "cso", HTLoadCSO, NULL } +GLOBALDEF(HTProtocol, HTCSO, _HTCSO_C_1_INIT); +#else +GLOBALDEF HTProtocol HTGopher = +{"gopher", HTLoadGopher, NULL}; +GLOBALDEF HTProtocol HTCSO = +{"cso", HTLoadCSO, NULL}; +#endif /* GLOBALDEF_IS_MACRO */ + +#endif /* not DISABLE_GOPHER */ diff --git a/WWW/Library/Implementation/HTGopher.h b/WWW/Library/Implementation/HTGopher.h new file mode 100644 index 0000000..667fc24 --- /dev/null +++ b/WWW/Library/Implementation/HTGopher.h @@ -0,0 +1,29 @@ +/* Gopher protocol module for libwww + GOPHER ACCESS + + HISTORY: + + 8 Jan 92 Adapted from HTTP TBL + + */ + +#ifndef HTGOPHER_H +#define HTGOPHER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTGopher); + +#else + GLOBALREF HTProtocol HTGopher; +#endif /* GLOBALREF_IS_MACRO */ + +#ifdef __cplusplus +} +#endif +#endif /* HTGOPHER_H */ diff --git a/WWW/Library/Implementation/HTGroup.c b/WWW/Library/Implementation/HTGroup.c new file mode 100644 index 0000000..42d55dc --- /dev/null +++ b/WWW/Library/Implementation/HTGroup.c @@ -0,0 +1,758 @@ +/* MODULE HTGroup.c + * GROUP FILE ROUTINES + * + * Contains group file parser and routines to match IP + * address templates and to find out group membership. + * + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * + * + * BUGS: + * + * + * + * GROUP DEFINITION GRAMMAR: + * + * string = "sequence of alphanumeric characters" + * user_name ::= string + * group_name ::= string + * group_ref ::= group_name + * user_def ::= user_name | group_ref + * user_def_list ::= user_def { ',' user_def } + * user_part = user_def | '(' user_def_list ')' + * + * templ = "sequence of alphanumeric characters and '*'s" + * ip_number_mask ::= templ '.' templ '.' templ '.' templ + * domain_name_mask ::= templ { '.' templ } + * address ::= ip_number_mask | domain_name_mask + * address_def ::= address + * address_def_list ::= address_def { ',' address_def } + * address_part = address_def | '(' address_def_list ')' + * + * item ::= [user_part] ['@' address_part] + * item_list ::= item { ',' item } + * group_def ::= item_list + * group_decl ::= group_name ':' group_def + * + */ + +#include + +#include +#include /* Lexical analysor */ +#include /* Implemented here */ + +#include +#include + +/* + * Group file parser + */ + +typedef HTList UserDefList; +typedef HTList AddressDefList; + +typedef struct { + UserDefList *user_def_list; + AddressDefList *address_def_list; +} Item; + +typedef struct { + char *name; + GroupDef *translation; +} Ref; + +static void syntax_error(FILE *fp, const char *msg, + LexItem lex_item) +{ + char buffer[41]; + int cnt = 0; + int ch; + + while ((ch = getc(fp)) != EOF && ch != '\n') + if (cnt < 40) + buffer[cnt++] = (char) ch; + buffer[cnt] = (char) 0; + + CTRACE((tfp, "%s %d before: '%s'\nHTGroup.c: %s (got %s)\n", + "HTGroup.c: Syntax error in rule file at line", + HTlex_line, buffer, msg, lex_verbose(lex_item))); + HTlex_line++; +} + +static AddressDefList *parse_address_part(FILE *fp) +{ + AddressDefList *address_def_list = NULL; + LexItem lex_item; + BOOL only_one = NO; + + lex_item = lex(fp); + if (lex_item == LEX_ALPH_STR || lex_item == LEX_TMPL_STR) + only_one = YES; + else if (lex_item != LEX_OPEN_PAREN || + ((lex_item = lex(fp)) != LEX_ALPH_STR && + lex_item != LEX_TMPL_STR)) { + syntax_error(fp, "Expecting a single address or '(' beginning list", + lex_item); + return NULL; + } + address_def_list = HTList_new(); + + for (;;) { + Ref *ref = typecalloc(Ref); + + if (ref == NULL) + outofmem(__FILE__, "parse_address_part"); + + ref->name = NULL; + ref->translation = NULL; + StrAllocCopy(ref->name, HTlex_buffer); + + HTList_addObject(address_def_list, (void *) ref); + + if (only_one || (lex_item = lex(fp)) != LEX_ITEM_SEP) + break; + /* + * Here lex_item == LEX_ITEM_SEP; after item separator it + * is ok to have one or more newlines (LEX_REC_SEP) and + * they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + + if (lex_item != LEX_ALPH_STR && lex_item != LEX_TMPL_STR) { + syntax_error(fp, "Expecting an address template", lex_item); + HTList_delete(address_def_list); + address_def_list = NULL; + return NULL; + } + } + + if (!only_one && lex_item != LEX_CLOSE_PAREN) { + HTList_delete(address_def_list); + address_def_list = NULL; + syntax_error(fp, "Expecting ')' closing address list", lex_item); + return NULL; + } + return address_def_list; +} + +static UserDefList *parse_user_part(FILE *fp) +{ + UserDefList *user_def_list = NULL; + LexItem lex_item; + BOOL only_one = NO; + + lex_item = lex(fp); + if (lex_item == LEX_ALPH_STR) + only_one = YES; + else if (lex_item != LEX_OPEN_PAREN || + (lex_item = lex(fp)) != LEX_ALPH_STR) { + syntax_error(fp, "Expecting a single name or '(' beginning list", + lex_item); + return NULL; + } + user_def_list = HTList_new(); + + for (;;) { + Ref *ref = typecalloc(Ref); + + if (ref == NULL) + outofmem(__FILE__, "parse_user_part"); + + ref->name = NULL; + ref->translation = NULL; + StrAllocCopy(ref->name, HTlex_buffer); + + HTList_addObject(user_def_list, (void *) ref); + + if (only_one || (lex_item = lex(fp)) != LEX_ITEM_SEP) + break; + /* + * Here lex_item == LEX_ITEM_SEP; after item separator it + * is ok to have one or more newlines (LEX_REC_SEP) and + * they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + + if (lex_item != LEX_ALPH_STR) { + syntax_error(fp, "Expecting user or group name", lex_item); + HTList_delete(user_def_list); + user_def_list = NULL; + return NULL; + } + } + + if (!only_one && lex_item != LEX_CLOSE_PAREN) { + HTList_delete(user_def_list); + user_def_list = NULL; + syntax_error(fp, "Expecting ')' closing user/group list", lex_item); + return NULL; + } + return user_def_list; +} + +static Item *parse_item(FILE *fp) +{ + Item *item = NULL; + UserDefList *user_def_list = NULL; + AddressDefList *address_def_list = NULL; + LexItem lex_item; + + lex_item = lex(fp); + if (lex_item == LEX_ALPH_STR || lex_item == LEX_OPEN_PAREN) { + unlex(lex_item); + user_def_list = parse_user_part(fp); + lex_item = lex(fp); + } + + if (lex_item == LEX_AT_SIGN) { + lex_item = lex(fp); + if (lex_item == LEX_ALPH_STR || lex_item == LEX_TMPL_STR || + lex_item == LEX_OPEN_PAREN) { + unlex(lex_item); + address_def_list = parse_address_part(fp); + } else { + if (user_def_list) { + HTList_delete(user_def_list); /* @@@@ */ + user_def_list = NULL; + } + syntax_error(fp, "Expected address part (single address or list)", + lex_item); + return NULL; + } + } else + unlex(lex_item); + + if (!user_def_list && !address_def_list) { + syntax_error(fp, "Empty item not allowed", lex_item); + return NULL; + } + item = typecalloc(Item); + if (item == NULL) + outofmem(__FILE__, "parse_item"); + + item->user_def_list = user_def_list; + item->address_def_list = address_def_list; + return item; +} + +static ItemList *parse_item_list(FILE *fp) +{ + ItemList *item_list = HTList_new(); + Item *item; + LexItem lex_item; + + for (;;) { + if (!(item = parse_item(fp))) { + HTList_delete(item_list); /* @@@@ */ + item_list = NULL; + return NULL; + } + HTList_addObject(item_list, (void *) item); + lex_item = lex(fp); + if (lex_item != LEX_ITEM_SEP) { + unlex(lex_item); + return item_list; + } + /* + * Here lex_item == LEX_ITEM_SEP; after item separator it + * is ok to have one or more newlines (LEX_REC_SEP) and + * they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + unlex(lex_item); + } +} + +GroupDef *HTAA_parseGroupDef(FILE *fp) +{ + ItemList *item_list = NULL; + GroupDef *group_def = NULL; + LexItem lex_item; + + if (!(item_list = parse_item_list(fp))) { + return NULL; + } + group_def = typecalloc(GroupDef); + if (group_def == NULL) + outofmem(__FILE__, "HTAA_parseGroupDef"); + + group_def->group_name = NULL; + group_def->item_list = item_list; + + if ((lex_item = lex(fp)) != LEX_REC_SEP) { + syntax_error(fp, "Garbage after group definition", lex_item); + } + + return group_def; +} + +#if 0 +static GroupDef *parse_group_decl(FILE *fp) +{ + char *group_name = NULL; + GroupDef *group_def = NULL; + LexItem lex_item; + + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); /* Ignore empty lines */ + + if (lex_item != LEX_ALPH_STR) { + if (lex_item != LEX_EOF) + syntax_error(fp, "Expecting group name", lex_item); + return NULL; + } + StrAllocCopy(group_name, HTlex_buffer); + + if (LEX_FIELD_SEP != (lex_item = lex(fp))) { + syntax_error(fp, "Expecting field separator", lex_item); + FREE(group_name); + return NULL; + } + + if (!(group_def = HTAA_parseGroupDef(fp))) { + FREE(group_name); + return NULL; + } + group_def->group_name = group_name; + + return group_def; +} + +/* + * Group manipulation routines + */ + +static GroupDef *find_group_def(GroupDefList *group_list, + const char *group_name) +{ + if (group_list && group_name) { + GroupDefList *cur = group_list; + GroupDef *group_def; + + while (NULL != (group_def = (GroupDef *) HTList_nextObject(cur))) { + if (!strcmp(group_name, group_def->group_name)) { + return group_def; + } + } + } + return NULL; +} + +void HTAA_resolveGroupReferences(GroupDef *group_def, + GroupDefList *group_def_list) +{ + if (group_def && group_def->item_list && group_def_list) { + ItemList *cur1 = group_def->item_list; + Item *item; + + while (NULL != (item = (Item *) HTList_nextObject(cur1))) { + UserDefList *cur2 = item->user_def_list; + Ref *ref; + + while (NULL != (ref = (Ref *) HTList_nextObject(cur2))) + ref->translation = find_group_def(group_def_list, ref->name); + + /* Does NOT translate address_def_list */ + } + } +} + +static void add_group_def(GroupDefList *group_def_list, + GroupDef *group_def) +{ + HTAA_resolveGroupReferences(group_def, group_def_list); + HTList_addObject(group_def_list, (void *) group_def); +} + +static GroupDefList *parse_group_file(FILE *fp) +{ + GroupDefList *group_def_list = HTList_new(); + GroupDef *group_def; + + while (NULL != (group_def = parse_group_decl(fp))) + add_group_def(group_def_list, group_def); + + return group_def_list; +} +#endif + +/* + * Trace functions + */ + +static void print_item(Item *item) +{ + if (!item) + fprintf(tfp, "\tNULL-ITEM\n"); + else { + UserDefList *cur1 = item->user_def_list; + AddressDefList *cur2 = item->address_def_list; + Ref *user_ref = (Ref *) HTList_nextObject(cur1); + Ref *addr_ref = (Ref *) HTList_nextObject(cur2); + + if (user_ref) { + fprintf(tfp, "\t[%s%s", user_ref->name, + (user_ref->translation ? "*REF*" : "")); + while (NULL != (user_ref = (Ref *) HTList_nextObject(cur1))) + fprintf(tfp, "; %s%s", user_ref->name, + (user_ref->translation ? "*REF*" : "")); + fprintf(tfp, "] "); + } else + fprintf(tfp, "\tANYBODY "); + + if (addr_ref) { + fprintf(tfp, "@ [%s", addr_ref->name); + while (NULL != (addr_ref = (Ref *) HTList_nextObject(cur2))) + fprintf(tfp, "; %s", addr_ref->name); + fprintf(tfp, "]\n"); + } else + fprintf(tfp, "@ ANYADDRESS\n"); + } +} + +static void print_item_list(ItemList *item_list) +{ + ItemList *cur = item_list; + Item *item; + + if (!item_list) + fprintf(tfp, "EMPTY"); + else + while (NULL != (item = (Item *) HTList_nextObject(cur))) + print_item(item); +} + +void HTAA_printGroupDef(GroupDef *group_def) +{ + if (!group_def) { + fprintf(tfp, "\nNULL RECORD\n"); + return; + } + + fprintf(tfp, "\nGroup %s:\n", + (group_def->group_name ? group_def->group_name : "NULL")); + + print_item_list(group_def->item_list); + fprintf(tfp, "\n"); +} + +#if 0 +static void print_group_def_list(GroupDefList *group_list) +{ + GroupDefList *cur = group_list; + GroupDef *group_def; + + while (NULL != (group_def = (GroupDef *) HTList_nextObject(cur))) + HTAA_printGroupDef(group_def); +} + +/* + * IP address template matching + */ + +/* static part_match() + * MATCH ONE PART OF INET ADDRESS AGAINST + * A PART OF MASK (inet address has 4 parts) + * ON ENTRY: + * tcur pointer to the beginning of template part. + * icur pointer to the beginning of actual inet + * number part. + * + * ON EXIT: + * returns YES, if match. + */ +static BOOL part_match(const char *tcur, + const char *icur) +{ + char required[4]; + char actual[4]; + const char *cur; + int cnt; + BOOL status; + + if (!tcur || !icur) + return NO; + + cur = tcur; + cnt = 0; + while (cnt < 3 && *cur && *cur != '.') + required[cnt++] = *(cur++); + required[cnt] = (char) 0; + + cur = icur; + cnt = 0; + while (cnt < 3 && *cur && *cur != '.') + actual[cnt++] = *(cur++); + actual[cnt] = (char) 0; + + status = HTAA_templateMatch(required, actual); + CTRACE((tfp, "part_match: req: '%s' act: '%s' match: %s\n", + required, actual, (status ? "yes" : "no"))); + + return status; +} + +/* static ip_number_match() + * MATCH INET NUMBER AGAINST AN INET NUMBER MASK + * ON ENTRY: + * template mask to match against, e.g., 128.141.*.* + * the_inet_addr actual inet address, e.g., 128.141.201.74 + * + * ON EXIT: + * returns YES, if match; NO, if not. + */ +static BOOL ip_number_match(const char *ctemplate, + const char *the_inet_addr) +{ + const char *tcur = ctemplate; + const char *icur = the_inet_addr; + int cnt; + + for (cnt = 0; cnt < 4; cnt++) { + if (!tcur || !icur || !part_match(tcur, icur)) + return NO; + if (NULL != (tcur = StrChr(tcur, '.'))) + tcur++; + if (NULL != (icur = StrChr(icur, '.'))) + icur++; + } + return YES; +} + +/* static is_domain_mask() + * DETERMINE IF A GIVEN MASK IS A + * DOMAIN NAME MASK OR AN INET NUMBER MASK + * ON ENTRY: + * mask either a domain name mask, + * e.g. + * *.cern.ch + * + * or an inet number mask, + * e.g. + * 128.141.*.* + * + * ON EXIT: + * returns YES, if mask is a domain name mask. + * NO, if it is an inet number mask. + */ +static BOOL is_domain_mask(const char *mask) +{ + const char *cur = mask; + + if (!mask) + return NO; + + while (*cur) { + if (*cur != '.' && *cur != '*' && (*cur < '0' || *cur > '9')) + return YES; /* Even one non-digit makes it a domain name mask */ + cur++; + } + return NO; /* All digits and dots, so it is an inet number mask */ +} + +/* static ip_mask_match() + * MATCH AN IP NUMBER MASK OR IP NAME MASK + * AGAINST ACTUAL IP NUMBER OR IP NAME + * + * ON ENTRY: + * mask mask. Mask may be either an inet number + * mask or a domain name mask, + * e.g. + * 128.141.*.* + * or + * *.cern.ch + * + * ip_number IP number of connecting host. + * ip_name IP name of the connecting host. + * + * ON EXIT: + * returns YES, if hostname/internet number + * matches the mask. + * NO, if no match (no fire). + */ +static BOOL ip_mask_match(const char *mask, + const char *ip_number, + const char *ip_name) +{ + if (mask && (ip_number || ip_name)) { + if (is_domain_mask(mask)) { + if (HTAA_templateMatch(mask, ip_name)) + return YES; + } else { + if (ip_number_match(mask, ip_number)) + return YES; + } + } + return NO; +} + +static BOOL ip_in_def_list(AddressDefList *address_def_list, + char *ip_number, + char *ip_name) +{ + if (address_def_list && (ip_number || ip_name)) { + AddressDefList *cur = address_def_list; + Ref *ref; + + while (NULL != (ref = (Ref *) HTList_nextObject(cur))) { + /* Value of ref->translation is ignored, i.e., */ + /* no recursion for ip address tamplates. */ + if (ip_mask_match(ref->name, ip_number, ip_name)) + return YES; + } + } + return NO; +} + +/* + * Group file cached reading + */ + +typedef struct { + char *group_filename; + GroupDefList *group_list; +} GroupCache; + +typedef HTList GroupCacheList; + +static GroupCacheList *group_cache_list = NULL; + +GroupDefList *HTAA_readGroupFile(const char *filename) +{ + FILE *fp; + GroupCache *group_cache; + + if (isEmpty(filename)) + return NULL; + + if (!group_cache_list) + group_cache_list = HTList_new(); + else { + GroupCacheList *cur = group_cache_list; + + while (NULL != (group_cache = (GroupCache *) HTList_nextObject(cur))) { + if (!strcmp(filename, group_cache->group_filename)) { + CTRACE((tfp, "%s '%s' %s\n", + "HTAA_readGroupFile: group file", + filename, "already found in cache")); + return group_cache->group_list; + } /* if cache match */ + } /* while cached files remain */ + } /* cache exists */ + + CTRACE((tfp, "HTAA_readGroupFile: reading group file `%s'\n", + filename)); + + if (!(fp = fopen(filename, TXT_R))) { + CTRACE((tfp, "%s '%s'\n", + "HTAA_readGroupFile: unable to open group file", + filename)); + return NULL; + } + + if ((group_cache = typecalloc(GroupCache)) == 0) + outofmem(__FILE__, "HTAA_readGroupFile"); + + group_cache->group_filename = NULL; + StrAllocCopy(group_cache->group_filename, filename); + group_cache->group_list = parse_group_file(fp); + HTList_addObject(group_cache_list, (void *) group_cache); + fclose(fp); + + CTRACE((tfp, "Read group file '%s', results follow:\n", filename)); + if (TRACE) + print_group_def_list(group_cache->group_list); + + return group_cache->group_list; +} + +/* PUBLIC HTAA_userAndInetInGroup() + * CHECK IF USER BELONGS TO TO A GIVEN GROUP + * AND THAT THE CONNECTION COMES FROM AN + * ADDRESS THAT IS ALLOWED BY THAT GROUP + * ON ENTRY: + * group the group definition structure. + * username connecting user. + * ip_number browser host IP number, optional. + * ip_name browser host IP name, optional. + * However, one of ip_number or ip_name + * must be given. + * ON EXIT: + * returns HTAA_IP_MASK, if IP address mask was + * reason for failing. + * HTAA_NOT_MEMBER, if user does not belong + * to the group. + * HTAA_OK if both IP address and user are ok. + */ +HTAAFailReasonType HTAA_userAndInetInGroup(GroupDef *group, + char *username, + char *ip_number, + char *ip_name) +{ + HTAAFailReasonType reason = HTAA_NOT_MEMBER; + + if (group && username) { + ItemList *cur1 = group->item_list; + Item *item; + + while (NULL != (item = (Item *) HTList_nextObject(cur1))) { + if (!item->address_def_list || /* Any address allowed */ + ip_in_def_list(item->address_def_list, ip_number, ip_name)) { + + if (!item->user_def_list) /* Any user allowed */ + return HTAA_OK; + else { + UserDefList *cur2 = item->user_def_list; + Ref *ref; + + while (NULL != (ref = (Ref *) HTList_nextObject(cur2))) { + + if (ref->translation) { /* Group, check recursively */ + reason = HTAA_userAndInetInGroup(ref->translation, + username, + ip_number, ip_name); + if (reason == HTAA_OK) + return HTAA_OK; + } else { /* Username, check directly */ + if (username && *username && + 0 == strcmp(ref->name, username)) + return HTAA_OK; + } + /* Every user/group name in this group */ + } + /* search for username */ + } + /* IP address ok */ + } else { + reason = HTAA_IP_MASK; + } + } /* while items in group */ + } + /* valid parameters */ + return reason; /* No match, or invalid parameters */ +} + +void GroupDef_delete(GroupDef *group_def) +{ + if (group_def) { + FREE(group_def->group_name); + if (group_def->item_list) { + HTList_delete(group_def->item_list); /* @@@@ */ + group_def->item_list = NULL; + } + FREE(group_def); + } +} +#endif diff --git a/WWW/Library/Implementation/HTGroup.h b/WWW/Library/Implementation/HTGroup.h new file mode 100644 index 0000000..7874565 --- /dev/null +++ b/WWW/Library/Implementation/HTGroup.h @@ -0,0 +1,182 @@ +/* GROUP FILE ROUTINES + + */ + +#ifndef HTGROUP_H +#define HTGROUP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef HTList GroupDefList; + typedef HTList ItemList; + + typedef struct { + char *group_name; + ItemList *item_list; + } GroupDef; + +/* + * Access Authorization failure reasons + */ + typedef enum { + HTAA_OK, /* 200 OK */ + HTAA_OK_GATEWAY, /* 200 OK, acting as a gateway */ + HTAA_NO_AUTH, /* 401 Unauthorized, not authenticated */ + HTAA_NOT_MEMBER, /* 401 Unauthorized, not authorized */ + HTAA_IP_MASK, /* 403 Forbidden by IP mask */ + HTAA_BY_RULE, /* 403 Forbidden by rule */ + HTAA_NO_ACL, /* 403 Forbidden, ACL non-existent */ + HTAA_NO_ENTRY, /* 403 Forbidden, no ACL entry */ + HTAA_SETUP_ERROR, /* 403 Forbidden, server setup error */ + HTAA_DOTDOT, /* 403 Forbidden, URL with /../ illegal */ + HTAA_HTBIN, /* 403 Forbidden, /htbin not enabled */ + HTAA_NOT_FOUND /* 404 Not found, or read protected */ + } HTAAFailReasonType; + +/* + +Group definition grammar + + string + "sequence of alphanumeric characters" + + user_name + string + + group_name + string + + group_ref + group_name + + user_def + user_name | group_ref + + user_def_list + user_def { ',' user_def } + + user_part + user_def | '(' user_def_list ')' + + templ + + "sequence of alphanumeric characters and '*'s" + + ip_number_mask + templ '.' templ '.' templ '.' templ + + domain_name_mask + templ { '.' templ } + + address + + ip_number_mask | domain_name_mask + + address_def + + address + + address_def_list + address_def { ',' address_def } + + address_part + address_def | '(' address_def_list ')' + + item + [user_part] ['@' address_part] + + item_list + item { ',' item } + + group_def + item_list + + group_decl + group_name ':' group_def + + PARSE GROUP DEFINITION + + */ + + extern GroupDef *HTAA_parseGroupDef(FILE *fp); + +/* + +Fill in Pointers to referenced Group Definitions in a Group Definition + + References to groups (by their name) are resolved from group_def_list and pointers to + those structures are added to group_def. + + */ + + extern void HTAA_resolveGroupReferences(GroupDef *group_def, + GroupDefList *group_def_list); + +/* + +Read Group File (and do caching) + + If group file is already in cache returns a pointer to previously read group definition + list. + + */ + + extern GroupDefList *HTAA_readGroupFile(const char *filename); + +/* + +Delete Group Definition + + Groups in cache should never be freed by this function. This should only be used to + free group definitions read by HTAA_parseGroupDef. + + */ + + extern void GroupDef_delete(GroupDef *group_def); + +/* + +Print Out Group Definition (for trace purposes) + + */ + + extern void HTAA_printGroupDef(GroupDef *group_def); + +/* + +Does a User Belong to a Given Set of Groups + + This function checks both the username and the internet address. + + */ + +/* PUBLIC HTAA_userAndInetInGroup() + * CHECK IF USER BELONGS TO TO A GIVEN GROUP + * AND THAT THE CONNECTION COMES FROM AN + * ADDRESS THAT IS ALLOWED BY THAT GROUP + * ON ENTRY: + * group the group definition structure. + * username connecting user. + * ip_number browser host IP number, optional. + * ip_name browser host IP name, optional. + * However, one of ip_number or ip_name + * must be given. + * ON EXIT: + * returns HTAA_IP_MASK, if IP address mask was + * reason for failing. + * HTAA_NOT_MEMBER, if user does not belong + * to the group. + * HTAA_OK if both IP address and user are ok. + */ + extern HTAAFailReasonType HTAA_userAndInetInGroup(GroupDef *group, + char *username, + char *ip_number, + char *ip_name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTGROUP_H */ diff --git a/WWW/Library/Implementation/HTInit.h b/WWW/Library/Implementation/HTInit.h new file mode 100644 index 0000000..4fc7000 --- /dev/null +++ b/WWW/Library/Implementation/HTInit.h @@ -0,0 +1,34 @@ +/* /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTInit.html + INITIALISATION MODULE + + This module registers all the plug & play software modules which will be + used in the program. This is for a browser. + + To override this, just copy it and link in your version before you link with + the library. + + Implemented by HTInit.c by default. + + */ + +#ifndef HTINIT_H +#define HTINIT_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + extern void HTFormatInit(void); + extern void HTPreparsedFormatInit(void); + extern void HTFileInit(void); + extern int LYTestMailcapCommand(const char *testcommand, const char *params); + extern BOOL LYMailcapUsesPctS(const char *controlstring); + extern char *LYMakeMailcapCommand(const char *command, const char *params, const char *filename); + +#ifdef __cplusplus +} +#endif +#endif /* HTINIT_H */ diff --git a/WWW/Library/Implementation/HTLex.c b/WWW/Library/Implementation/HTLex.c new file mode 100644 index 0000000..5a0df91 --- /dev/null +++ b/WWW/Library/Implementation/HTLex.c @@ -0,0 +1,142 @@ + +/* MODULE HTLex.c + * LEXICAL ANALYSOR + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * + * + * BUGS: + * + * + */ + +#include + +#include /* Implemented here */ + +#include + +/* + * Global variables + */ +char HTlex_buffer[40]; /* Read lexical string */ +int HTlex_line = 1; /* Line number in source file */ + +/* + * Module-wide variables + */ +static int lex_cnt; +static BOOL lex_template; +static LexItem lex_pushed_back = LEX_NONE; +static FILE *cache = NULL; + +void unlex(LexItem lex_item) +{ + lex_pushed_back = lex_item; +} + +LexItem lex(FILE *fp) +{ + int ch = 0; + + if (fp != cache) { /* This cache doesn't work ok because the system */ + cache = fp; /* often assign same FILE structure the next open */ + HTlex_line = 1; /* file. So, if there are syntax errors in setup * + files it may confuse things later on. */ + } + if (lex_pushed_back != LEX_NONE) { + LexItem ret = lex_pushed_back; + + lex_pushed_back = LEX_NONE; + return ret; + } + + lex_cnt = 0; + lex_template = NO; + + for (;;) { + switch (ch = getc(fp)) { + case EOF: + case ' ': + case '\t': + case '\r': + case '\n': + case ':': + case ',': + case '(': + case ')': + case '@': + if (lex_cnt > 0) { + if (ch != EOF) + ungetc(ch, fp); + if (lex_template) + return LEX_TMPL_STR; + else + return LEX_ALPH_STR; + } else + switch (ch) { + case EOF: + return LEX_EOF; + case '\n': + HTlex_line++; + return LEX_REC_SEP; + case ':': + return LEX_FIELD_SEP; + case ',': + return LEX_ITEM_SEP; + case '(': + return LEX_OPEN_PAREN; + case ')': + return LEX_CLOSE_PAREN; + case '@': + return LEX_AT_SIGN; + default: /* Leading white space ignored (SP,TAB,CR) */ + break; + } + break; + default: + if (lex_cnt < (int) (sizeof(HTlex_buffer) - 1)) + HTlex_buffer[lex_cnt++] = (char) ch; + HTlex_buffer[lex_cnt] = '\0'; + if ('*' == ch) + lex_template = YES; + } /* switch ch */ + } /* forever */ +} + +const char *lex_verbose(LexItem lex_item) +{ + static char msg[sizeof(HTlex_buffer) + 30]; /* @@@@@@@@ */ + + switch (lex_item) { + case LEX_NONE: /* Internally used */ + return "NO-LEX-ITEM"; + case LEX_EOF: /* End of file */ + return "end-of-file"; + case LEX_REC_SEP: /* Record separator */ + return "record separator (newline)"; + case LEX_FIELD_SEP: /* Field separator */ + return "field separator ':'"; + case LEX_ITEM_SEP: /* List item separator */ + return "item separator ','"; + case LEX_OPEN_PAREN: /* Group start tag */ + return "'('"; + case LEX_CLOSE_PAREN: /* Group end tag */ + return "')'"; + case LEX_AT_SIGN: /* Address qualifier */ + return "address qualifier '@'"; + case LEX_ALPH_STR: /* Alphanumeric string */ + sprintf(msg, "alphanumeric string '%.*s'", + (int) sizeof(HTlex_buffer), HTlex_buffer); + return msg; + case LEX_TMPL_STR: /* Template string */ + sprintf(msg, "template string '%.*s'", + (int) sizeof(HTlex_buffer), HTlex_buffer); + return msg; + default: + return "UNKNOWN-LEX-ITEM"; + } +} diff --git a/WWW/Library/Implementation/HTLex.h b/WWW/Library/Implementation/HTLex.h new file mode 100644 index 0000000..fde9034 --- /dev/null +++ b/WWW/Library/Implementation/HTLex.h @@ -0,0 +1,64 @@ +/* LEXICAL ANALYSOR (MAINLY FOR CONFIG FILES) + + */ + +#ifndef HTLEX_H +#define HTLEX_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { + LEX_NONE, /* Internally used */ + LEX_EOF, /* End of file */ + LEX_REC_SEP, /* Record separator */ + LEX_FIELD_SEP, /* Field separator */ + LEX_ITEM_SEP, /* List item separator */ + LEX_OPEN_PAREN, /* Group start tag */ + LEX_CLOSE_PAREN, /* Group end tag */ + LEX_AT_SIGN, /* Address qualifier */ + LEX_ALPH_STR, /* Alphanumeric string */ + LEX_TMPL_STR /* Template string */ + } LexItem; + + extern char HTlex_buffer[]; /* Read lexical string */ + extern int HTlex_line; /* Line number in source file */ + +/* + +Get Next Lexical Item + + If returns LEX_ALPH_STR or LEX_TMPL_STR the string is in global buffer lex_buffer. + + */ + + extern LexItem lex(FILE *fp); + +/* + +Push Back Latest Item + + */ + + extern void unlex(LexItem lex_item); + +/* + +Get the Name for Lexical Item + + */ + + extern const char *lex_verbose(LexItem lex_item); + +/* + + */ + +#ifdef __cplusplus +} +#endif +#endif /* not HTLEX_H */ diff --git a/WWW/Library/Implementation/HTList.c b/WWW/Library/Implementation/HTList.c new file mode 100644 index 0000000..d12cd38 --- /dev/null +++ b/WWW/Library/Implementation/HTList.c @@ -0,0 +1,402 @@ +/* + * $LynxId: HTList.c,v 1.20 2016/11/24 15:29:50 tom Exp $ + * + * A small List class HTList.c + * ================== + * + * A list is represented as a sequence of linked nodes of type HTList. + * The first node is a header which contains no object. + * New nodes are inserted between the header and the rest of the list. + */ + +#include +#include + +#include + +/* Create list. +*/ +HTList *HTList_new(void) +{ + HTList *newList; + + if ((newList = typeMalloc(HTList)) == NULL) + outofmem(__FILE__, "HTList_new"); + + newList->object = NULL; + newList->next = NULL; + + return newList; +} + +/* Delete list. +*/ +void HTList_delete(HTList *me) +{ + HTList *current; + + while ((current = me)) { + me = me->next; + FREE(current); + } + + return; +} + +/* Reverse order of elements in list. + */ +HTList *HTList_reverse(HTList *start) +{ + HTList *cur, *succ; + + if (!(start && start->next && (cur = start->next->next))) + return start; + start->next->next = NULL; + while (cur) { + succ = cur->next; + cur->next = start->next; + start->next = cur; + cur = succ; + } + return start; +} + +/* Append a list to another. + * + * If successful, the second list will become empty but not freed. + */ +HTList *HTList_appendList(HTList *start, + HTList *tail) +{ + HTList *temp = start; + + if (!start) { + CTRACE((tfp, + "HTList: Trying to append list %p to a nonexisting list\n", + (void *) tail)); + return NULL; + } + if (!(tail && tail->next)) + return start; + + while (temp->next) + temp = temp->next; + + temp->next = tail->next; + tail->next = NULL; /* tail is now an empty list */ + return start; +} + +/* Link object to START of list (so it is pointed to by the head). + * + * Unlike HTList_addObject(), it does not malloc memory for HTList entry, + * it use already allocated memory which should not be free'd by any + * list operations (optimization). + */ +void HTList_linkObject(HTList *me, void *newObject, + HTList *newNode) +{ + if (me) { + if (newNode->object == NULL && newNode->next == NULL) { + /* It is safe: */ + newNode->object = newObject; + newNode->next = me->next; + me->next = newNode; + + } else { + /* + * This node is already linked to some list (probably this one), so + * refuse changing node pointers to keep the list valid!!! + */ + CTRACE((tfp, "*** HTList: Refuse linking already linked obj ")); + CTRACE((tfp, "%p, node %p, list %p\n", + (void *) newObject, (void *) newNode, (void *) me)); + } + + } else { + CTRACE((tfp, + "HTList: Trying to link object %p to a nonexisting list\n", + newObject)); + } + + return; +} + +/* Add object to START of list (so it is pointed to by the head). +*/ +void HTList_addObject(HTList *me, void *newObject) +{ + HTList *newNode; + + if (me) { + if ((newNode = typeMalloc(HTList)) == NULL) + outofmem(__FILE__, "HTList_addObject"); + + newNode->object = newObject; + newNode->next = me->next; + me->next = newNode; + + } else { + CTRACE((tfp, "HTList: Trying to add object %p to a nonexisting list\n", + newObject)); + } + + return; +} + +/* Append object to END of list (furthest from the head). +*/ +void HTList_appendObject(HTList *me, void *newObject) +{ + HTList *temp = me; + + if (temp && newObject) { + while (temp->next) + temp = temp->next; + HTList_addObject(temp, newObject); + } + + return; +} + +/* Insert an object into the list at a specified position. + * If position is 0, this places the object at the head of the list + * and is equivalent to HTList_addObject(). + */ +void HTList_insertObjectAt(HTList *me, void *newObject, + int pos) +{ + HTList *newNode; + HTList *temp = me; + HTList *prevNode; + int Pos = pos; + + if (!temp) { + CTRACE((tfp, "HTList: Trying to add object %p to a nonexisting list\n", + newObject)); + return; + } + if (Pos < 0) { + Pos = 0; + CTRACE((tfp, "HTList: Treating negative object position %d as %d.\n", + pos, Pos)); + } + + prevNode = temp; + while ((temp = temp->next)) { + if (Pos == 0) { + if ((newNode = typeMalloc(HTList)) == NULL) + outofmem(__FILE__, "HTList_addObjectAt"); + + newNode->object = newObject; + newNode->next = temp; + if (prevNode) + prevNode->next = newNode; + return; + } + prevNode = temp; + Pos--; + } + if (Pos >= 0) + HTList_addObject(prevNode, newObject); + + return; +} + +/* Unlink specified object from list. + * It does not free memory. + */ +BOOL HTList_unlinkObject(HTList *me, void *oldObject) +{ + HTList *temp = me; + HTList *prevNode; + + if (temp && oldObject) { + while (temp->next) { + prevNode = temp; + temp = temp->next; + if (temp->object == oldObject) { + prevNode->next = temp->next; + temp->next = NULL; + temp->object = NULL; + return YES; /* Success */ + } + } + } + return NO; /* object not found or NULL list */ +} + +/* Remove specified object from list. +*/ +BOOL HTList_removeObject(HTList *me, void *oldObject) +{ + HTList *temp = me; + HTList *prevNode; + + if (temp && oldObject) { + while (temp->next) { + prevNode = temp; + temp = temp->next; + if (temp->object == oldObject) { + prevNode->next = temp->next; + FREE(temp); + return YES; /* Success */ + } + } + } + return NO; /* object not found or NULL list */ +} + +/* Remove object at a given position in the list, where 0 is the + * object pointed to by the head (returns a pointer to the element + * (->object) for the object, and NULL if the list is empty, or + * if it doesn't exist - Yuk!). + */ +void *HTList_removeObjectAt(HTList *me, int position) +{ + HTList *temp = me; + HTList *prevNode; + int pos = position; + void *result = NULL; + + if (temp != NULL && pos >= 0) { + prevNode = temp; + while ((temp = temp->next) != NULL) { + if (pos == 0) { + prevNode->next = temp->next; + result = temp->object; + FREE(temp); + break; + } + prevNode = temp; + pos--; + } + } + + return result; +} + +/* Unlink object from START of list (the Last one inserted + * via HTList_linkObject(), and pointed to by the head). + * It does not free memory. + */ +void *HTList_unlinkLastObject(HTList *me) +{ + HTList *lastNode; + void *lastObject; + + if (me && me->next) { + lastNode = me->next; + lastObject = lastNode->object; + me->next = lastNode->next; + lastNode->next = NULL; + lastNode->object = NULL; + return lastObject; + + } else { /* Empty list */ + return NULL; + } +} + +/* Remove object from START of list (the Last one inserted + * via HTList_addObject(), and pointed to by the head). + */ +void *HTList_removeLastObject(HTList *me) +{ + HTList *lastNode; + void *lastObject; + + if (me && me->next) { + lastNode = me->next; + lastObject = lastNode->object; + me->next = lastNode->next; + FREE(lastNode); + return lastObject; + + } else { /* Empty list */ + return NULL; + } +} + +/* Remove object from END of list (the First one inserted + * via HTList_addObject(), and furthest from the head). + */ +void *HTList_removeFirstObject(HTList *me) +{ + HTList *temp = me; + HTList *prevNode; + void *firstObject; + + if (!temp) + return NULL; + + prevNode = temp; + if (temp->next) { + while (temp->next) { + prevNode = temp; + temp = temp->next; + } + firstObject = temp->object; + prevNode->next = NULL; + FREE(temp); + return firstObject; + + } else { /* Empty list */ + return NULL; + } +} + +/* Determine total number of objects in the list, + * not counting the head. + */ +int HTList_count(HTList *me) +{ + HTList *temp = me; + int count = 0; + + if (temp) + while ((temp = temp->next)) + count++; + + return count; +} + +/* Determine position of an object in the list (a value of 0 + * means it is pointed to by the head; returns -1 if not found). + */ +int HTList_indexOf(HTList *me, void *object) +{ + HTList *temp = me; + int position = 0; + + if (temp) { + while ((temp = temp->next)) { + if (temp->object == object) + return position; + position++; + } + } + + return -1; /* Object not in the list */ +} + +/* Return pointer to the object at a specified position in the list, + * where 0 is the object pointed to by the head (returns NULL if + * the list is empty, or if it doesn't exist - Yuk!). + */ +void *HTList_objectAt(HTList *me, int position) +{ + HTList *temp = me; + int pos = position; + + if (!temp || pos < 0) + return NULL; + + while ((temp = temp->next)) { + if (pos == 0) + return temp->object; + pos--; + } + + return NULL; /* Reached the end of the list */ +} diff --git a/WWW/Library/Implementation/HTList.h b/WWW/Library/Implementation/HTList.h new file mode 100644 index 0000000..93f9147 --- /dev/null +++ b/WWW/Library/Implementation/HTList.h @@ -0,0 +1,142 @@ + +/* List object + * + * The list object is a generic container for storing collections + * of things in order. + */ +#ifndef HTLIST_H +#define HTLIST_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTList HTList; + + struct _HTList { + void *object; + HTList *next; + }; + +/* Fast macro to traverse a list. Call it first with copy of the list + * header. It returns the first object and increments the passed list + * pointer. Call it with the same variable until it returns NULL. + */ +#define HTList_nextObject(me) \ + ((me) && ((me) = (me)->next) ? (me)->object : NULL) + +/* Macro to find object pointed to by the head (returns NULL + * if list is empty, OR if it doesn't exist - Yuk!) + */ +#define HTList_lastObject(me) \ + ((me) && (me)->next ? (me)->next->object : NULL) + +/* Macro to check if a list is empty (or doesn't exist - Yuk!) +*/ +#define HTList_isEmpty(me) ((me) ? ((me)->next == NULL) : YES) + +/* Create list. +*/ + extern HTList *HTList_new(void); + +/* Delete list. +*/ + extern void HTList_delete(HTList *me); + +/* Reverse a list. +*/ + extern HTList *HTList_reverse(HTList *start); + +/* Append two lists, making second list empty. +*/ + extern HTList *HTList_appendList(HTList *start, + HTList *tail); + +/* Add object to START of list (so it is pointed to by the head). +*/ + extern void HTList_addObject(HTList *me, + void *newObject); + +/* Append object to END of list (furthest from the head). +*/ + extern void HTList_appendObject(HTList *me, + void *newObject); + +/* Insert an object into the list at a specified position. + * If position is 0, this places the object at the head of the list + * and is equivalent to HTList_addObject(). + */ + extern void HTList_insertObjectAt(HTList *me, + void *newObject, + int pos); + +/* Remove specified object from list. +*/ + extern BOOL HTList_removeObject(HTList *me, + void *oldObject); + +/* Remove object at a given position in the list, where 0 is the + * object pointed to by the head (returns a pointer to the element + * (->object) for the object, and NULL if the list is empty, or + * if it doesn't exist - Yuk!). + */ + extern void *HTList_removeObjectAt(HTList *me, + int position); + +/* Remove object from START of list (the Last one inserted + * via HTList_addObject(), and pointed to by the head). + */ + extern void *HTList_removeLastObject(HTList *me); + +/* Remove object from END of list (the First one inserted + * via HTList_addObject(), and furthest from the head). + */ + extern void *HTList_removeFirstObject(HTList *me); + +/* Determine total number of objects in the list, + * not counting the head. + */ + extern int HTList_count(HTList *me); + +/* Determine position of an object in the list (a value of 0 + * means it is pointed to by the head; returns -1 if not found). + */ + extern int HTList_indexOf(HTList *me, + void *object); + +/* Return pointer to the object at a specified position in the list, + * where 0 is the object pointed to by the head (returns NULL if + * the list is empty, or if it doesn't exist - Yuk!). + */ + extern void *HTList_objectAt(HTList *me, + int position); + +/* Link object to START of list (so it is pointed to by the head). + * + * Unlike HTList_addObject(), it does not malloc memory for HTList entry, + * it use already allocated memory which should not be free'd by any + * list operations (optimization). + */ + extern void HTList_linkObject(HTList *me, + void *newObject, + HTList *newNode); + +/* Unlink object from START of list (the Last one inserted + * via HTList_linkObject(), and pointed to by the head). + * It does not free memory. + */ + extern void *HTList_unlinkLastObject(HTList *me); + +/* Unlink specified object from list. + * It does not free memory. + */ + extern BOOL HTList_unlinkObject(HTList *me, + void *oldObject); + +#ifdef __cplusplus +} +#endif +#endif /* HTLIST_H */ diff --git a/WWW/Library/Implementation/HTMIME.c b/WWW/Library/Implementation/HTMIME.c new file mode 100644 index 0000000..fde89a6 --- /dev/null +++ b/WWW/Library/Implementation/HTMIME.c @@ -0,0 +1,2594 @@ +/* + * $LynxId: HTMIME.c,v 1.102 2022/03/12 14:47:02 tom Exp $ + * + * MIME Message Parse HTMIME.c + * ================== + * + * This is RFC 1341-specific code. + * The input stream pushed into this parser is assumed to be + * stripped on CRs, ie lines end with LF, not CR LF. + * (It is easy to change this except for the body part where + * conversion can be slow.) + * + * History: + * Feb 92 Written Tim Berners-Lee, CERN + * + */ + +#define HTSTREAM_INTERNAL 1 + +#include +#include /* Implemented here */ +#include /* for redirecting_url */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* MIME Object + * ----------- + */ + +typedef enum { + MIME_TRANSPARENT, /* put straight through to target ASAP! */ + /* states for "Transfer-Encoding: chunked" */ + MIME_CHUNKED, + mcCHUNKED_COUNT_DIGIT, + mcCHUNKED_COUNT_CR, + mcCHUNKED_COUNT_LF, + mcCHUNKED_EXTENSION, + mcCHUNKED_DATA, + mcCHUNKED_DATA_CR, + mcCHUNKED_DATA_LF, + /* character state-machine */ + miBEGINNING_OF_LINE, /* first character and not a continuation */ + miA, + miACCEPT_RANGES, + miAGE, + miAL, + miALLOW, + miALTERNATES, + miC, + miCACHE_CONTROL, + miCO, + miCOOKIE, + miCON, + miCONNECTION, + miCONTENT_, + miCONTENT_BASE, + miCONTENT_DISPOSITION, + miCONTENT_ENCODING, + miCONTENT_FEATURES, + miCONTENT_L, + miCONTENT_LANGUAGE, + miCONTENT_LENGTH, + miCONTENT_LOCATION, + miCONTENT_MD5, + miCONTENT_RANGE, + miCONTENT_T, + miCONTENT_TRANSFER_ENCODING, + miCONTENT_TYPE, + miDATE, + miE, + miETAG, + miEXPIRES, + miKEEP_ALIVE, + miL, + miLAST_MODIFIED, + miLINK, + miLOCATION, + miP, + miPR, + miPRAGMA, + miPROXY_AUTHENTICATE, + miPUBLIC, + miR, + miRE, + miREFRESH, + miRETRY_AFTER, + miS, + miSAFE, + miSE, + miSERVER, + miSET_COOKIE, + miSET_COOKIE1, + miSET_COOKIE2, + miT, + miTITLE, + miTRANSFER_ENCODING, + miU, + miUPGRADE, + miURI, + miV, + miVARY, + miVIA, + miW, + miWARNING, + miWWW_AUTHENTICATE, + miSKIP_GET_VALUE, /* Skip space then get value */ + miGET_VALUE, /* Get value till white space */ + miJUNK_LINE, /* Ignore the rest of this folded line */ + miNEWLINE, /* Just found a LF .. maybe continuation */ + miCHECK, /* check against check_pointer */ + MIME_NET_ASCII, /* Translate from net ascii */ + MIME_IGNORE /* Ignore entire file */ + /* TRANSPARENT and IGNORE are defined as stg else in _WINDOWS */ +} MIME_state; + +#define VALUE_SIZE 5120 /* @@@@@@@ Arbitrary? */ +struct _HTStream { + const HTStreamClass *isa; + + BOOL net_ascii; /* Is input net ascii? */ + MIME_state state; /* current state */ + MIME_state if_ok; /* got this state if match */ + MIME_state field; /* remember which field */ + MIME_state fold_state; /* state on a fold */ + BOOL head_only; /* only parsing header */ + BOOL pickup_redirection; /* parsing for location */ + BOOL no_streamstack; /* use sink directly */ + const char *check_pointer; /* checking input */ + + char *value_pointer; /* storing values */ + char value[VALUE_SIZE]; + + HTParentAnchor *anchor; /* Given on creation */ + HTStream *sink; /* Given on creation */ + + char *boundary; /* For multipart */ + char *set_cookie; /* Set-Cookie */ + char *set_cookie2; /* Set-Cookie2 */ + char *location; /* Location */ + + char *refresh_url; /* "Refresh:" URL */ + + HTFormat c_t_encoding; /* Content-Transfer-Encoding */ + char *compression_encoding; + + BOOL chunked_encoding; /* Transfer-Encoding: chunked */ + long chunked_size; /* ...counter for "chunked" */ + + HTFormat format; /* Content-Type */ + HTStream *target; /* While writing out */ + HTStreamClass targetClass; + + HTAtom *targetRep; /* Converting into? */ +}; + +/* + * This function is for trimming off any paired + * open- and close-double quotes from header values. + * It does not parse the string for embedded quotes, + * and will not modify the string unless both the + * first and last characters are double-quotes. - FM + */ +void HTMIME_TrimDoubleQuotes(char *value) +{ + size_t i; + char *cp = value; + + if (isEmpty(cp) || *cp != '"') + return; + + i = strlen(cp); + if (cp[(i - 1)] != '"') + return; + else + cp[(i - 1)] = '\0'; + + for (i = 0; value[i]; i++) + value[i] = cp[(i + 1)]; +} + +/* + * Check if the token from "Content-Encoding" corresponds to a compression + * type. + */ +static BOOL content_is_compressed(HTStream *me) +{ + char *encoding = me->anchor->content_encoding; + BOOL result = (BOOL) (HTEncodingToCompressType(encoding) != cftNone); + + CTRACE((tfp, "content is%s compressed\n", result ? "" : " NOT")); + return result; +} + +/* + * Strip quotes from a refresh-URL. + */ +static void dequote(char *url) +{ + size_t len; + + len = strlen(url); + if (*url == '\'' && len > 1 && url[len - 1] == url[0]) { + url[len - 1] = '\0'; + while ((url[0] = url[1]) != '\0') { + ++url; + } + } +} + +/* + * Strip off any compression-suffix from the address and check if the result + * looks like one of the presentable suffixes. If so, return the corresponding + * MIME type. + */ +static const char *UncompressedContentType(HTStream *me, CompressFileType method) +{ + const char *result = 0; + char *address = me->anchor->address; + const char *expected = HTCompressTypeToSuffix(method); + const char *actual = strrchr(address, '.'); + + /* + * We have to ensure the suffix is consistent, to use HTFileFormat(). + */ + if (actual != 0 && !strcasecomp(actual, expected)) { + HTFormat format; + HTAtom *pencoding = 0; + const char *description = 0; + + format = HTFileFormat(address, &pencoding, &description); + result = HTAtom_name(format); + } + + return result; +} + +static int pumpData(HTStream *me) +{ + CompressFileType method; + const char *new_encoding; + const char *new_content; + + CTRACE((tfp, "Begin pumpData\n")); + /* + * If the content-type says it is compressed, and there is no + * content-encoding, check further and see if the address (omitting the + * suffix for a compressed type) looks like a type we can present. If so, + * rearrange things so we'll present the StreamStack code with the + * presentable type, already marked as compressed. + */ + CTRACE((tfp, "...address{%s}\n", me->anchor->address)); + method = HTContentTypeToCompressType(me->anchor->content_type_params); + if (isEmpty(me->anchor->content_encoding)) + me->anchor->no_content_encoding = TRUE; + if ((method != cftNone) + && isEmpty(me->anchor->content_encoding) + && (new_content = UncompressedContentType(me, method)) != 0) { + + new_encoding = HTCompressTypeToEncoding(method); + CTRACE((tfp, "reinterpreting as content-type:%s, encoding:%s\n", + new_content, new_encoding)); + + StrAllocCopy(me->anchor->content_encoding, new_encoding); + FREE(me->compression_encoding); + StrAllocCopy(me->compression_encoding, new_encoding); + + LYStrNCpy(me->value, new_content, VALUE_SIZE - 1); + StrAllocCopy(me->anchor->content_type_params, me->value); + me->format = HTAtom_for(me->value); + } + + if (StrChr(HTAtom_name(me->format), ';') != NULL) { + char *cp = NULL, *cp1, *cp2, *cp3 = NULL, *cp4; + + CTRACE((tfp, "HTMIME: Extended MIME Content-Type is %s\n", + HTAtom_name(me->format))); + StrAllocCopy(cp, HTAtom_name(me->format)); + /* + * Note that the Content-Type value was converted + * to lower case when we loaded into me->format, + * but there may have been a mixed or upper-case + * atom, so we'll force lower-casing again. We + * also stripped spaces and double-quotes, but + * we'll make sure they're still gone from any + * charset parameter we check. - FM + */ + LYLowerCase(cp); + if ((cp1 = StrChr(cp, ';')) != NULL) { + BOOL chartrans_ok = NO; + + if ((cp2 = strstr(cp1, "charset")) != NULL) { + int chndl; + + cp2 += 7; + while (*cp2 == ' ' || *cp2 == '=' || *cp2 == '"') + cp2++; + StrAllocCopy(cp3, cp2); /* copy to mutilate more */ + for (cp4 = cp3; (*cp4 != '\0' && *cp4 != '"' && + *cp4 != ';' && *cp4 != ':' && + !WHITE(*cp4)); cp4++) ; /* do nothing */ + *cp4 = '\0'; + cp4 = cp3; + chndl = UCGetLYhndl_byMIME(cp3); + if (UCCanTranslateFromTo(chndl, + current_char_set)) { + chartrans_ok = YES; + *cp1 = '\0'; + me->format = HTAtom_for(cp); + StrAllocCopy(me->anchor->charset, cp4); + HTAnchor_setUCInfoStage(me->anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_MIME); + } else if (chndl < 0) { /* got something but we don't + recognize it */ + 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 */ + if (UCCanTranslateFromTo(chndl, + current_char_set)) { + chartrans_ok = YES; + *cp1 = '\0'; + me->format = HTAtom_for(cp); + HTAnchor_setUCInfoStage(me->anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + } else { + /* + * Something like 'big5' - we cannot translate it, but + * the user may still be able to navigate the links. + */ + *cp1 = '\0'; + me->format = HTAtom_for(cp); + StrAllocCopy(me->anchor->charset, cp4); + HTAnchor_setUCInfoStage(me->anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_MIME); + } + if (chartrans_ok) { + LYUCcharset *p_in = + HTAnchor_getUCInfoStage(me->anchor, + UCT_STAGE_MIME); + LYUCcharset *p_out = + HTAnchor_setUCInfoStage(me->anchor, + current_char_set, + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + + if (!p_out) + /* + * Try again. + */ + p_out = + HTAnchor_getUCInfoStage(me->anchor, + UCT_STAGE_HTEXT); + + if (!strcmp(p_in->MIMEname, + "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(me->anchor, + HTAnchor_getUCLYhndl(me->anchor, + UCT_STAGE_HTEXT), + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + if (!strcmp(p_out->MIMEname, + "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(me->anchor, + HTAnchor_getUCLYhndl(me->anchor, + UCT_STAGE_MIME), + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + } + if ((p_in->enc != UCT_ENC_CJK) +#ifdef USE_JAPANESEUTF8_SUPPORT + && ((p_in->enc != UCT_ENC_UTF8) + || (p_out->enc != UCT_ENC_CJK)) +#endif + ) { + HTCJK = NOCJK; + if (!(p_in->codepoints & + UCT_CP_SUBSETOF_LAT1) && + chndl == current_char_set) { + HTPassEightBitRaw = TRUE; + } + } else if (p_out->enc == UCT_ENC_CJK) { + Set_HTCJK(p_in->MIMEname, p_out->MIMEname); + } + } else { + /* + * Cannot translate. If according to some heuristic the + * given charset and the current display character both are + * likely to be like ISO-8859 in structure, pretend we have + * some kind of match. + */ + BOOL given_is_8859 = + (BOOL) (!StrNCmp(cp4, "iso-8859-", 9) && + isdigit(UCH(cp4[9]))); + BOOL given_is_8859like = + (BOOL) (given_is_8859 || + !StrNCmp(cp4, "windows-", 8) || + !StrNCmp(cp4, "cp12", 4) || + !StrNCmp(cp4, "cp-12", 5)); + BOOL given_and_display_8859like = + (BOOL) (given_is_8859like && + (strstr(LYchar_set_names[current_char_set], + "ISO-8859") || + strstr(LYchar_set_names[current_char_set], + "windows-"))); + + if (given_and_display_8859like) { + *cp1 = '\0'; + me->format = HTAtom_for(cp); + } + if (given_is_8859) { + cp1 = &cp4[10]; + while (*cp1 && + isdigit(UCH(*cp1))) + cp1++; + *cp1 = '\0'; + } + if (given_and_display_8859like) { + StrAllocCopy(me->anchor->charset, cp4); + HTPassEightBitRaw = TRUE; + } + HTAlert(*cp4 ? cp4 : me->anchor->charset); + } + FREE(cp3); + } else { + /* + * No charset parameter is present. Ignore all other + * parameters, as we do when charset is present. - FM + */ + *cp1 = '\0'; + me->format = HTAtom_for(cp); + } + } + FREE(cp); + } + /* + * If we have an Expires header and haven't already set the no_cache + * element for the anchor, check if we should set it based on that header. + * - FM + */ + if (me->anchor->no_cache == FALSE && + me->anchor->expires != NULL) { + if (!strcmp(me->anchor->expires, "0")) { + /* + * The value is zero, which we treat as an absolute no-cache + * directive. - FM + */ + me->anchor->no_cache = TRUE; + } else if (me->anchor->date != NULL) { + /* + * We have a Date header, so check if the value is less than or + * equal to that. - FM + */ + if (LYmktime(me->anchor->expires, TRUE) <= + LYmktime(me->anchor->date, TRUE)) { + me->anchor->no_cache = TRUE; + } + } else if (LYmktime(me->anchor->expires, FALSE) == 0) { + /* + * We don't have a Date header, and the value is in past for us. - + * FM + */ + me->anchor->no_cache = TRUE; + } + } + StrAllocCopy(me->anchor->content_type, + HTAtom_name(me->format)); + + if (me->set_cookie != NULL || me->set_cookie2 != NULL) { + LYSetCookie(me->set_cookie, + me->set_cookie2, + me->anchor->address); + FREE(me->set_cookie); + FREE(me->set_cookie2); + } + if (me->pickup_redirection) { + if (me->location && *me->location) { + redirecting_url = me->location; + me->location = NULL; + if (me->targetRep != WWW_DEBUG || me->sink) + me->head_only = YES; + + } else { + permanent_redirection = FALSE; + if (me->location) { + CTRACE((tfp, "HTTP: 'Location:' is zero-length!\n")); + HTAlert(REDIRECTION_WITH_BAD_LOCATION); + } + CTRACE((tfp, "HTTP: Failed to pick up location.\n")); + if (me->location) { + FREE(me->location); + } else { + HTAlert(REDIRECTION_WITH_NO_LOCATION); + } + } + } + CTRACE((tfp, "...pumpData finished reading header\n")); + if (me->head_only) { + /* We are done! - kw */ + me->state = MIME_IGNORE; + } else { + + if (me->no_streamstack) { + me->target = me->sink; + } else { + if (!me->compression_encoding) { + CTRACE((tfp, + "HTMIME: MIME Content-Type is '%s', converting to '%s'\n", + HTAtom_name(me->format), HTAtom_name(me->targetRep))); + } else { + /* + * Change the format to that for "www/compressed" and set up a + * stream to deal with it. - FM + */ + CTRACE((tfp, "HTMIME: MIME Content-Type is '%s',\n", + HTAtom_name(me->format))); + me->format = HTAtom_for("www/compressed"); + CTRACE((tfp, " Treating as '%s'. Converting to '%s'\n", + HTAtom_name(me->format), HTAtom_name(me->targetRep))); + FREE(me->compression_encoding); + } + me->target = HTStreamStack(me->format, me->targetRep, + me->sink, me->anchor); + if (!me->target) { + CTRACE((tfp, "HTMIME: Can't translate! ** \n")); + me->target = me->sink; /* Cheat */ + } + } + if (me->target) { + me->targetClass = *me->target->isa; + /* + * Pump rest of data right through, according to the transfer encoding. + */ + me->state = (me->chunked_encoding + ? MIME_CHUNKED + : MIME_TRANSPARENT); + } else { + me->state = MIME_IGNORE; /* What else to do? */ + } + if (me->refresh_url != NULL && !content_is_compressed(me)) { + char *url = NULL; + char *num = NULL; + char *txt = NULL; + const char *base = ""; /* FIXME: refresh_url may be relative to doc */ + + LYParseRefreshURL(me->refresh_url, &num, &url); + if (url != NULL && me->format == WWW_HTML) { + CTRACE((tfp, + "Formatting refresh-url as first line of result\n")); + HTSprintf0(&txt, gettext("Refresh: ")); + HTSprintf(&txt, gettext("%s seconds "), num); + dequote(url); + HTSprintf(&txt, "%s
    ", base, url, url); + CTRACE((tfp, "URL %s%s\n", base, url)); + (me->isa->put_string) (me, txt); + free(txt); + } + FREE(num); + FREE(url); + } + } + CTRACE((tfp, "...end of pumpData, copied %" + PRI_off_t " vs %" + PRI_off_t "\n", + CAST_off_t (me->anchor->actual_length), + CAST_off_t (me->anchor->content_length))); + me->anchor->actual_length = 0; + return HT_OK; +} + +static int dispatchField(HTStream *me) +{ + int i, j; + char *cp; + + *me->value_pointer = '\0'; + + cp = me->value_pointer; + while ((cp > me->value) && *(--cp) == ' ') /* S/390 -- gil -- 0146 */ + /* + * Trim trailing spaces. + */ + *cp = '\0'; + + switch (me->field) { + case miACCEPT_RANGES: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Accept-Ranges: '%s'\n", + me->value)); + break; + case miAGE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Age: '%s'\n", + me->value)); + break; + case miALLOW: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Allow: '%s'\n", + me->value)); + break; + case miALTERNATES: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Alternates: '%s'\n", + me->value)); + break; + case miCACHE_CONTROL: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Cache-Control: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Convert to lowercase and indicate in anchor. - FM + */ + LYLowerCase(me->value); + StrAllocCopy(me->anchor->cache_control, me->value); + /* + * Check whether to set no_cache for the anchor. - FM + */ + { + char *cp1, *cp0 = me->value; + + while ((cp1 = strstr(cp0, "no-cache")) != NULL) { + cp1 += 8; + while (*cp1 != '\0' && WHITE(*cp1)) + cp1++; + if (*cp1 == '\0' || *cp1 == ';') { + me->anchor->no_cache = TRUE; + break; + } + cp0 = cp1; + } + if (me->anchor->no_cache == TRUE) + break; + cp0 = me->value; + while ((cp1 = strstr(cp0, "max-age")) != NULL) { + cp1 += 7; + while (*cp1 != '\0' && WHITE(*cp1)) + cp1++; + if (*cp1 == '=') { + cp1++; + while (*cp1 != '\0' && WHITE(*cp1)) + cp1++; + if (isdigit(UCH(*cp1))) { + cp0 = cp1; + while (isdigit(UCH(*cp1))) + cp1++; + if (*cp0 == '0' && cp1 == (cp0 + 1)) { + me->anchor->no_cache = TRUE; + break; + } + } + } + cp0 = cp1; + } + } + break; + case miCOOKIE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Cookie: '%s'\n", + me->value)); + break; + case miCONNECTION: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Connection: '%s'\n", + me->value)); + break; + case miCONTENT_BASE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Base: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->content_base, me->value); + break; + case miCONTENT_DISPOSITION: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Disposition: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->content_disposition, me->value); + /* + * It's not clear yet from existing RFCs and IDs whether we should be + * looking for file;, attachment;, and/or inline; before the + * filename=value, so we'll just search for "filename" followed by '=' + * and just hope we get the intended value. It is purely a suggested + * name, anyway. - FM + */ + cp = me->anchor->content_disposition; + while (*cp != '\0' && strncasecomp(cp, "filename", 8)) + cp++; + if (*cp == '\0') + break; + cp += 8; + while ((*cp != '\0') && (WHITE(*cp) || *cp == '=')) + cp++; + if (*cp == '\0') + break; + while (*cp != '\0' && WHITE(*cp)) + cp++; + if (*cp == '\0') + break; + StrAllocCopy(me->anchor->SugFname, cp); + if (*me->anchor->SugFname == '"') { + if ((cp = StrChr((me->anchor->SugFname + 1), + '"')) != NULL) { + *(cp + 1) = '\0'; + HTMIME_TrimDoubleQuotes(me->anchor->SugFname); + } else { + FREE(me->anchor->SugFname); + break; + } + } + cp = me->anchor->SugFname; + while (*cp != '\0' && !WHITE(*cp)) + cp++; + *cp = '\0'; + if (*me->anchor->SugFname == '\0') + FREE(me->anchor->SugFname); + break; + case miCONTENT_ENCODING: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Encoding: '%s'\n", + me->value)); + if (me->value[0] == '\0' || + !strcasecomp(me->value, "identity")) + break; + /* + * Convert to lowercase and indicate in anchor. - FM + */ + LYLowerCase(me->value); + StrAllocCopy(me->anchor->content_encoding, me->value); + FREE(me->compression_encoding); + if (content_is_compressed(me)) { + /* + * Save it to use as a flag for setting up a "www/compressed" + * target. - FM + */ + StrAllocCopy(me->compression_encoding, me->value); + } else { + /* + * Some server indicated "8bit", "7bit" or "binary" + * inappropriately. We'll ignore it. - FM + */ + CTRACE((tfp, " Ignoring it!\n")); + } + break; + case miCONTENT_FEATURES: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Features: '%s'\n", + me->value)); + break; + case miCONTENT_LANGUAGE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Language: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Convert to lowercase and indicate in anchor. - FM + */ + LYLowerCase(me->value); + StrAllocCopy(me->anchor->content_language, me->value); + break; + case miCONTENT_LENGTH: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Length: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Convert to integer and indicate in anchor. - FM + */ + me->anchor->content_length = LYatoll(me->value); + if (me->anchor->content_length < 0) + me->anchor->content_length = 0; + CTRACE((tfp, " Converted to integer: '%" PRI_off_t "'\n", + CAST_off_t (me->anchor->content_length))); + break; + case miCONTENT_LOCATION: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Location: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->content_location, me->value); + break; + case miCONTENT_MD5: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-MD5: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->content_md5, me->value); + break; + case miCONTENT_RANGE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Range: '%s'\n", + me->value)); + break; + case miCONTENT_TRANSFER_ENCODING: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Transfer-Encoding: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Force the Content-Transfer-Encoding value to all lower case. - FM + */ + LYLowerCase(me->value); + me->c_t_encoding = HTAtom_for(me->value); + break; + case miCONTENT_TYPE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Content-Type: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Force the Content-Type value to all lower case and strip spaces and + * double-quotes. - FM + */ + for (i = 0, j = 0; me->value[i]; i++) { + if (me->value[i] != ' ' && me->value[i] != '"') { + me->value[j++] = (char) TOLOWER(me->value[i]); + } + } + me->value[j] = '\0'; + me->format = HTAtom_for(me->value); + StrAllocCopy(me->anchor->content_type_params, me->value); + break; + case miDATE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Date: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->date, me->value); + break; + case miETAG: + /* Do not trim double quotes: an entity tag consists of an opaque + * quoted string, possibly prefixed by a weakness indicator. + */ + CTRACE((tfp, "HTMIME: PICKED UP ETag: %s\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->ETag, me->value); + break; + case miEXPIRES: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Expires: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->expires, me->value); + break; + case miKEEP_ALIVE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Keep-Alive: '%s'\n", + me->value)); + break; + case miLAST_MODIFIED: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Last-Modified: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->last_modified, me->value); + break; + case miLINK: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Link: '%s'\n", + me->value)); + break; + case miLOCATION: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Location: '%s'\n", + me->value)); + if (me->pickup_redirection && !me->location) { + StrAllocCopy(me->location, me->value); + } else { + CTRACE((tfp, "HTMIME: *** Ignoring Location!\n")); + } + break; + case miPRAGMA: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Pragma: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Check whether to set no_cache for the anchor. - FM + */ + if (!strcmp(me->value, "no-cache")) + me->anchor->no_cache = TRUE; + break; + case miPROXY_AUTHENTICATE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Proxy-Authenticate: '%s'\n", + me->value)); + break; + case miPUBLIC: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Public: '%s'\n", + me->value)); + break; + case miREFRESH: /* nonstandard: Netscape */ + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Refresh: '%s'\n", + me->value)); + StrAllocCopy(me->refresh_url, me->value); + break; + case miRETRY_AFTER: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Retry-After: '%s'\n", + me->value)); + break; + case miSAFE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Safe: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor if "YES" or "TRUE". - FM + */ + if (!strcasecomp(me->value, "YES") || + !strcasecomp(me->value, "TRUE")) { + me->anchor->safe = TRUE; + } else if (!strcasecomp(me->value, "NO") || + !strcasecomp(me->value, "FALSE")) { + /* + * If server explicitly tells us that it has changed its mind, + * reset flag in anchor. - kw + */ + me->anchor->safe = FALSE; + } + break; + case miSERVER: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Server: '%s'\n", + me->value)); + if (me->value[0] == '\0') + break; + /* + * Indicate in anchor. - FM + */ + StrAllocCopy(me->anchor->server, me->value); + break; + case miSET_COOKIE1: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Set-Cookie: '%s'\n", + me->value)); + if (me->set_cookie == NULL) { + StrAllocCopy(me->set_cookie, me->value); + } else { + StrAllocCat(me->set_cookie, ", "); + StrAllocCat(me->set_cookie, me->value); + } + break; + case miSET_COOKIE2: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Set-Cookie2: '%s'\n", + me->value)); + if (me->set_cookie2 == NULL) { + StrAllocCopy(me->set_cookie2, me->value); + } else { + StrAllocCat(me->set_cookie2, ", "); + StrAllocCat(me->set_cookie2, me->value); + } + break; + case miTITLE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Title: '%s'\n", + me->value)); + break; + case miTRANSFER_ENCODING: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Transfer-Encoding: '%s'\n", + me->value)); + if (!strcmp(me->value, "chunked")) + me->chunked_encoding = YES; + break; + case miUPGRADE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Upgrade: '%s'\n", + me->value)); + break; + case miURI: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP URI: '%s'\n", + me->value)); + break; + case miVARY: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Vary: '%s'\n", + me->value)); + break; + case miVIA: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Via: '%s'\n", + me->value)); + break; + case miWARNING: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP Warning: '%s'\n", + me->value)); + break; + case miWWW_AUTHENTICATE: + HTMIME_TrimDoubleQuotes(me->value); + CTRACE((tfp, "HTMIME: PICKED UP WWW-Authenticate: '%s'\n", + me->value)); + break; + default: /* Should never get here */ + return HT_ERROR; + } + return HT_OK; +} + +/*_________________________________________________________________________ + * + * A C T I O N R O U T I N E S + */ + +/* Character handling + * ------------------ + * + * This is a FSM parser. It ignores field names it does not understand. + * Folded header fields are recognized. Lines without a fieldname at + * the beginning (that are not folded continuation lines) are ignored + * as unknown field names. Fields with empty values are not picked up. + */ +static void HTMIME_put_character(HTStream *me, int c) +{ + if (me->anchor->inHEAD) { + me->anchor->header_length++; + } + switch (me->state) { + begin_transparent: + case MIME_TRANSPARENT: + me->anchor->actual_length += 1; + (me->targetClass.put_character) (me->target, c); + return; + + /* RFC-2616 describes chunked transfer coding */ + case mcCHUNKED_DATA: + (*me->targetClass.put_character) (me->target, c); + me->chunked_size--; + if (me->chunked_size <= 0) + me->state = mcCHUNKED_DATA_CR; + return; + + case mcCHUNKED_DATA_CR: + me->state = mcCHUNKED_DATA_LF; + if (c == CR) { + return; + } + /* FALLTHRU */ + + case mcCHUNKED_DATA_LF: + me->state = MIME_CHUNKED; + if (c == LF) { + return; + } + + CTRACE((tfp, "HTIME_put_character expected LF in chunked data\n")); + me->state = MIME_TRANSPARENT; + goto begin_transparent; + + /* FALLTHRU */ + begin_chunked: + case MIME_CHUNKED: + me->chunked_size = 0; + me->state = mcCHUNKED_COUNT_DIGIT; + + /* FALLTHRU */ + case mcCHUNKED_COUNT_DIGIT: + if (isxdigit(UCH(c))) { + me->chunked_size <<= 4; + if (isdigit(UCH(c))) + me->chunked_size += UCH(c) - '0'; + else + me->chunked_size += TOUPPER(UCH(c)) - 'A' + 10; + return; + } + if (c == ';') + me->state = mcCHUNKED_EXTENSION; + + /* FALLTHRU */ + case mcCHUNKED_EXTENSION: + if (c != CR && c != LF) { + return; + } + me->state = mcCHUNKED_COUNT_CR; + + /* FALLTHRU */ + case mcCHUNKED_COUNT_CR: + me->state = mcCHUNKED_COUNT_LF; + if (c == CR) { + return; + } + + /* FALLTHRU */ + case mcCHUNKED_COUNT_LF: + me->state = ((me->chunked_size != 0) + ? mcCHUNKED_DATA + : MIME_CHUNKED); + if (c == LF) { + return; + } + goto begin_chunked; + + default: + break; + } + + /* + * This slightly simple conversion just strips CR and turns LF to newline. + * On unix LF is \n but on Mac \n is CR for example. See NetToText for an + * implementation which preserves single CR or LF. + */ + if (me->net_ascii) { + /* + * This is evidence that at one time, this code supported + * local character sets other than ASCII. But there is so much + * code in HTTP.c that depends on line_buffer's having been + * translated to local character set that I needed to put the + * FROMASCII translation there, leaving this translation purely + * destructive. -- gil + */ + /* S/390 -- gil -- 0118 */ +#ifndef NOT_ASCII + c = FROMASCII(c); +#endif /* NOT_ASCII */ + if (c == CR) + return; + else if (c == LF) + c = '\n'; + } + + switch (me->state) { + + case MIME_IGNORE: + return; + + case MIME_TRANSPARENT: /* Not reached see above */ + case MIME_CHUNKED: + case mcCHUNKED_COUNT_DIGIT: + case mcCHUNKED_COUNT_CR: + case mcCHUNKED_COUNT_LF: + case mcCHUNKED_EXTENSION: + case mcCHUNKED_DATA: + case mcCHUNKED_DATA_CR: + case mcCHUNKED_DATA_LF: + return; + + case MIME_NET_ASCII: + (*me->targetClass.put_character) (me->target, c); /* MUST BE FAST */ + return; + + case miNEWLINE: + if (c != '\n' && WHITE(c)) { /* Folded line */ + me->state = me->fold_state; /* pop state before newline */ + if (me->state == miGET_VALUE && + me->value_pointer && me->value_pointer != me->value && + !WHITE(*(me->value_pointer - 1))) { + c = ' '; + goto GET_VALUE; /* will add space to value if it fits - kw */ + } + break; + } else if (me->fold_state == miGET_VALUE) { + /* Got a field, and now we know it's complete - so + * act on it. - kw */ + dispatchField(me); + } + /* FALLTHRU */ + + case miBEGINNING_OF_LINE: + me->net_ascii = YES; + switch (c) { + case 'a': + case 'A': + me->state = miA; + CTRACE((tfp, "HTMIME: Got 'A' at beginning of line, state now A\n")); + break; + + case 'c': + case 'C': + me->state = miC; + CTRACE((tfp, "HTMIME: Got 'C' at beginning of line, state now C\n")); + break; + + case 'd': + case 'D': + me->check_pointer = "ate:"; + me->if_ok = miDATE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Got 'D' at beginning of line, checking for 'ate:'\n")); + break; + + case 'e': + case 'E': + me->state = miE; + CTRACE((tfp, "HTMIME: Got 'E' at beginning of line, state now E\n")); + break; + + case 'k': + case 'K': + me->check_pointer = "eep-alive:"; + me->if_ok = miKEEP_ALIVE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Got 'K' at beginning of line, checking for 'eep-alive:'\n")); + break; + + case 'l': + case 'L': + me->state = miL; + CTRACE((tfp, "HTMIME: Got 'L' at beginning of line, state now L\n")); + break; + + case 'p': + case 'P': + me->state = miP; + CTRACE((tfp, "HTMIME: Got 'P' at beginning of line, state now P\n")); + break; + + case 'r': + case 'R': + me->state = miR; + CTRACE((tfp, "HTMIME: Got 'R' at beginning of line, state now R\n")); + break; + + case 's': + case 'S': + me->state = miS; + CTRACE((tfp, "HTMIME: Got 'S' at beginning of line, state now S\n")); + break; + + case 't': + case 'T': + me->state = miT; + CTRACE((tfp, "HTMIME: Got 'T' at beginning of line, state now T\n")); + break; + + case 'u': + case 'U': + me->state = miU; + CTRACE((tfp, "HTMIME: Got 'U' at beginning of line, state now U\n")); + break; + + case 'v': + case 'V': + me->state = miV; + CTRACE((tfp, "HTMIME: Got 'V' at beginning of line, state now V\n")); + break; + + case 'w': + case 'W': + me->state = miW; + CTRACE((tfp, "HTMIME: Got 'W' at beginning of line, state now W\n")); + break; + + case '\n': /* Blank line: End of Header! */ + { + me->anchor->inHEAD = FALSE; + CTRACE((tfp, "HTMIME length %" PRI_off_t "\n", + CAST_off_t (me->anchor->header_length))); + me->net_ascii = NO; + pumpData(me); + } + break; + + default: + goto bad_field_name; + + } /* switch on character */ + break; + + case miA: /* Check for 'c','g' or 'l' */ + switch (c) { + case 'c': + case 'C': + me->check_pointer = "cept-ranges:"; + me->if_ok = miACCEPT_RANGES; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was A, found C, checking for 'cept-ranges:'\n")); + break; + + case 'g': + case 'G': + me->check_pointer = "e:"; + me->if_ok = miAGE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was A, found G, checking for 'e:'\n")); + break; + + case 'l': + case 'L': + me->state = miAL; + CTRACE((tfp, "HTMIME: Was A, found L, state now AL'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'g' or 'l'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miAL: /* Check for 'l' or 't' */ + switch (c) { + case 'l': + case 'L': + me->check_pointer = "ow:"; + me->if_ok = miALLOW; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was AL, found L, checking for 'ow:'\n")); + break; + + case 't': + case 'T': + me->check_pointer = "ernates:"; + me->if_ok = miALTERNATES; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was AL, found T, checking for 'ernates:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'l' or 't'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miC: /* Check for 'a' or 'o' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "che-control:"; + me->if_ok = miCACHE_CONTROL; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was C, found A, checking for 'che-control:'\n")); + break; + + case 'o': + case 'O': + me->state = miCO; + CTRACE((tfp, "HTMIME: Was C, found O, state now CO'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a' or 'o'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miCO: /* Check for 'n' or 'o' */ + switch (c) { + case 'n': + case 'N': + me->state = miCON; + CTRACE((tfp, "HTMIME: Was CO, found N, state now CON\n")); + break; + + case 'o': + case 'O': + me->check_pointer = "kie:"; + me->if_ok = miCOOKIE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was CO, found O, checking for 'kie:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'n' or 'o'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miCON: /* Check for 'n' or 't' */ + switch (c) { + case 'n': + case 'N': + me->check_pointer = "ection:"; + me->if_ok = miCONNECTION; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was CON, found N, checking for 'ection:'\n")); + break; + + case 't': + case 'T': + me->check_pointer = "ent-"; + me->if_ok = miCONTENT_; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was CON, found T, checking for 'ent-'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'n' or 't'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miE: /* Check for 't' or 'x' */ + switch (c) { + case 't': + case 'T': + me->check_pointer = "ag:"; + me->if_ok = miETAG; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was E, found T, checking for 'ag:'\n")); + break; + + case 'x': + case 'X': + me->check_pointer = "pires:"; + me->if_ok = miEXPIRES; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was E, found X, checking for 'pires:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'t' or 'x'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miL: /* Check for 'a', 'i' or 'o' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "st-modified:"; + me->if_ok = miLAST_MODIFIED; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was L, found A, checking for 'st-modified:'\n")); + break; + + case 'i': + case 'I': + me->check_pointer = "nk:"; + me->if_ok = miLINK; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was L, found I, checking for 'nk:'\n")); + break; + + case 'o': + case 'O': + me->check_pointer = "cation:"; + me->if_ok = miLOCATION; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was L, found O, checking for 'cation:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a', 'i' or 'o'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miP: /* Check for 'r' or 'u' */ + switch (c) { + case 'r': + case 'R': + me->state = miPR; + CTRACE((tfp, "HTMIME: Was P, found R, state now PR'\n")); + break; + + case 'u': + case 'U': + me->check_pointer = "blic:"; + me->if_ok = miPUBLIC; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was P, found U, checking for 'blic:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'r' or 'u'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miPR: /* Check for 'a' or 'o' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "gma:"; + me->if_ok = miPRAGMA; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was PR, found A, checking for 'gma'\n")); + break; + + case 'o': + case 'O': + me->check_pointer = "xy-authenticate:"; + me->if_ok = miPROXY_AUTHENTICATE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was PR, found O, checking for 'xy-authenticate'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a' or 'o'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miR: /* Check for 'e' */ + switch (c) { + case 'e': + case 'E': + me->state = miRE; + CTRACE((tfp, "HTMIME: Was R, found E\n")); + break; + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'e'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miRE: /* Check for 'a' or 'o' */ + switch (c) { + case 'f': + case 'F': /* nonstandard: Netscape */ + me->check_pointer = "resh:"; + me->if_ok = miREFRESH; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was RE, found F, checking for '%s'\n", me->check_pointer)); + break; + + case 't': + case 'T': + me->check_pointer = "ry-after:"; + me->if_ok = miRETRY_AFTER; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was RE, found T, checking for '%s'\n", me->check_pointer)); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'f' or 't'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miS: /* Check for 'a' or 'e' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "fe:"; + me->if_ok = miSAFE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was S, found A, checking for 'fe:'\n")); + break; + + case 'e': + case 'E': + me->state = miSE; + CTRACE((tfp, "HTMIME: Was S, found E, state now SE'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a' or 'e'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miSE: /* Check for 'r' or 't' */ + switch (c) { + case 'r': + case 'R': + me->check_pointer = "ver:"; + me->if_ok = miSERVER; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was SE, found R, checking for 'ver'\n")); + break; + + case 't': + case 'T': + me->check_pointer = "-cookie"; + me->if_ok = miSET_COOKIE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was SE, found T, checking for '-cookie'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'r' or 't'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miSET_COOKIE: /* Check for ':' or '2' */ + switch (c) { + case ':': + me->field = miSET_COOKIE1; /* remember it */ + me->state = miSKIP_GET_VALUE; + CTRACE((tfp, "HTMIME: Was SET_COOKIE, found :, processing\n")); + break; + + case '2': + me->check_pointer = ":"; + me->if_ok = miSET_COOKIE2; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was SET_COOKIE, found 2, checking for ':'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "':' or '2'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miT: /* Check for 'i' or 'r' */ + switch (c) { + case 'i': + case 'I': + me->check_pointer = "tle:"; + me->if_ok = miTITLE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was T, found I, checking for 'tle:'\n")); + break; + + case 'r': + case 'R': + me->check_pointer = "ansfer-encoding:"; + me->if_ok = miTRANSFER_ENCODING; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was T, found R, checking for 'ansfer-encoding'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'i' or 'r'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miU: /* Check for 'p' or 'r' */ + switch (c) { + case 'p': + case 'P': + me->check_pointer = "grade:"; + me->if_ok = miUPGRADE; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was U, found P, checking for 'grade:'\n")); + break; + + case 'r': + case 'R': + me->check_pointer = "i:"; + me->if_ok = miURI; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was U, found R, checking for 'i:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'p' or 'r'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miV: /* Check for 'a' or 'i' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "ry:"; + me->if_ok = miVARY; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was V, found A, checking for 'ry:'\n")); + break; + + case 'i': + case 'I': + me->check_pointer = "a:"; + me->if_ok = miVIA; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was V, found I, checking for 'a:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a' or 'i'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miW: /* Check for 'a' or 'w' */ + switch (c) { + case 'a': + case 'A': + me->check_pointer = "rning:"; + me->if_ok = miWARNING; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was W, found A, checking for 'rning:'\n")); + break; + + case 'w': + case 'W': + me->check_pointer = "w-authenticate:"; + me->if_ok = miWWW_AUTHENTICATE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was W, found W, checking for 'w-authenticate:'\n")); + break; + + default: + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, "'a' or 'w'")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miCHECK: /* Check against string */ + if (TOLOWER(c) == *(me->check_pointer)++) { + if (!*me->check_pointer) + me->state = me->if_ok; + } else { /* Error */ + CTRACE((tfp, + "HTMIME: Bad character `%c' found where `%s' expected\n", + c, me->check_pointer - 1)); + goto bad_field_name; + } + break; + + case miCONTENT_: + CTRACE((tfp, "HTMIME: in case CONTENT_\n")); + + switch (c) { + case 'b': + case 'B': + me->check_pointer = "ase:"; + me->if_ok = miCONTENT_BASE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found B, checking for 'ase:'\n")); + break; + + case 'd': + case 'D': + me->check_pointer = "isposition:"; + me->if_ok = miCONTENT_DISPOSITION; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found D, checking for 'isposition:'\n")); + break; + + case 'e': + case 'E': + me->check_pointer = "ncoding:"; + me->if_ok = miCONTENT_ENCODING; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found E, checking for 'ncoding:'\n")); + break; + + case 'f': + case 'F': + me->check_pointer = "eatures:"; + me->if_ok = miCONTENT_FEATURES; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found F, checking for 'eatures:'\n")); + break; + + case 'l': + case 'L': + me->state = miCONTENT_L; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found L, state now CONTENT_L\n")); + break; + + case 'm': + case 'M': + me->check_pointer = "d5:"; + me->if_ok = miCONTENT_MD5; + me->state = miCHECK; + CTRACE((tfp, "HTMIME: Was CONTENT_, found M, checking for 'd5:'\n")); + break; + + case 'r': + case 'R': + me->check_pointer = "ange:"; + me->if_ok = miCONTENT_RANGE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found R, checking for 'ange:'\n")); + break; + + case 't': + case 'T': + me->state = miCONTENT_T; + CTRACE((tfp, + "HTMIME: Was CONTENT_, found T, state now CONTENT_T\n")); + break; + + default: + CTRACE((tfp, "HTMIME: Was CONTENT_, found nothing; bleah\n")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miCONTENT_L: + CTRACE((tfp, "HTMIME: in case CONTENT_L\n")); + + switch (c) { + case 'a': + case 'A': + me->check_pointer = "nguage:"; + me->if_ok = miCONTENT_LANGUAGE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_L, found A, checking for 'nguage:'\n")); + break; + + case 'e': + case 'E': + me->check_pointer = "ngth:"; + me->if_ok = miCONTENT_LENGTH; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_L, found E, checking for 'ngth:'\n")); + break; + + case 'o': + case 'O': + me->check_pointer = "cation:"; + me->if_ok = miCONTENT_LOCATION; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_L, found O, checking for 'cation:'\n")); + break; + + default: + CTRACE((tfp, "HTMIME: Was CONTENT_L, found nothing; bleah\n")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miCONTENT_T: + CTRACE((tfp, "HTMIME: in case CONTENT_T\n")); + + switch (c) { + case 'r': + case 'R': + me->check_pointer = "ansfer-encoding:"; + me->if_ok = miCONTENT_TRANSFER_ENCODING; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_T, found R, checking for 'ansfer-encoding:'\n")); + break; + + case 'y': + case 'Y': + me->check_pointer = "pe:"; + me->if_ok = miCONTENT_TYPE; + me->state = miCHECK; + CTRACE((tfp, + "HTMIME: Was CONTENT_T, found Y, checking for 'pe:'\n")); + break; + + default: + CTRACE((tfp, "HTMIME: Was CONTENT_T, found nothing; bleah\n")); + goto bad_field_name; + + } /* switch on character */ + break; + + case miACCEPT_RANGES: + case miAGE: + case miALLOW: + case miALTERNATES: + case miCACHE_CONTROL: + case miCOOKIE: + case miCONNECTION: + case miCONTENT_BASE: + case miCONTENT_DISPOSITION: + case miCONTENT_ENCODING: + case miCONTENT_FEATURES: + case miCONTENT_LANGUAGE: + case miCONTENT_LENGTH: + case miCONTENT_LOCATION: + case miCONTENT_MD5: + case miCONTENT_RANGE: + case miCONTENT_TRANSFER_ENCODING: + case miCONTENT_TYPE: + case miDATE: + case miETAG: + case miEXPIRES: + case miKEEP_ALIVE: + case miLAST_MODIFIED: + case miLINK: + case miLOCATION: + case miPRAGMA: + case miPROXY_AUTHENTICATE: + case miPUBLIC: + case miREFRESH: + case miRETRY_AFTER: + case miSAFE: + case miSERVER: + case miSET_COOKIE1: + case miSET_COOKIE2: + case miTITLE: + case miTRANSFER_ENCODING: + case miUPGRADE: + case miURI: + case miVARY: + case miVIA: + case miWARNING: + case miWWW_AUTHENTICATE: + me->field = me->state; /* remember it */ + me->state = miSKIP_GET_VALUE; + /* FALLTHRU */ + + case miSKIP_GET_VALUE: + if (c == '\n') { + me->fold_state = me->state; + me->state = miNEWLINE; + break; + } + if (WHITE(c)) + /* + * Skip white space. + */ + break; + + me->value_pointer = me->value; + me->state = miGET_VALUE; + /* Fall through to store first character */ + /* FALLTHRU */ + + case miGET_VALUE: + GET_VALUE: + if (c != '\n') { /* Not end of line */ + if (me->value_pointer < me->value + VALUE_SIZE - 1) { + *me->value_pointer++ = (char) c; + break; + } else { + goto value_too_long; + } + } + /* Fall through (if end of line) */ + /* FALLTHRU */ + + case miJUNK_LINE: + if (c == '\n') { + me->fold_state = me->state; + me->state = miNEWLINE; + } + break; + + } /* switch on state */ + + HTChunkPutc(&me->anchor->http_headers, UCH(c)); + if (me->state == MIME_TRANSPARENT) { + HTChunkTerminate(&me->anchor->http_headers); + CTRACE((tfp, "Server Headers (%d bytes):\n%.*s\n", + me->anchor->http_headers.size, + me->anchor->http_headers.size, + me->anchor->http_headers.data)); + CTRACE((tfp, "Server Content-Type:%s\n", + me->anchor->content_type_params)); + } + return; + + value_too_long: + CTRACE((tfp, "HTMIME: *** Syntax error. (string too long)\n")); + + bad_field_name: /* Ignore it */ + me->state = miJUNK_LINE; + + HTChunkPutc(&me->anchor->http_headers, UCH(c)); + + return; +} + +/* String handling + * --------------- + * + * Strings must be smaller than this buffer size. + */ +static void HTMIME_put_string(HTStream *me, + const char *s) +{ + const char *p; + + if (me->state == MIME_TRANSPARENT) { /* Optimisation */ + (*me->targetClass.put_string) (me->target, s); + + } else if (me->state != MIME_IGNORE) { + CTRACE((tfp, "HTMIME: %s\n", s)); + + for (p = s; *p; p++) + HTMIME_put_character(me, *p); + } +} + +/* Buffer write. Buffers can (and should!) be big. + * ------------ + */ +static void HTMIME_write(HTStream *me, + const char *s, + int l) +{ + const char *p; + + if (me->state == MIME_TRANSPARENT) { /* Optimisation */ + (*me->targetClass.put_block) (me->target, s, l); + + } else { + CTRACE((tfp, "HTMIME: %.*s\n", l, s)); + + for (p = s; p < s + l; p++) + HTMIME_put_character(me, *p); + } +} + +/* Free an HTML object + * ------------------- + * + */ +static void HTMIME_free(HTStream *me) +{ + if (me) { + FREE(me->location); + FREE(me->compression_encoding); + if (me->target) + (*me->targetClass._free) (me->target); + FREE(me); + } +} + +/* End writing +*/ +static void HTMIME_abort(HTStream *me, + HTError e) +{ + if (me) { + FREE(me->location); + FREE(me->compression_encoding); + if (me->target) + (*me->targetClass._abort) (me->target, e); + FREE(me); + } +} + +/* Structured Object Class + * ----------------------- + */ +static const HTStreamClass HTMIME = +{ + "MIMEParser", + HTMIME_free, + HTMIME_abort, + HTMIME_put_character, + HTMIME_put_string, + HTMIME_write +}; + +/* Subclass-specific Methods + * ------------------------- + */ +HTStream *HTMIMEConvert(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + HTStream *me; + + CTRACE((tfp, "HTMIMEConvert\n")); + me = typecalloc(HTStream); + + if (me == NULL) + outofmem(__FILE__, "HTMIMEConvert"); + + me->isa = &HTMIME; + me->sink = sink; + me->anchor = anchor; + me->anchor->safe = FALSE; + me->anchor->no_cache = FALSE; + + FREE(me->anchor->cache_control); + FREE(me->anchor->SugFname); + FREE(me->anchor->charset); + + HTChunkClear(&me->anchor->http_headers); + HTChunkInit(&me->anchor->http_headers, 128); + + FREE(me->anchor->content_type_params); + FREE(me->anchor->content_language); + FREE(me->anchor->content_encoding); + FREE(me->anchor->content_base); + FREE(me->anchor->content_disposition); + FREE(me->anchor->content_location); + FREE(me->anchor->content_md5); + + me->anchor->inHEAD = TRUE; + me->anchor->header_length = 0; + me->anchor->content_length = 0; + + FREE(me->anchor->date); + FREE(me->anchor->expires); + FREE(me->anchor->last_modified); + FREE(me->anchor->ETag); + FREE(me->anchor->server); + + me->target = NULL; + me->state = miBEGINNING_OF_LINE; + me->format = HTAtom_for(ContentTypes[LYContentType]); + + CTRACE((tfp, "default Content-Type is %s\n", HTAtom_name(me->format))); + me->targetRep = pres->rep_out; + me->boundary = NULL; /* Not set yet */ + me->set_cookie = NULL; /* Not set yet */ + me->set_cookie2 = NULL; /* Not set yet */ + me->refresh_url = NULL; /* Not set yet */ + me->c_t_encoding = 0; /* Not set yet */ + me->compression_encoding = NULL; /* Not set yet */ + me->net_ascii = NO; /* Local character set */ + + HTAnchor_setUCInfoStage(me->anchor, current_char_set, + UCT_STAGE_STRUCTURED, + UCT_SETBY_DEFAULT); + HTAnchor_setUCInfoStage(me->anchor, current_char_set, + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + return me; +} + +HTStream *HTNetMIME(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + HTStream *me = HTMIMEConvert(pres, anchor, sink); + + if (!me) + return NULL; + + me->net_ascii = YES; + return me; +} + +HTStream *HTMIMERedirect(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + HTStream *me = HTMIMEConvert(pres, anchor, sink); + + if (!me) + return NULL; + + me->pickup_redirection = YES; + if (me->targetRep == WWW_DEBUG && sink) + me->no_streamstack = YES; + return me; +} + +/* Japanese header handling functions + * ================================== + * + * K&Rized and added 07-Jun-96 by FM, based on: + * +//////////////////////////////////////////////////////////////////////// + * + * ISO-2022-JP handling routines + * & + * MIME decode routines (quick hack just for ISO-2022-JP) + * + * Thu Jan 25 10:11:42 JST 1996 + * + * Copyright (C) 1994, 1995, 1996 + * Shuichi Ichikawa (ichikawa@nuee.nagoya-u.ac.jp) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either versions 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SKK, see the file COPYING. If not, write to the Free + * Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * MIME decoding routines + * + * Written by S. Ichikawa, + * partially inspired by encdec.c of . + * Caller's buffers decode to no longer than the input strings. + */ +#include /* S/390 -- gil -- 0163 */ + +static char HTmm64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +static char HTmmquote[] = "0123456789ABCDEF"; +static int HTmmcont = 0; + +static void HTmmdec_base64(char **t, + char *s) +{ + int d, count, j, val; + char *buf, *bp, nw[4], *p; + + if ((buf = typeMallocn(char, strlen(s) * 3 + 1)) == 0) + outofmem(__FILE__, "HTmmdec_base64"); + + for (bp = buf; *s; s += 4) { + val = 0; + if (s[2] == '=') + count = 1; + else if (s[3] == '=') + count = 2; + else + count = 3; + + for (j = 0; j <= count; j++) { + if (!(p = StrChr(HTmm64, s[j]))) { + FREE(buf); + return; + } + d = (int) (p - HTmm64); + d <<= (3 - j) * 6; + val += d; + } + for (j = 2; j >= 0; j--) { + nw[j] = (char) (val & 255); + val >>= 8; + } + if (count--) + *bp++ = nw[0]; + if (count--) + *bp++ = nw[1]; + if (count) + *bp++ = nw[2]; + } + *bp = '\0'; + StrAllocCopy(*t, buf); + FREE(buf); +} + +static void HTmmdec_quote(char **t, + char *s) +{ + char *buf, cval, *bp, *p; + + if ((buf = typeMallocn(char, strlen(s) + 1)) == 0) + outofmem(__FILE__, "HTmmdec_quote"); + + for (bp = buf; *s;) { + if (*s == '=') { + cval = 0; + if (s[1] && (p = StrChr(HTmmquote, s[1]))) { + cval = (char) (cval + (char) (p - HTmmquote)); + } else { + *bp++ = *s++; + continue; + } + if (s[2] && (p = StrChr(HTmmquote, s[2]))) { + cval = (char) (cval << 4); + cval = (char) (cval + (p - HTmmquote)); + *bp++ = cval; + s += 3; + } else { + *bp++ = *s++; + } + } else if (*s == '_') { + *bp++ = 0x20; + s++; + } else { + *bp++ = *s++; + } + } + *bp = '\0'; + StrAllocCopy(*t, buf); + FREE(buf); +} + +/* + * HTmmdecode for ISO-2022-JP - FM + */ +void HTmmdecode(char **target, + char *source) +{ + char *buf; + char *mmbuf = NULL; + char *m2buf = NULL; + char *s, *t, *u; + int base64, quote; + + if ((buf = typeMallocn(char, strlen(source) + 1)) == 0) + outofmem(__FILE__, "HTmmdecode"); + + for (s = source, *(u = buf) = '\0'; *s;) { + if (!strncasecomp(s, "=?ISO-2022-JP?B?", 16)) { + base64 = 1; + } else { + base64 = 0; + } + if (!strncasecomp(s, "=?ISO-2022-JP?Q?", 16)) { + quote = 1; + } else { + quote = 0; + } + if (base64 || quote) { + if (HTmmcont) { + for (t = s - 1; + t >= source && (*t == ' ' || *t == '\t'); t--) { + u--; + } + } + if (mmbuf == 0) /* allocate buffer big enough for source */ + StrAllocCopy(mmbuf, source); + for (s += 16, t = mmbuf; *s;) { + if (s[0] == '?' && s[1] == '=') { + break; + } else { + *t++ = *s++; + *t = '\0'; + } + } + if (s[0] != '?' || s[1] != '=') { + goto end; + } else { + s += 2; + *t = '\0'; + } + if (base64) + HTmmdec_base64(&m2buf, mmbuf); + else + HTmmdec_quote(&m2buf, mmbuf); + for (t = m2buf; non_empty(t);) + *u++ = *t++; + HTmmcont = 1; + } else { + if (*s != ' ' && *s != '\t') + HTmmcont = 0; + *u++ = *s++; + } + } + *u = '\0'; + end: + StrAllocCopy(*target, buf); + FREE(m2buf); + FREE(mmbuf); + FREE(buf); +} + +/* + * Insert ESC where it seems lost. + * (The author of this function "rjis" is S. Ichikawa.) + */ +int HTrjis(char **t, + char *s) +{ + char *p; + char *buf = NULL; + int kanji = 0; + + if (StrChr(s, CH_ESC) || !StrChr(s, '$')) { + if (s != *t) + StrAllocCopy(*t, s); + return 1; + } + + if ((buf = typeMallocn(char, strlen(s) * 2 + 1)) == 0) + outofmem(__FILE__, "HTrjis"); + + for (p = buf; *s;) { + if (!kanji && s[0] == '$' && (s[1] == '@' || s[1] == 'B')) { + if (HTmaybekanji((int) s[2], (int) s[3])) { + kanji = 1; + *p++ = CH_ESC; + *p++ = *s++; + *p++ = *s++; + *p++ = *s++; + *p++ = *s++; + continue; + } + *p++ = *s++; + continue; + } + if (kanji && s[0] == '(' && (s[1] == 'J' || s[1] == 'B')) { + kanji = 0; + *p++ = CH_ESC; + *p++ = *s++; + *p++ = *s++; + continue; + } + *p++ = *s++; + } + *p = *s; /* terminate string */ + + StrAllocCopy(*t, buf); + FREE(buf); + return 0; +} + +/* + * The following function "maybekanji" is derived from + * RJIS-1.0 by Mr. Hironobu Takahashi. + * Maybekanji() is included here under the courtesy of the author. + * The original comment of rjis.c is also included here. + */ +/* + * RJIS ( Recover JIS code from broken file ) + * Copyright (C) 1992 1994 + * Hironobu Takahashi (takahasi@tiny.or.jp) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either versions 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SKK, see the file COPYING. If not, write to the Free + * Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +int HTmaybekanji(int c1, + int c2) +{ + + if ((c2 < 33) || (c2 > 126)) + return 0; + if ((c1 < 33) || ((40 < c1) && (c1 < 48)) || (116 < c1)) + return 0; + c2 -= 32; + switch (c1 - 32) { + case 2: + if ((14 < c2) && (c2 < 26)) + return 0; + if ((33 < c2) && (c2 < 42)) + return 0; + if ((48 < c2) && (c2 < 60)) + return 0; + if ((74 < c2) && (c2 < 82)) + return 0; + if ((89 < c2) && (c2 < 94)) + return 0; + break; + case 3: + if (c2 < 16) + return 0; + if ((25 < c2) && (c2 < 33)) + return 0; + if ((58 < c2) && (c2 < 65)) + return 0; + if (90 < c2) + return 0; + break; + case 4: + if (83 < c2) + return 0; + break; + case 5: + if (86 < c2) + return 0; + break; + case 6: + if ((24 < c2) && (c2 < 33)) + return 0; + if (56 < c2) + return 0; + break; + case 7: + if ((33 < c2) && (c2 < 49)) + return 0; + if (81 < c2) + return 0; + break; + case 8: + if (32 < c2) + return 0; + break; + case 47: + if (51 < c2) + return 0; + break; + case 84: + if (6 < c2) + return 0; + break; + } + return 1; +} diff --git a/WWW/Library/Implementation/HTMIME.h b/WWW/Library/Implementation/HTMIME.h new file mode 100644 index 0000000..6410c15 --- /dev/null +++ b/WWW/Library/Implementation/HTMIME.h @@ -0,0 +1,84 @@ +/* /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTMIME.html + MIME PARSER + + The MIME parser stream presents a MIME document. It recursively invokes the + format manager to handle embedded formats. + + As well as stripping off and parsing the headers, the MIME parser has to + parse any weirld MIME encodings it may meet within the body parts of + messages, and must deal with multipart messages. + + This module is implemented to the level necessary for operation with WWW, + but is not currently complete for any arbitrary MIME message. + + Check the source for latest additions to functionality. + + The MIME parser is complicated by the fact that WWW allows real binary to be + sent, not ASCII encoded. Therefore the netascii decoding is included in + this module. One cannot layer it by converting first from Net to local + text, then decoding it. Of course, for local files, the net ascii decoding + is not needed. There are therefore two creation routines. + + */ +#ifndef HTMIME_H +#define HTMIME_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * This function is for trimming off any paired + * open- and close-double quotes from header values. + * It does not parse the string for embedded quotes, + * and will not modify the string unless both the + * first and last characters are double-quotes. - FM + */ extern void HTMIME_TrimDoubleQuotes(char *value); + +/* + + INPUT: LOCAL TEXT + + */ + extern HTStream *HTMIMEConvert(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + +/* + + INPUT: NET ASCII + + */ + extern HTStream *HTNetMIME(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + +/* + + INPUT: Redirection message, parse headers only for Location if present + + */ + extern HTStream *HTMIMERedirect(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + +/* + + For handling Japanese headers. + +*/ + extern void HTmmdecode(char **target, + char *source); + + extern int HTrjis(char **target, + char *source); + + extern int HTmaybekanji(int c1, + int c2); + +#ifdef __cplusplus +} +#endif +#endif /* !HTMIME_H */ diff --git a/WWW/Library/Implementation/HTMLDTD.c b/WWW/Library/Implementation/HTMLDTD.c new file mode 100644 index 0000000..382c141 --- /dev/null +++ b/WWW/Library/Implementation/HTMLDTD.c @@ -0,0 +1,334 @@ +/* + * $LynxId: HTMLDTD.c,v 1.58 2021/07/23 00:00:03 tom Exp $ + * + * Our Static DTD for HTML + * ----------------------- + */ + +/* Implements: +*/ + +#include +#include +#include +#include + +/* + * Character entities like   now excluded from our DTD tables, they are + * mapped to Unicode and handled by chartrans code directly the similar way the + * numeric entities like { does. See src/chrtrans/entities.h for real + * mapping. + */ + +/* Entity Names + * ------------ + * + * This table must be matched exactly with ALL the translation tables + * (this is an obsolete translation mechanism, probably unused, + * currently replaced with Unicode chartrans in most cases...) + */ +static const char *entities[] = +{ + "AElig", /* capital AE diphthong (ligature) */ + "Aacute", /* capital A, acute accent */ + "Acirc", /* capital A, circumflex accent */ + "Agrave", /* capital A, grave accent */ + "Aring", /* capital A, ring */ + "Atilde", /* capital A, tilde */ + "Auml", /* capital A, dieresis or umlaut mark */ + "Ccedil", /* capital C, cedilla */ + "Dstrok", /* capital Eth, Icelandic */ + "ETH", /* capital Eth, Icelandic */ + "Eacute", /* capital E, acute accent */ + "Ecirc", /* capital E, circumflex accent */ + "Egrave", /* capital E, grave accent */ + "Euml", /* capital E, dieresis or umlaut mark */ + "Iacute", /* capital I, acute accent */ + "Icirc", /* capital I, circumflex accent */ + "Igrave", /* capital I, grave accent */ + "Iuml", /* capital I, dieresis or umlaut mark */ + "Ntilde", /* capital N, tilde */ + "Oacute", /* capital O, acute accent */ + "Ocirc", /* capital O, circumflex accent */ + "Ograve", /* capital O, grave accent */ + "Oslash", /* capital O, slash */ + "Otilde", /* capital O, tilde */ + "Ouml", /* capital O, dieresis or umlaut mark */ + "THORN", /* capital THORN, Icelandic */ + "Uacute", /* capital U, acute accent */ + "Ucirc", /* capital U, circumflex accent */ + "Ugrave", /* capital U, grave accent */ + "Uuml", /* capital U, dieresis or umlaut mark */ + "Yacute", /* capital Y, acute accent */ + "aacute", /* small a, acute accent */ + "acirc", /* small a, circumflex accent */ + "acute", /* spacing acute */ + "aelig", /* small ae diphthong (ligature) */ + "agrave", /* small a, grave accent */ + "amp", /* ampersand */ + "aring", /* small a, ring */ + "atilde", /* small a, tilde */ + "auml", /* small a, dieresis or umlaut mark */ + "brkbar", /* broken vertical bar */ + "brvbar", /* broken vertical bar */ + "ccedil", /* small c, cedilla */ + "cedil", /* spacing cedilla */ + "cent", /* cent sign */ + "copy", /* copyright sign */ + "curren", /* currency sign */ + "deg", /* degree sign */ + "die", /* spacing dieresis */ + "divide", /* division sign */ + "eacute", /* small e, acute accent */ + "ecirc", /* small e, circumflex accent */ + "egrave", /* small e, grave accent */ + "emdash", /* dash the width of emsp */ + "emsp", /* em space - not collapsed */ + "endash", /* dash the width of ensp */ + "ensp", /* en space - not collapsed */ + "eth", /* small eth, Icelandic */ + "euml", /* small e, dieresis or umlaut mark */ + "frac12", /* fraction 1/2 */ + "frac14", /* fraction 1/4 */ + "frac34", /* fraction 3/4 */ + "gt", /* greater than */ + "hibar", /* spacing macron */ + "iacute", /* small i, acute accent */ + "icirc", /* small i, circumflex accent */ + "iexcl", /* inverted exclamation mark */ + "igrave", /* small i, grave accent */ + "iquest", /* inverted question mark */ + "iuml", /* small i, dieresis or umlaut mark */ + "laquo", /* angle quotation mark, left */ + "lt", /* less than */ + "macr", /* spacing macron */ + "mdash", /* dash the width of emsp */ + "micro", /* micro sign */ + "middot", /* middle dot */ + "nbsp", /* non breaking space */ + "ndash", /* dash the width of ensp */ + "not", /* negation sign */ + "ntilde", /* small n, tilde */ + "oacute", /* small o, acute accent */ + "ocirc", /* small o, circumflex accent */ + "ograve", /* small o, grave accent */ + "ordf", /* feminine ordinal indicator */ + "ordm", /* masculine ordinal indicator */ + "oslash", /* small o, slash */ + "otilde", /* small o, tilde */ + "ouml", /* small o, dieresis or umlaut mark */ + "para", /* paragraph sign */ + "plusmn", /* plus-or-minus sign */ + "pound", /* pound sign */ + "quot", /* quote '"' */ + "raquo", /* angle quotation mark, right */ + "reg", /* circled R registered sign */ + "sect", /* section sign */ + "shy", /* soft hyphen */ + "sup1", /* superscript 1 */ + "sup2", /* superscript 2 */ + "sup3", /* superscript 3 */ + "szlig", /* small sharp s, German (sz ligature) */ + "thinsp", /* thin space (not collapsed) */ + "thorn", /* small thorn, Icelandic */ + "times", /* multiplication sign */ + "trade", /* trade mark sign (U+2122) */ + "uacute", /* small u, acute accent */ + "ucirc", /* small u, circumflex accent */ + "ugrave", /* small u, grave accent */ + "uml", /* spacing dieresis */ + "uuml", /* small u, dieresis or umlaut mark */ + "yacute", /* small y, acute accent */ + "yen", /* yen sign */ + "yuml", /* small y, dieresis or umlaut mark */ +}; + +/* Attribute Lists + * --------------- + * + * Lists must be in alphabetical order by attribute name + * The tag elements contain the number of attributes + */ + +/* From Peter Flynn's intro to the HTML Pro DTD: + + %structure; + + DIV, CENTER, H1 to H6, P, UL, OL, DL, DIR, MENU, PRE, XMP, LISTING, BLOCKQUOTE, BQ, + 2 1 2 2 1 8 8 8 8 8 8 8 8 4 4 + MULTICOL,?NOBR, FORM, TABLE, ADDRESS, FIG, BDO, NOTE, and FN; plus?WBR, LI, and LH + 8 n ?1 n 8 8 2 2 2 2 2 ?1 nE 4 4 + + %insertions; + + Elements which usually contain special-purpose material, or no text material at all. + + BASEFONT, APPLET, OBJECT, EMBED, SCRIPT, MAP, MARQUEE, HR, ISINDEX, BGSOUND, TAB,?IMG, + 1 e? 2 2 l 1 e 2 l 8 4 4 E 1? E 1 E ! E ?1 E + IMAGE, BR, plus NOEMBED, SERVER, SPACER, AUDIOSCOPE, and SIDEBAR; ?area + 1 n 1 E n n n n n 8 E + + %text; + + Elements within the %structure; which directly contain running text. + + Descriptive or analytic markup: EM, STRONG, DFN, CODE, SAMP, KBD, VAR, CITE, Q, LANG, AU, + 2 2 2 2 2 2 2 2 2 2 n 2 + AUTHOR, PERSON, ACRONYM, ABBR, INS, DEL, and SPAN + 2 2 n 2 2 2 2 2 + Visual markup:S, STRIKE, I, B, TT, U,?NOBR,?WBR, BR, BIG, SMALL, FONT, STYLE, BLINK, TAB, + 1 1 1 1 1 1 ?1 n ?1nE? 1 E 1 1 1 1 l 1 1 E? + BLACKFACE, LIMITTEXT, NOSMARTQUOTES, and SHADOW + 1 n 1 n 1 n 1 n + Hypertext and graphics: A and?IMG + 8 ?8 E + Mathematical: SUB, SUP, and MATH + 4 4 4 l + Documentary: COMMENT, ENTITY, ELEMENT, and ATTRIB + 4 4 n 4 n 4 n + %formula; + */ + +/* Elements + * -------- + * + * Must match definitions in HTMLDTD.html! + * Must be in alphabetical order. + * + * The T_* extra info is listed here, even though most fields are not used + * in SGML.c if Old_DTD is set (with the exception of some Tgf_* flags). + * This simplifies comparison of the tags_table0[] table (otherwise unchanged + * from original Lynx treatment) with the tags_table1[] table below. - kw + * + * Name*, Attributes, No. of attributes, content, extra info... + */ + +#include +#include + +/* Dummy space, will be filled with the contents of either tags_table1 + or tags_table0 on calling HTSwitchDTD - kw */ + +static HTTag tags[HTML_ALL_ELEMENTS]; + +const SGML_dtd HTML_dtd = +{ + tags, + HTML_ELEMENTS, + entities, /* probably unused */ + TABLESIZE(entities), +}; + +/* This function fills the "tags" part of the HTML_dtd structure with + what we want to use, either tags_table0 or tags_table1. Note that it + has to be called at least once before HTML_dtd is used, otherwise + the HTML_dtd contents will be invalid! This could be coded in a way + that would make an initialisation call unnecessary, but my C knowledge + is limited and I didn't want to list the whole tags_table1 table + twice... - kw */ +void HTSwitchDTD(int new_flag) +{ + if (TRACE) + CTRACE((tfp, + "HTMLDTD: Copying %s DTD element info of size %d, %d * %d\n", + new_flag ? "strict" : "tagsoup", + (int) (new_flag ? sizeof(tags_table1) : sizeof(tags_table0)), + HTML_ALL_ELEMENTS, + (int) sizeof(HTTag))); + if (new_flag) + MemCpy(tags, tags_table1, HTML_ALL_ELEMENTS * sizeof(HTTag)); + else + MemCpy(tags, tags_table0, HTML_ALL_ELEMENTS * sizeof(HTTag)); +} + +HTTag HTTag_unrecognized = + +{NULL_HTTag, NULL, 0, 0, SGML_EMPTY, T__UNREC_, 0, 0}; + +/* + * Utility Routine: Useful for people building HTML objects. + */ + +/* Start anchor element + * -------------------- + * + * It is kinda convenient to have a particulr routine for + * starting an anchor element, as everything else for HTML is + * simple anyway. + */ +struct _HTStructured { + HTStructuredClass *isa; + /* ... */ +}; + +void HTStartAnchor(HTStructured * obj, const char *name, + const char *href) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = NO; + + if (name && *name) { + present[HTML_A_NAME] = YES; + value[HTML_A_NAME] = (const char *) name; + } + if (href) { + present[HTML_A_HREF] = YES; + value[HTML_A_HREF] = (const char *) href; + } + + (*obj->isa->start_element) (obj, HTML_A, present, value, -1, 0); +} + +void HTStartAnchor5(HTStructured * obj, const char *name, + const char *href, + const char *linktype, + int tag_charset) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = NO; + + if (name && *name) { + present[HTML_A_NAME] = YES; + value[HTML_A_NAME] = name; + } + if (href && *href) { + present[HTML_A_HREF] = YES; + value[HTML_A_HREF] = href; + } + if (linktype && *linktype) { + present[HTML_A_TYPE] = YES; + value[HTML_A_TYPE] = linktype; + } + + (*obj->isa->start_element) (obj, HTML_A, present, value, tag_charset, 0); +} + +void HTStartIsIndex(HTStructured * obj, const char *prompt, + const char *href) +{ + BOOL present[HTML_ISINDEX_ATTRIBUTES]; + const char *value[HTML_ISINDEX_ATTRIBUTES]; + int i; + + for (i = 0; i < HTML_ISINDEX_ATTRIBUTES; i++) + present[i] = NO; + + if (prompt && *prompt) { + present[HTML_ISINDEX_PROMPT] = YES; + value[HTML_ISINDEX_PROMPT] = (const char *) prompt; + } + if (href) { + present[HTML_ISINDEX_HREF] = YES; + value[HTML_ISINDEX_HREF] = (const char *) href; + } + + (*obj->isa->start_element) (obj, HTML_ISINDEX, present, value, -1, 0); +} diff --git a/WWW/Library/Implementation/HTMLDTD.h b/WWW/Library/Implementation/HTMLDTD.h new file mode 100644 index 0000000..10b0308 --- /dev/null +++ b/WWW/Library/Implementation/HTMLDTD.h @@ -0,0 +1,97 @@ +/* + * $LynxId: HTMLDTD.h,v 1.32 2008/07/06 17:38:13 tom Exp $ + * + The HTML DTD -- software interface in libwww + HTML DTD - SOFTWARE INTERFACE + + SGML purists should excuse the use of the term "DTD" in this file to + represent DTD-related information which is not exactly a DTD itself. + + The C modular structure doesn't work very well here, as the dtd is + partly in the .h and partly in the .c which are not very independent. + Tant pis. + + */ +#ifndef HTMLDTD_H +#define HTMLDTD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Valid name chars for tag parsing. + */ +#define IsNmStart(c) (isalpha(UCH(c))) +#define IsNmChar(c) (isalnum(UCH(c)) || \ + c == '_' || c=='-' || c == '.' || c==':') +#define ReallyEmptyTagNum(e) ((HTML_dtd.tags[e].contents == SGML_EMPTY) && \ + !(HTML_dtd.tags[e].flags & Tgf_nreie)) +#define ReallyEmptyTag(t) ((t->contents == SGML_EMPTY) && \ + !(t->flags & Tgf_nreie)) + +#include + +#ifdef USE_PRETTYSRC +/* values of HTML attributes' types */ +#define HTMLA_NORMAL 0 /* nothing specific */ +#define HTMLA_ANAME 1 /* anchor name - 'id' or a's 'name' */ +#define HTMLA_HREF 2 /* href */ +#define HTMLA_CLASS 4 /* class name. */ +#define HTMLA_AUXCLASS 8 /* attribute, the value of which also designates + a class name */ +#endif + extern const SGML_dtd HTML_dtd; + + extern void HTSwitchDTD(int new_flag); + + extern HTTag HTTag_unrecognized; + extern HTTag HTTag_mixedObject; + +/* + +Start anchor element + + It is kinda convenient to have a particular routine for starting an anchor + element, as everything else for HTML is simple anyway. + + ON ENTRY + + targetstream points to a structured stream object. + + name and href point to attribute strings or are NULL if the attribute is + to be omitted. + + */ + extern void HTStartAnchor(HTStructured * targetstream, const char *name, + const char *href); + + extern void HTStartAnchor5(HTStructured * targetstream, const char *name, + const char *href, + const char *linktype, + int tag_charset); + +/* + +Start IsIndex element - FM + + It is kinda convenient to have a particular routine for starting an IsIndex + element with the prompt and/or href (action) attributes specified. + + ON ENTRY + + targetstream points to a structured stream object. + + prompt and href point to attribute strings or are NULL if the attribute is + to be omitted. + + */ + extern void HTStartIsIndex(HTStructured * targetstream, const char *prompt, + const char *href); + +#ifdef __cplusplus +} +#endif +#endif /* HTMLDTD_H */ diff --git a/WWW/Library/Implementation/HTMLGen.c b/WWW/Library/Implementation/HTMLGen.c new file mode 100644 index 0000000..c63723c --- /dev/null +++ b/WWW/Library/Implementation/HTMLGen.c @@ -0,0 +1,738 @@ +/* + * $LynxId: HTMLGen.c,v 1.46 2020/01/21 22:02:35 tom Exp $ + * + * HTML Generator + * ============== + * + * This version of the HTML object sends HTML markup to the output stream. + * + * Bugs: Line wrapping is not done at all. + * All data handled as PCDATA. + * Should convert old XMP, LISTING and PLAINTEXT to PRE. + * + * It is not obvious to me right now whether the HEAD should be generated + * from the incoming data or the anchor. Currently it is from the former + * which is cleanest. + */ + +#define HTSTREAM_INTERNAL 1 + +#include + +#define BUFFER_SIZE 200 /* Line buffer attempts to make neat breaks */ +#define MAX_CLEANNESS 20 + +/* Implements: +*/ +#include + +#include +#include +#include +#include + +#ifdef USE_COLOR_STYLE +#include +#include +#include +#include +#endif + +#include +#include +#include + +#ifdef USE_COLOR_STYLE +char class_string[TEMPSTRINGSIZE + 1]; + +static char *Style_className = NULL; +static int hcode; +#endif + +/* HTML Object + * ----------- + */ +struct _HTStream { + const HTStreamClass *isa; + HTStream *target; + HTStreamClass targetClass; /* COPY for speed */ +}; + +struct _HTStructured { + const HTStructuredClass *isa; + HTStream *target; + HTStreamClass targetClass; /* COPY for speed */ + + char buffer[BUFFER_SIZE + 1]; /* 1for NL */ + int buffer_maxchars; + char *write_pointer; + char *line_break[MAX_CLEANNESS + 1]; + int cleanness; + BOOL overflowed; + BOOL delete_line_break_char[MAX_CLEANNESS + 1]; + BOOL preformatted; + BOOL escape_specials; + BOOL in_attrval; +#ifdef USE_COLOR_STYLE + HText *text; +#endif +}; + +/* Flush Buffer + * ------------ + */ + +static void flush_breaks(HTStructured * me) +{ + int i; + + for (i = 0; i <= MAX_CLEANNESS; i++) { + me->line_break[i] = NULL; + } +} + +static void HTMLGen_flush(HTStructured * me) +{ + (*me->targetClass.put_block) (me->target, + me->buffer, + (int) (me->write_pointer - me->buffer)); + me->write_pointer = me->buffer; + flush_breaks(me); + me->cleanness = 0; + me->delete_line_break_char[0] = NO; +} + +#ifdef USE_COLOR_STYLE +/* + * We need to flush our buffer each time before we effect a color style change, + * this also relies on the subsequent stage not doing any buffering - this is + * currently true, in cases where it matters the target stream should be the + * HTPlain converter. The flushing currently prevents reasonable line breaking + * in lines with tags. Since color styles help visual scanning of displayed + * source lines, and long lines are wrapped in GridText anyway, this is + * probably acceptable (or even A Good Thing - more to see in one screenful). + * The pointer to the HText structure is initialized here before we effect the + * first style change. Getting it from the global HTMainText variable isn't + * very clean, since it relies on the fact that HText_new() has already been + * called for the current stream stack's document by the time we start + * processing the first element; we rely on HTMLGenerator's callers + * (HTMLParsedPresent in particular) to guarantee this when it matters. + * Normally the target stream will have been setup by HTPlainPresent, which + * does what we need in this respect. (A check whether we have the right + * output stream could be done by checking that targetClass.name is + * "PlainPresenter" or similar.) + * + * All special color style handling is only done if LYPreparsedSource is set. + * We could always do it for displaying source generated by an internal + * gateway, but this makes the rule more simple for the user: color styles are + * applied to html source only with the -preparsed flag. - kw + */ +static void do_cstyle_flush(HTStructured * me) +{ + if (!me->text && LYPreparsedSource) { + me->text = HTMainText; + } + if (me->text) { + HTMLGen_flush(me); + } +} +#endif /* COLOR_STYLE */ + +/* Weighted optional line break + * + * We keep track of all the breaks for when we chop the line + */ + +static void allow_break(HTStructured * me, int new_cleanness, int dlbc) +{ + if (dlbc && me->write_pointer == me->buffer) + dlbc = NO; + me->line_break[new_cleanness] = + dlbc ? me->write_pointer - 1 /* Point to space */ + : me->write_pointer; /* point to gap */ + me->delete_line_break_char[new_cleanness] = (BOOLEAN) dlbc; + if (new_cleanness >= me->cleanness && + (me->overflowed || me->line_break[new_cleanness] > me->buffer)) + me->cleanness = new_cleanness; +} + +/* Character handling + * ------------------ + * + * The tricky bits are the line break handling. This attempts + * to synchrononise line breaks on sentence or phrase ends. This + * is important if one stores SGML files in a line-oriented code + * repository, so that if a small change is made, line ends don't + * shift in a ripple-through to apparently change a large part of the + * file. We give extra "cleanness" to spaces appearing directly + * after periods (full stops), [semi]colons and commas. + * This should make the source files easier to read and modify + * by hand, too, though this is not a primary design consideration. TBL + */ +static void HTMLGen_put_character(HTStructured * me, int c) +{ + if (me->escape_specials && UCH(c) < 32) { + if (c == HT_NON_BREAK_SPACE || c == HT_EN_SPACE || + c == LY_SOFT_HYPHEN) { /* recursion... */ + HTMLGen_put_character(me, '&'); + HTMLGen_put_character(me, '#'); + HTMLGen_put_character(me, 'x'); + switch (c) { + case HT_NON_BREAK_SPACE: /*   */ + HTMLGen_put_character(me, 'A'); + HTMLGen_put_character(me, '0'); + break; + case HT_EN_SPACE: /*   */ + HTMLGen_put_character(me, '2'); + HTMLGen_put_character(me, '0'); + HTMLGen_put_character(me, '0'); + HTMLGen_put_character(me, '2'); + break; + case LY_SOFT_HYPHEN: /* ­ */ + HTMLGen_put_character(me, 'A'); + HTMLGen_put_character(me, 'D'); + break; + } + c = ';'; + } + } + + *me->write_pointer++ = (char) c; + + if (c == '\n') { + HTMLGen_flush(me); + return; + } + + /* Figure our whether we can break at this point + */ + if ((!me->preformatted && (c == ' ' || c == '\t'))) { + int new_cleanness = 3; + + if (me->write_pointer > (me->buffer + 1)) { + char delims[5]; + char *p; + + strcpy(delims, ",;:."); /* @@ english bias */ + p = StrChr(delims, me->write_pointer[-2]); + if (p) + new_cleanness = (int) (p - delims + 6); + if (!me->in_attrval) + new_cleanness += 10; + } + allow_break(me, new_cleanness, YES); + } + + /* + * Flush buffer out when full, or whenever the line is over the nominal + * maximum and we can break at all + */ + if (me->write_pointer >= me->buffer + me->buffer_maxchars || + (me->overflowed && me->cleanness)) { + if (me->cleanness) { + char line_break_char = me->line_break[me->cleanness][0]; + char *saved = me->line_break[me->cleanness]; + + if (me->delete_line_break_char[me->cleanness]) + saved++; + me->line_break[me->cleanness][0] = '\n'; + (*me->targetClass.put_block) (me->target, + me->buffer, + (int) (me->line_break[me->cleanness] - + me->buffer + 1)); + me->line_break[me->cleanness][0] = line_break_char; + { /* move next line in */ + char *p = saved; + char *q; + + for (q = me->buffer; p < me->write_pointer;) + *q++ = *p++; + } + me->cleanness = 0; + /* Now we have to check whether there are any perfectly good breaks + * which weren't good enough for the last line but may be good + * enough for the next + */ + { + int i; + + for (i = 0; i <= MAX_CLEANNESS; i++) { + if (me->line_break[i] != NULL && + me->line_break[i] > saved) { + me->line_break[i] = me->line_break[i] - + (saved - me->buffer); + me->cleanness = i; + } else { + me->line_break[i] = NULL; + } + } + } + + me->delete_line_break_char[0] = 0; + me->write_pointer = me->write_pointer - (saved - me->buffer); + me->overflowed = NO; + + } else { + (*me->targetClass.put_block) (me->target, + me->buffer, + me->buffer_maxchars); + me->write_pointer = me->buffer; + flush_breaks(me); + me->overflowed = YES; + } + } +} + +/* String handling + * --------------- + */ +static void HTMLGen_put_string(HTStructured * me, const char *s) +{ + const char *p; + + for (p = s; *p; p++) + HTMLGen_put_character(me, *p); +} + +static void HTMLGen_write(HTStructured * me, const char *s, + int l) +{ + const char *p; + + for (p = s; p < (s + l); p++) + HTMLGen_put_character(me, *p); +} + +/* Start Element + * ------------- + * + * Within the opening tag, there may be spaces and the line may be broken at + * these spaces. + */ +static int HTMLGen_start_element(HTStructured * me, int element_number, + const BOOL *present, + STRING2PTR value, + int charset GCC_UNUSED, + char **insert GCC_UNUSED) +{ + int i; + BOOL was_preformatted = me->preformatted; + HTTag *tag = &HTML_dtd.tags[element_number]; + +#if defined(USE_COLOR_STYLE) + char *title = NULL; + char *title_tmp = NULL; + const char *name; + + if (LYPreparsedSource && (name = tag->name) != 0) { + char *myHash = NULL; + + /* + * Same logic as in HTML_start_element, copied from there. - kw + */ + HTSprintf(&Style_className, ";%s", name); + StrAllocCopy(myHash, name); + if (class_string[0]) { + StrAllocCat(myHash, "."); + StrAllocCat(myHash, class_string); + HTSprintf(&Style_className, ".%s", class_string); + } + class_string[0] = '\0'; + strtolower(myHash); + hcode = color_style_1(myHash); + strtolower(Style_className); + + if (TRACE_STYLE) { + fprintf(tfp, "CSSTRIM:%s -> %d", myHash, hcode); + if (!hashStyles[hcode].used) { + char *rp = strrchr(myHash, '.'); + + fprintf(tfp, " (undefined) %s\n", myHash); + if (rp) { + int hcd; + + *rp = '\0'; /* trim the class */ + hcd = color_style_1(myHash); + fprintf(tfp, "CSS:%s -> %d", myHash, hcd); + if (!hashStyles[hcd].used) + fprintf(tfp, " (undefined) %s\n", myHash); + else + fprintf(tfp, " ca=%d\n", hashStyles[hcd].color); + } + } else + fprintf(tfp, " ca=%d\n", hashStyles[hcode].color); + } + + if (displayStyles[element_number + STARTAT].color > -2) { + CTRACE2(TRACE_STYLE, + (tfp, "CSSTRIM: start_element: top <%s>\n", + tag->name)); + do_cstyle_flush(me); + HText_characterStyle(me->text, hcode, 1); + } + FREE(myHash); + } +#endif /* USE_COLOR_STYLE */ + me->preformatted = YES; /* free text within tags */ + HTMLGen_put_character(me, '<'); + HTMLGen_put_string(me, tag->name); + if (present) { + BOOL had_attr = NO; + + for (i = 0; i < tag->number_of_attributes; i++) { + if (present[i]) { + had_attr = YES; + HTMLGen_put_character(me, ' '); + allow_break(me, 11, YES); +#ifdef USE_COLOR_STYLE + /* + * Try to mimic HTML_start_element's special handling for + * HTML_LINK. If applicable, color the displayed attribute / + * value pairs differently. - kw + */ + if (LYPreparsedSource && + element_number == HTML_LINK && !title && + present[HTML_LINK_CLASS] && *value[HTML_LINK_CLASS] && + !present[HTML_LINK_REV] && + (present[HTML_LINK_REL] || present[HTML_LINK_HREF])) { + if (present[HTML_LINK_TITLE] && *value[HTML_LINK_TITLE]) { + StrAllocCopy(title, value[HTML_LINK_TITLE]); + LYTrimHead(title); + LYTrimTail(title); + } + if ((!title || *title == '\0') && present[HTML_LINK_REL]) { + StrAllocCopy(title, value[HTML_LINK_REL]); + } + if (title && *title) { + HTSprintf0(&title_tmp, "link.%s.%s", + value[HTML_LINK_CLASS], title); + CTRACE2(TRACE_STYLE, + (tfp, "CSSTRIM:link=%s\n", title_tmp)); + + do_cstyle_flush(me); + HText_characterStyle(me->text, + color_style_1(title_tmp), 1); + } + } +#endif + HTMLGen_put_string(me, tag->attributes[i].name); + if (value[i]) { + me->preformatted = was_preformatted; + me->in_attrval = YES; + if (StrChr(value[i], '"') == NULL) { + HTMLGen_put_string(me, "=\""); + HTMLGen_put_string(me, value[i]); + HTMLGen_put_character(me, '"'); + } else if (StrChr(value[i], '\'') == NULL) { + HTMLGen_put_string(me, "='"); + HTMLGen_put_string(me, value[i]); + HTMLGen_put_character(me, '\''); + } else { /* attribute value has both kinds of quotes */ + const char *p; + + HTMLGen_put_string(me, "=\""); + for (p = value[i]; *p; p++) { + if (*p != '"') { + HTMLGen_put_character(me, *p); + } else { + HTMLGen_put_string(me, """); + } + } + HTMLGen_put_character(me, '"'); + } + me->preformatted = YES; + me->in_attrval = NO; + } + } + } +#ifdef USE_COLOR_STYLE + if (had_attr && LYPreparsedSource && element_number == HTML_LINK) { + /* + * Clean up after special HTML_LINK handling - kw + */ + if (title && *title) { + do_cstyle_flush(me); + HText_characterStyle(me->text, color_style_1(title_tmp), 0); + FREE(title_tmp); + } + FREE(title); + } +#endif + if (had_attr) + allow_break(me, 12, NO); + } + HTMLGen_put_string(me, ">"); /* got rid of \n LJM */ + + /* + * Make very specific HTML assumption that PRE can't be nested! + */ + me->preformatted = (BOOL) ((element_number == HTML_PRE) + ? YES + : was_preformatted); + + /* + * Can break after element start. + */ + if (!me->preformatted && tag->contents != SGML_EMPTY) { + if (tag->contents == SGML_ELEMENT) + allow_break(me, 15, NO); + else + allow_break(me, 2, NO); + } +#if defined(USE_COLOR_STYLE) + /* + * Same logic as in HTML_start_element, copied from there. - kw + */ + + /* end really empty tags straight away */ + if (LYPreparsedSource && ReallyEmptyTagNum(element_number)) { + CTRACE2(TRACE_STYLE, + (tfp, "STYLE:begin_element:ending EMPTY element style\n")); + do_cstyle_flush(me); + HText_characterStyle(me->text, hcode, STACK_OFF); + TrimColorClass(tag->name, Style_className, &hcode); + } +#endif /* USE_COLOR_STYLE */ + if (element_number == HTML_OBJECT && tag->contents == SGML_LITTERAL) { + /* + * These conditions only approximate the ones used in HTML.c. Let our + * SGML parser know that further content is to be parsed normally not + * literally. - kw + */ + if (!present) { + return HT_PARSER_OTHER_CONTENT; + } else if (!present[HTML_OBJECT_DECLARE] && + !(present[HTML_OBJECT_NAME] && + value[HTML_OBJECT_NAME] && *value[HTML_OBJECT_NAME])) { + if (present[HTML_OBJECT_SHAPES] || + !(present[HTML_OBJECT_USEMAP] && + value[HTML_OBJECT_USEMAP] && *value[HTML_OBJECT_USEMAP])) + return HT_PARSER_OTHER_CONTENT; + } + } + return HT_OK; +} + +/* End Element + * ----------- + * + * When we end an element, the style must be returned to that in effect before + * that element. Note that anchors (etc?) don't have an associated style, so + * that we must scan down the stack for an element with a defined style. (In + * fact, the styles should be linked to the whole stack not just the top one.) + * TBL 921119 + */ +static int HTMLGen_end_element(HTStructured * me, int element_number, + char **insert GCC_UNUSED) +{ + if (!me->preformatted && + HTML_dtd.tags[element_number].contents != SGML_EMPTY) { + /* + * Can break before element end. + */ + if (HTML_dtd.tags[element_number].contents == SGML_ELEMENT) + allow_break(me, 14, NO); + else + allow_break(me, 1, NO); + } + HTMLGen_put_string(me, "'); + if (element_number == HTML_PRE) { + me->preformatted = NO; + } +#ifdef USE_COLOR_STYLE + /* + * Same logic as in HTML_end_element, copied from there. - kw + */ + TrimColorClass(HTML_dtd.tags[element_number].name, + Style_className, &hcode); + + if (LYPreparsedSource && !ReallyEmptyTagNum(element_number)) { + CTRACE2(TRACE_STYLE, + (tfp, "STYLE:end_element: ending non-EMPTY style\n")); + do_cstyle_flush(me); + HText_characterStyle(me->text, hcode, STACK_OFF); + } +#endif /* USE_COLOR_STYLE */ + return HT_OK; +} + +/* Expanding entities + * ------------------ + * + */ +static int HTMLGen_put_entity(HTStructured * me, int entity_number) +{ + int nent = (int) HTML_dtd.number_of_entities; + + HTMLGen_put_character(me, '&'); + if (entity_number < nent) { + HTMLGen_put_string(me, HTML_dtd.entity_names[entity_number]); + } + HTMLGen_put_character(me, ';'); + return HT_OK; +} + +/* Free an HTML object + * ------------------- + * + */ +static void HTMLGen_free(HTStructured * me) +{ + (*me->targetClass.put_character) (me->target, '\n'); + HTMLGen_flush(me); + (*me->targetClass._free) (me->target); /* ripple through */ +#ifdef USE_COLOR_STYLE + FREE(Style_className); +#endif + FREE(me); +} + +static void PlainToHTML_free(HTStructured * me) +{ + HTMLGen_end_element(me, HTML_PRE, 0); + HTMLGen_free(me); +} + +static void HTMLGen_abort(HTStructured * me, HTError e GCC_UNUSED) +{ + HTMLGen_free(me); +#ifdef USE_COLOR_STYLE + FREE(Style_className); +#endif +} + +static void PlainToHTML_abort(HTStructured * me, HTError e GCC_UNUSED) +{ + PlainToHTML_free(me); +} + +/* Structured Object Class + * ----------------------- + */ +static const HTStructuredClass HTMLGeneration = /* As opposed to print etc */ +{ + "HTMLGen", + HTMLGen_free, + HTMLGen_abort, + HTMLGen_put_character, HTMLGen_put_string, HTMLGen_write, + HTMLGen_start_element, HTMLGen_end_element, + HTMLGen_put_entity +}; + +/* Subclass-specific Methods + * ------------------------- + */ +HTStructured *HTMLGenerator(HTStream *output) +{ + HTStructured *me = (HTStructured *) malloc(sizeof(*me)); + + if (me == NULL) + outofmem(__FILE__, "HTMLGenerator"); + + me->isa = &HTMLGeneration; + + me->target = output; + me->targetClass = *me->target->isa; /* Copy pointers to routines for speed */ + + me->write_pointer = me->buffer; + flush_breaks(me); + me->line_break[0] = me->buffer; + me->cleanness = 0; + me->overflowed = NO; + me->delete_line_break_char[0] = NO; + me->preformatted = NO; + me->in_attrval = NO; + + /* + * For what line length should we attempt to wrap ? - kw + */ + if (!LYPreparsedSource) { + me->buffer_maxchars = 80; /* work as before - kw */ + } else if (dump_output_width > 1) { + me->buffer_maxchars = dump_output_width; /* try to honor -width - kw */ + } else if (dump_output_immediately) { + me->buffer_maxchars = 80; /* try to honor -width - kw */ + } else { + me->buffer_maxchars = (LYcolLimit - 1); + if (me->buffer_maxchars < 38) /* too narrow, let GridText deal */ + me->buffer_maxchars = 40; + } + if (me->buffer_maxchars > 900) /* likely not true - kw */ + me->buffer_maxchars = 78; + if (me->buffer_maxchars > BUFFER_SIZE) /* must not be larger! */ + me->buffer_maxchars = BUFFER_SIZE - 2; + + /* + * If dump_output_immediately is set, there likely isn't anything after + * this stream to interpret the Lynx special chars. Also if they get + * displayed via HTPlain, that will probably make non-breaking space chars + * etc. invisible. So let's translate them to numerical character + * references. For debugging purposes we'll use the new hex format. + */ + me->escape_specials = LYPreparsedSource; + +#ifdef USE_COLOR_STYLE + me->text = NULL; /* Will be initialized when first needed. - kw */ + FREE(Style_className); + class_string[0] = '\0'; +#endif /* COLOR_STYLE */ + + return me; +} + +/* Stream Object Class + * ------------------- + * + * This object just converts a plain text stream into HTML + * It is officially a structured stream but only the stream bits exist. + * This is just the easiest way of typecasting all the routines. + */ +static const HTStructuredClass PlainToHTMLConversion = +{ + "plaintexttoHTML", + HTMLGen_free, + PlainToHTML_abort, + HTMLGen_put_character, + HTMLGen_put_string, + HTMLGen_write, + NULL, /* Structured stuff */ + NULL, + NULL +}; + +/* HTConverter from plain text to HTML Stream + * ------------------------------------------ + */ +HTStream *HTPlainToHTML(HTPresentation *pres GCC_UNUSED, + HTParentAnchor *anchor GCC_UNUSED, + HTStream *sink) +{ + HTStructured *me = (HTStructured *) malloc(sizeof(*me)); + + if (me == NULL) + outofmem(__FILE__, "PlainToHTML"); + + me->isa = (const HTStructuredClass *) &PlainToHTMLConversion; + + /* + * Copy pointers to routines for speed. + */ + me->target = sink; + me->targetClass = *me->target->isa; + me->write_pointer = me->buffer; + flush_breaks(me); + me->cleanness = 0; + me->overflowed = NO; + me->delete_line_break_char[0] = NO; + /* try to honor -width - kw */ + me->buffer_maxchars = (dump_output_width > 1 ? + dump_output_width : 80); + + HTMLGen_put_string(me, "\n\n
    \n");
    +    me->preformatted = YES;
    +    me->escape_specials = NO;
    +    me->in_attrval = NO;
    +    return (HTStream *) me;
    +}
    diff --git a/WWW/Library/Implementation/HTMLGen.h b/WWW/Library/Implementation/HTMLGen.h
    new file mode 100644
    index 0000000..f2db3d4
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTMLGen.h
    @@ -0,0 +1,26 @@
    +/*                  /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTMLGen.html
    +                                      HTML GENERATOR
    +                                             
    +   This module converts structed stream into stream.  That is, given a stream
    +   to write to, it will give you a structured stream to
    +   
    + */
    +#ifndef HTMLGEN_H
    +#define HTMLGEN_H
    +
    +#include 
    +#include 
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern HTStructured *HTMLGenerator(HTStream *output);
    +
    +    extern HTStream *HTPlainToHTML(HTPresentation *pres,
    +				   HTParentAnchor *anchor,
    +				   HTStream *sink);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTMLGEN_H */
    diff --git a/WWW/Library/Implementation/HTNews.c b/WWW/Library/Implementation/HTNews.c
    new file mode 100644
    index 0000000..a1b94dc
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTNews.c
    @@ -0,0 +1,3147 @@
    +/*
    + * $LynxId: HTNews.c,v 1.81 2022/04/01 00:18:22 tom Exp $
    + *
    + *			NEWS ACCESS				HTNews.c
    + *			===========
    + *
    + * History:
    + *	26 Sep 90	Written TBL
    + *	29 Nov 91	Downgraded to C, for portable implementation.
    + */
    +
    +#include 		/* Coding convention macros */
    +
    +#ifndef DISABLE_NEWS
    +
    +/* Implements:
    +*/
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#define NEWS_PORT 119		/* See rfc977 */
    +#define SNEWS_PORT 563		/* See Lou Montulli */
    +#define APPEND			/* Use append methods */
    +int HTNewsChunkSize = 30;	/* Number of articles for quick display */
    +int HTNewsMaxChunk = 40;	/* Largest number of articles in one window */
    +
    +#ifndef DEFAULT_NEWS_HOST
    +#define DEFAULT_NEWS_HOST "news"
    +#endif /* DEFAULT_NEWS_HOST */
    +
    +#ifndef NEWS_SERVER_FILE
    +#define NEWS_SERVER_FILE "/usr/local/lib/rn/server"
    +#endif /* NEWS_SERVER_FILE */
    +
    +#ifndef NEWS_AUTH_FILE
    +#define NEWS_AUTH_FILE ".newsauth"
    +#endif /* NEWS_AUTH_FILE */
    +
    +#ifdef USE_SSL
    +
    +#if defined(LIBRESSL_VERSION_NUMBER)
    +/* OpenSSL and LibreSSL version numbers do not correspond */
    +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    +#undef  SSL_load_error_strings
    +#define SSL_load_error_strings()	/* nothing */
    +#endif
    +
    +static SSL *Handle = NULL;
    +static int channel_s = 1;
    +
    +#define NEWS_NETWRITE(sock, buff, size) \
    +	((Handle != NULL) \
    +	 ? SSL_write(Handle, buff, size) \
    +	 : NETWRITE(sock, buff, size))
    +#define NEWS_NETCLOSE(sock) \
    +	{ \
    +	    if ((int)(sock) >= 0) { \
    +	        (void)NETCLOSE(sock); \
    +	    } \
    +	    if (Handle != NULL) { \
    +	        SSL_free(Handle); \
    +	        Handle = NULL; \
    +	    } \
    +	}
    +static int HTNewsGetCharacter(void);
    +
    +#define NEXT_CHAR HTNewsGetCharacter()
    +#else
    +#define NEWS_NETWRITE  NETWRITE
    +#define NEWS_NETCLOSE  NETCLOSE
    +#define NEXT_CHAR HTGetCharacter()
    +#endif /* USE_SSL */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +
    +#define SnipIn(d,fmt,len,s)      sprintf(d, fmt,      (int)sizeof(d)-len, s)
    +#define SnipIn2(d,fmt,tag,len,s) sprintf(d, fmt, tag, (int)sizeof(d)-len, s)
    +
    +struct _HTStructured {
    +    const HTStructuredClass *isa;
    +    /* ... */
    +};
    +
    +#define LINE_LENGTH 512		/* Maximum length of line of ARTICLE etc */
    +#define GROUP_NAME_LENGTH	256	/* Maximum length of group name */
    +
    +/*
    + *  Module-wide variables.
    + */
    +char *HTNewsHost = NULL;	/* Default host */
    +static char *NewsHost = NULL;	/* Current host */
    +static char *NewsHREF = NULL;	/* Current HREF prefix */
    +static int s;			/* Socket for NewsHost */
    +static int HTCanPost = FALSE;	/* Current POST permission */
    +static char response_text[LINE_LENGTH + 1];	/* Last response */
    +
    +static HTStructured *target;	/* The output sink */
    +static HTStructuredClass targetClass;	/* Copy of fn addresses */
    +static HTStream *rawtarget = NULL;	/* The output sink for rawtext */
    +static HTStreamClass rawtargetClass;	/* Copy of fn addresses */
    +static int diagnostic;		/* level: 0=none 2=source */
    +static BOOL rawtext = NO;	/* Flag: HEAD or -mime_headers */
    +static HTList *NNTP_AuthInfo = NULL;	/* AUTHINFO database */
    +static char *name = NULL;
    +static char *address = NULL;
    +static char *dbuf = NULL;	/* dynamic buffer for long messages etc. */
    +
    +#define PUTC(c) (*targetClass.put_character)(target, c)
    +#define PUTS(s) (*targetClass.put_string)(target, s)
    +#define RAW_PUTS(s) (*rawtargetClass.put_string)(rawtarget, s)
    +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
    +#define END(e) (*targetClass.end_element)(target, e, 0)
    +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
    +			(*targetClass.end_element)(target, e, 0)
    +#define FREE_TARGET if (rawtext) (*rawtargetClass._free)(rawtarget); \
    +			else (*targetClass._free)(target)
    +#define ABORT_TARGET if (rawtext) (*rawtargetClass._abort)(rawtarget, NULL); \
    +			else (*targetClass._abort)(target, NULL)
    +
    +typedef struct _NNTPAuth {
    +    char *host;
    +    char *user;
    +    char *pass;
    +} NNTPAuth;
    +
    +#ifdef LY_FIND_LEAKS
    +static void free_news_globals(void)
    +{
    +    if (s >= 0) {
    +	NEWS_NETCLOSE(s);
    +	s = -1;
    +    }
    +    FREE(HTNewsHost);
    +    FREE(NewsHost);
    +    FREE(NewsHREF);
    +    FREE(name);
    +    FREE(address);
    +    FREE(dbuf);
    +}
    +#endif /* LY_FIND_LEAKS */
    +
    +static void free_NNTP_AuthInfo(void)
    +{
    +    HTList *cur = NNTP_AuthInfo;
    +    NNTPAuth *auth = NULL;
    +
    +    if (!cur)
    +	return;
    +
    +    while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
    +	FREE(auth->host);
    +	FREE(auth->user);
    +	FREE(auth->pass);
    +	FREE(auth);
    +    }
    +    HTList_delete(NNTP_AuthInfo);
    +    NNTP_AuthInfo = NULL;
    +    return;
    +}
    +
    +/*
    + * Initialize the authentication list by loading the user's $HOME/.newsauth
    + * file.  That file is part of tin's configuration and is used by a few other
    + * programs.
    + */
    +static void load_NNTP_AuthInfo(void)
    +{
    +    FILE *fp;
    +    char fname[LY_MAXPATH];
    +    char buffer[LINE_LENGTH + 1];
    +
    +    LYAddPathToHome(fname, sizeof(fname), NEWS_AUTH_FILE);
    +
    +    if ((fp = fopen(fname, "r")) != 0) {
    +	while (fgets(buffer, (int) sizeof(buffer), fp) != 0) {
    +	    char the_host[LINE_LENGTH + 1];
    +	    char the_pass[LINE_LENGTH + 1];
    +	    char the_user[LINE_LENGTH + 1];
    +
    +	    if (sscanf(buffer, "%s%s%s", the_host, the_pass, the_user) == 3
    +		&& strlen(the_host) != 0
    +		&& strlen(the_pass) != 0
    +		&& strlen(the_user) != 0) {
    +		NNTPAuth *auth = typecalloc(NNTPAuth);
    +
    +		if (auth == NULL)
    +		    break;
    +		StrAllocCopy(auth->host, the_host);
    +		StrAllocCopy(auth->pass, the_pass);
    +		StrAllocCopy(auth->user, the_user);
    +
    +		HTList_appendObject(NNTP_AuthInfo, auth);
    +	    }
    +	}
    +	fclose(fp);
    +    }
    +}
    +
    +const char *HTGetNewsHost(void)
    +{
    +    return HTNewsHost;
    +}
    +
    +void HTSetNewsHost(const char *value)
    +{
    +    StrAllocCopy(HTNewsHost, value);
    +}
    +
    +/*	Initialisation for this module
    + *	------------------------------
    + *
    + *	Except on the NeXT, we pick up the NewsHost name from
    + *
    + *	1.	Environment variable NNTPSERVER
    + *	2.	File NEWS_SERVER_FILE
    + *	3.	Compilation time macro DEFAULT_NEWS_HOST
    + *	4.	Default to "news"
    + *
    + *	On the NeXT, we pick up the NewsHost name from, in order:
    + *
    + *	1.	WorldWideWeb default "NewsHost"
    + *	2.	Global default "NewsHost"
    + *	3.	News default "NewsHost"
    + *	4.	Compilation time macro DEFAULT_NEWS_HOST
    + *	5.	Default to "news"
    + */
    +static BOOL initialized = NO;
    +static BOOL initialize(void)
    +{
    +#ifdef NeXTStep
    +    char *cp = NULL;
    +#endif
    +
    +    /*
    +     * Get name of Host.
    +     */
    +#ifdef NeXTStep
    +    if ((cp = NXGetDefaultValue("WorldWideWeb", "NewsHost")) == 0) {
    +	if ((cp = NXGetDefaultValue("News", "NewsHost")) == 0) {
    +	    StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
    +	}
    +    }
    +    if (cp) {
    +	StrAllocCopy(HTNewsHost, cp);
    +	cp = NULL;
    +    }
    +#else
    +    if (LYGetEnv("NNTPSERVER")) {
    +	StrAllocCopy(HTNewsHost, LYGetEnv("NNTPSERVER"));
    +	CTRACE((tfp, "HTNews: NNTPSERVER defined as `%s'\n",
    +		HTNewsHost));
    +    } else {
    +	FILE *fp = fopen(NEWS_SERVER_FILE, TXT_R);
    +
    +	if (fp) {
    +	    char server_name[MAXHOSTNAMELEN + 1];
    +
    +	    if (fgets(server_name, (int) sizeof server_name, fp) != NULL) {
    +		char *p = StrChr(server_name, '\n');
    +
    +		if (p != NULL)
    +		    *p = '\0';
    +		StrAllocCopy(HTNewsHost, server_name);
    +		CTRACE((tfp, "HTNews: File %s defines news host as `%s'\n",
    +			NEWS_SERVER_FILE, HTNewsHost));
    +	    }
    +	    fclose(fp);
    +	}
    +    }
    +    if (!HTNewsHost)
    +	StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
    +#endif /* NeXTStep */
    +
    +    s = -1;			/* Disconnected */
    +#ifdef LY_FIND_LEAKS
    +    atexit(free_news_globals);
    +#endif
    +    return YES;
    +}
    +
    +/*	Send NNTP Command line to remote host & Check Response
    + *	------------------------------------------------------
    + *
    + * On entry,
    + *	command points to the command to be sent, including CRLF, or is null
    + *		pointer if no command to be sent.
    + * On exit,
    + *	Negative status indicates transmission error, socket closed.
    + *	Positive status is an NNTP status.
    + */
    +static int response(char *command)
    +{
    +    int result;
    +    char *p = response_text;
    +    int ich;
    +
    +    if (command) {
    +	int status;
    +	int length = (int) strlen(command);
    +
    +	CTRACE((tfp, "NNTP command to be sent: %s", command));
    +#ifdef NOT_ASCII
    +	{
    +	    const char *p2;
    +	    char *q;
    +	    char ascii[LINE_LENGTH + 1];
    +
    +	    for (p2 = command, q = ascii; *p2; p2++, q++) {
    +		*q = TOASCII(*p2);
    +	    }
    +	    status = NEWS_NETWRITE(s, ascii, length);
    +	}
    +#else
    +	status = (int) NEWS_NETWRITE(s, (char *) command, length);
    +#endif /* NOT_ASCII */
    +	if (status < 0) {
    +	    CTRACE((tfp, "HTNews: Unable to send command. Disconnecting.\n"));
    +	    NEWS_NETCLOSE(s);
    +	    s = -1;
    +	    return status;
    +	}			/* if bad status */
    +    }
    +    /* if command to be sent */
    +    for (;;) {
    +	ich = NEXT_CHAR;
    +	if (((*p++ = (char) ich) == LF) ||
    +	    (p == &response_text[LINE_LENGTH])) {
    +	    *--p = '\0';	/* Terminate the string */
    +	    CTRACE((tfp, "NNTP Response: %s\n", response_text));
    +	    sscanf(response_text, "%d", &result);
    +	    return result;
    +	}
    +	/* if end of line */
    +	if (ich == EOF) {
    +	    *(p - 1) = '\0';
    +	    if (interrupted_in_htgetcharacter) {
    +		CTRACE((tfp,
    +			"HTNews: Interrupted on read, closing socket %d\n",
    +			s));
    +	    } else {
    +		CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n",
    +			s));
    +	    }
    +	    NEWS_NETCLOSE(s);	/* End of file, close socket */
    +	    s = -1;
    +	    if (interrupted_in_htgetcharacter) {
    +		interrupted_in_htgetcharacter = 0;
    +		return (HT_INTERRUPTED);
    +	    }
    +	    return ((int) EOF);	/* End of file on response */
    +	}
    +    }				/* Loop over characters */
    +}
    +
    +/*	Case insensitive string comparisons
    + *	-----------------------------------
    + *
    + * On entry,
    + *	template must be already in upper case.
    + *	unknown may be in upper or lower or mixed case to match.
    + */
    +static BOOL match(const char *unknown, const char *ctemplate)
    +{
    +    const char *u = unknown;
    +    const char *t = ctemplate;
    +
    +    for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) ;		/* Find mismatch or end */
    +    return (BOOL) (*t == 0);	/* OK if end of template */
    +}
    +
    +typedef enum {
    +    NNTPAUTH_ERROR = 0,		/* general failure */
    +    NNTPAUTH_OK = 281,		/* authenticated successfully */
    +    NNTPAUTH_CLOSE = 502	/* server probably closed connection */
    +} NNTPAuthResult;
    +
    +/*
    + *  This function handles nntp authentication. - FM
    + */
    +static NNTPAuthResult HTHandleAuthInfo(char *host)
    +{
    +    HTList *cur = NULL;
    +    NNTPAuth *auth = NULL;
    +    char *UserName = NULL;
    +    char *PassWord = NULL;
    +    char *msg = NULL;
    +    char buffer[512];
    +    int status, tries;
    +
    +    /*
    +     * Make sure we have a host.  - FM
    +     */
    +    if (isEmpty(host))
    +	return NNTPAUTH_ERROR;
    +
    +    /*
    +     * Check for an existing authorization entry.  - FM
    +     */
    +    if (NNTP_AuthInfo == NULL) {
    +	NNTP_AuthInfo = HTList_new();
    +	load_NNTP_AuthInfo();
    +#ifdef LY_FIND_LEAKS
    +	atexit(free_NNTP_AuthInfo);
    +#endif
    +    }
    +
    +    cur = NNTP_AuthInfo;
    +    while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
    +	if (!strcmp(auth->host, host)) {
    +	    UserName = auth->user;
    +	    PassWord = auth->pass;
    +	    break;
    +	}
    +    }
    +
    +    /*
    +     * Handle the username.  - FM
    +     */
    +    buffer[sizeof(buffer) - 1] = '\0';
    +    tries = 3;
    +
    +    while (tries) {
    +	if (UserName == NULL) {
    +	    HTSprintf0(&msg, gettext("Username for news host '%s':"), host);
    +	    UserName = HTPrompt(msg, NULL);
    +	    FREE(msg);
    +	    if (!(UserName && *UserName)) {
    +		FREE(UserName);
    +		return NNTPAUTH_ERROR;
    +	    }
    +	}
    +	sprintf(buffer, "AUTHINFO USER %.*s%c%c",
    +		(int) sizeof(buffer) - 17, UserName, CR, LF);
    +	if ((status = response(buffer)) < 0) {
    +	    if (status == HT_INTERRUPTED)
    +		_HTProgress(CONNECTION_INTERRUPTED);
    +	    else
    +		HTAlert(FAILED_CONNECTION_CLOSED);
    +	    if (auth) {
    +		if (auth->user != UserName) {
    +		    FREE(auth->user);
    +		    auth->user = UserName;
    +		}
    +	    } else {
    +		FREE(UserName);
    +	    }
    +	    return NNTPAUTH_CLOSE;
    +	}
    +	if (status == 281) {
    +	    /*
    +	     * Username is accepted and no password is required.  - FM
    +	     */
    +	    if (auth) {
    +		if (auth->user != UserName) {
    +		    FREE(auth->user);
    +		    auth->user = UserName;
    +		}
    +	    } else {
    +		/*
    +		 * Store the accepted username and no password.  - FM
    +		 */
    +		if ((auth = typecalloc(NNTPAuth)) != NULL) {
    +		    StrAllocCopy(auth->host, host);
    +		    auth->user = UserName;
    +		    HTList_appendObject(NNTP_AuthInfo, auth);
    +		}
    +	    }
    +	    return NNTPAUTH_OK;
    +	}
    +	if (status != 381) {
    +	    /*
    +	     * Not success, nor a request for the password, so it must be an
    +	     * error.  - FM
    +	     */
    +	    HTAlert(response_text);
    +	    tries--;
    +	    if ((tries > 0) && HTConfirm(gettext("Change username?"))) {
    +		if (!auth || auth->user != UserName) {
    +		    FREE(UserName);
    +		}
    +		if ((UserName = HTPrompt(gettext("Username:"), UserName))
    +		    != NULL &&
    +		    *UserName) {
    +		    continue;
    +		}
    +	    }
    +	    if (auth) {
    +		if (auth->user != UserName) {
    +		    FREE(auth->user);
    +		}
    +		FREE(auth->pass);
    +	    }
    +	    FREE(UserName);
    +	    return NNTPAUTH_ERROR;
    +	}
    +	break;
    +    }
    +
    +    if (status == 381) {
    +	/*
    +	 * Handle the password.  - FM
    +	 */
    +	tries = 3;
    +	while (tries) {
    +	    if (PassWord == NULL) {
    +		HTSprintf0(&msg, gettext("Password for news host '%s':"), host);
    +		PassWord = HTPromptPassword(msg, NULL);
    +		FREE(msg);
    +		if (!(PassWord && *PassWord)) {
    +		    FREE(PassWord);
    +		    return NNTPAUTH_ERROR;
    +		}
    +	    }
    +	    sprintf(buffer, "AUTHINFO PASS %.*s%c%c",
    +		    (int) sizeof(buffer) - 17, PassWord, CR, LF);
    +	    if ((status = response(buffer)) < 0) {
    +		if (status == HT_INTERRUPTED) {
    +		    _HTProgress(CONNECTION_INTERRUPTED);
    +		} else {
    +		    HTAlert(FAILED_CONNECTION_CLOSED);
    +		}
    +		if (auth) {
    +		    if (auth->user != UserName) {
    +			FREE(auth->user);
    +			auth->user = UserName;
    +		    }
    +		    if (auth->pass != PassWord) {
    +			FREE(auth->pass);
    +			auth->pass = PassWord;
    +		    }
    +		} else {
    +		    FREE(UserName);
    +		    FREE(PassWord);
    +		}
    +		return NNTPAUTH_CLOSE;
    +	    }
    +	    if (status == 502) {
    +		/*
    +		 * That's what INN's nnrpd returns.  It closes the connection
    +		 * after this.  - kw
    +		 */
    +		HTAlert(response_text);
    +		if (auth) {
    +		    if (auth->user == UserName)
    +			UserName = NULL;
    +		    FREE(auth->user);
    +		    if (auth->pass == PassWord)
    +			PassWord = NULL;
    +		    FREE(auth->pass);
    +		}
    +		FREE(UserName);
    +		FREE(PassWord);
    +		return NNTPAUTH_CLOSE;
    +	    }
    +	    if (status == 281) {
    +		/*
    +		 * Password also is accepted, and everything has been stored. 
    +		 * - FM
    +		 */
    +		if (auth) {
    +		    if (auth->user != UserName) {
    +			FREE(auth->user);
    +			auth->user = UserName;
    +		    }
    +		    if (auth->pass != PassWord) {
    +			FREE(auth->pass);
    +			auth->pass = PassWord;
    +		    }
    +		} else {
    +		    if ((auth = typecalloc(NNTPAuth)) != NULL) {
    +			StrAllocCopy(auth->host, host);
    +			auth->user = UserName;
    +			auth->pass = PassWord;
    +			HTList_appendObject(NNTP_AuthInfo, auth);
    +		    }
    +		}
    +		return NNTPAUTH_OK;
    +	    }
    +	    /*
    +	     * Not success, so it must be an error.  - FM
    +	     */
    +	    HTAlert(response_text);
    +	    if (!auth || auth->pass != PassWord) {
    +		FREE(PassWord);
    +	    } else {
    +		PassWord = NULL;
    +	    }
    +	    tries--;
    +	    if ((tries > 0) && HTConfirm(gettext("Change password?"))) {
    +		continue;
    +	    }
    +	    if (auth) {
    +		if (auth->user == UserName)
    +		    UserName = NULL;
    +		FREE(auth->user);
    +		FREE(auth->pass);
    +	    }
    +	    FREE(UserName);
    +	    break;
    +	}
    +    }
    +
    +    return NNTPAUTH_ERROR;
    +}
    +
    +/*	Find Author's name in mail address
    + *	----------------------------------
    + *
    + * On exit,
    + *	Returns allocated string which cannot be freed by the
    + *	calling function, and is reallocated on subsequent calls
    + *	to this function.
    + *
    + * For example, returns "Tim Berners-Lee" if given any of
    + *	" Tim Berners-Lee  "
    + *  or	" tim@online.cern.ch ( Tim Berners-Lee ) "
    + */
    +static char *author_name(char *email)
    +{
    +    char *p, *e;
    +
    +    StrAllocCopy(name, email);
    +    CTRACE((tfp, "Trying to find name in: %s\n", name));
    +
    +    if ((p = strrchr(name, '(')) && (e = strrchr(name, ')'))) {
    +	if (e > p) {
    +	    *e = '\0';		/* Chop off everything after the ')'  */
    +	    return HTStrip(p + 1);	/* Remove leading and trailing spaces */
    +	}
    +    }
    +
    +    if ((p = strrchr(name, '<')) && (e = strrchr(name, '>'))) {
    +	if (e++ > p) {
    +	    while ((*p++ = *e++) != 0)	/* Remove <...> */
    +		;
    +	    return HTStrip(name);	/* Remove leading and trailing spaces */
    +	}
    +    }
    +
    +    return HTStrip(name);	/* Default to the whole thing */
    +}
    +
    +/*	Find Author's mail address
    + *	--------------------------
    + *
    + * On exit,
    + *	Returns allocated string which cannot be freed by the
    + *	calling function, and is reallocated on subsequent calls
    + *	to this function.
    + *
    + * For example, returns "montulli@spaced.out.galaxy.net" if given any of
    + *	" Lou Montulli  "
    + *  or	" montulli@spaced.out.galaxy.net ( Lou "The Stud" Montulli ) "
    + */
    +static char *author_address(char *email)
    +{
    +    char *p, *at, *e;
    +
    +    StrAllocCopy(address, email);
    +    CTRACE((tfp, "Trying to find address in: %s\n", address));
    +
    +    if ((p = strrchr(address, '<'))) {
    +	if ((e = strrchr(p, '>')) && (at = strrchr(p, '@'))) {
    +	    if (at < e) {
    +		*e = '\0';	/* Remove > */
    +		return HTStrip(p + 1);	/* Remove leading and trailing spaces */
    +	    }
    +	}
    +    }
    +
    +    if ((p = strrchr(address, '(')) &&
    +	(e = strrchr(address, ')')) && (at = StrChr(address, '@'))) {
    +	if (e > p && at < e) {
    +	    *p = '\0';		/* Chop off everything after the ')'  */
    +	    return HTStrip(address);	/* Remove leading and trailing spaces */
    +	}
    +    }
    +
    +    if ((at = strrchr(address, '@')) && at > address) {
    +	p = (at - 1);
    +	e = (at + 1);
    +	while (p > address && !isspace(UCH(*p)))
    +	    p--;
    +	while (*e && !isspace(UCH(*e)))
    +	    e++;
    +	*e = 0;
    +	return HTStrip(p);
    +    }
    +
    +    /*
    +     * Default to the first word.
    +     */
    +    p = address;
    +    while (isspace(UCH(*p)))
    +	p++;			/* find first non-space */
    +    e = p;
    +    while (!isspace(UCH(*e)) && *e != '\0')
    +	e++;			/* find next space or end */
    +    *e = '\0';			/* terminate space */
    +
    +    return (p);
    +}
    +
    +/*	Start anchor element
    + *	--------------------
    + */
    +static void start_anchor(const char *href)
    +{
    +    BOOL present[HTML_A_ATTRIBUTES];
    +    const char *value[HTML_A_ATTRIBUTES];
    +    int i;
    +
    +    for (i = 0; i < HTML_A_ATTRIBUTES; i++)
    +	present[i] = (BOOL) (i == HTML_A_HREF);
    +    value[HTML_A_HREF] = href;
    +    (*targetClass.start_element) (target, HTML_A, present, value, -1, 0);
    +}
    +
    +/*	Start link element
    + *	------------------
    + */
    +static void start_link(const char *href, const char *rev)
    +{
    +    BOOL present[HTML_LINK_ATTRIBUTES];
    +    const char *value[HTML_LINK_ATTRIBUTES];
    +    int i;
    +
    +    for (i = 0; i < HTML_LINK_ATTRIBUTES; i++)
    +	present[i] = (BOOL) (i == HTML_LINK_HREF || i == HTML_LINK_REV);
    +    value[HTML_LINK_HREF] = href;
    +    value[HTML_LINK_REV] = rev;
    +    (*targetClass.start_element) (target, HTML_LINK, present, value, -1, 0);
    +}
    +
    +/*	Start list element
    + *	------------------
    + */
    +static void start_list(int seqnum)
    +{
    +    BOOL present[HTML_OL_ATTRIBUTES];
    +    const char *value[HTML_OL_ATTRIBUTES];
    +    char SeqNum[20];
    +    int i;
    +
    +    for (i = 0; i < HTML_OL_ATTRIBUTES; i++)
    +	present[i] = (BOOL) (i == HTML_OL_SEQNUM || i == HTML_OL_START);
    +    sprintf(SeqNum, "%d", seqnum);
    +    value[HTML_OL_SEQNUM] = SeqNum;
    +    value[HTML_OL_START] = SeqNum;
    +    (*targetClass.start_element) (target, HTML_OL, present, value, -1, 0);
    +}
    +
    +/*	Paste in an Anchor
    + *	------------------
    + *
    + *
    + * On entry,
    + *	HT	has a selection of zero length at the end.
    + *	text	points to the text to be put into the file, 0 terminated.
    + *	addr	points to the hypertext reference address,
    + *		terminated by white space, comma, NULL or '>'
    + */
    +static void write_anchor(const char *text, const char *addr)
    +{
    +    char href[LINE_LENGTH + 1];
    +    const char *p;
    +    char *q;
    +
    +    for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++) {
    +	;
    +    }
    +    if (strlen(NewsHREF) + (size_t) (p - addr) + 1 < sizeof(href)) {
    +	q = href;
    +	strcpy(q, NewsHREF);
    +	/* Make complete hypertext reference */
    +	StrNCat(q, addr, (size_t) (p - addr));
    +    } else {
    +	q = NULL;
    +	HTSprintf0(&q, "%s%.*s", NewsHREF, (int) (p - addr), addr);
    +    }
    +
    +    start_anchor(q);
    +    PUTS(text);
    +    END(HTML_A);
    +
    +    if (q != href)
    +	FREE(q);
    +}
    +
    +/*	Write list of anchors
    + *	---------------------
    + *
    + *	We take a pointer to a list of objects, and write out each,
    + *	generating an anchor for each.
    + *
    + * On entry,
    + *	HT	has a selection of zero length at the end.
    + *	text	points to a comma or space separated list of addresses.
    + * On exit,
    + *	*text	is NOT any more chopped up into substrings.
    + */
    +static void write_anchors(char *text)
    +{
    +    char *start = text;
    +    char *end;
    +    char c;
    +
    +    for (;;) {
    +	for (; *start && (WHITE(*start)); start++) ;	/* Find start */
    +	if (!*start)
    +	    return;		/* (Done) */
    +	for (end = start;
    +	     *end && (*end != ' ') && (*end != ','); end++) ;	/* Find end */
    +	if (*end)
    +	    end++;		/* Include comma or space but not NULL */
    +	c = *end;
    +	*end = '\0';
    +	if (*start == '<')
    +	    write_anchor(start, start + 1);
    +	else
    +	    write_anchor(start, start);
    +	START(HTML_BR);
    +	*end = c;
    +	start = end;		/* Point to next one */
    +    }
    +}
    +
    +/*	Abort the connection					abort_socket
    + *	--------------------
    + */
    +static void abort_socket(void)
    +{
    +    CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n", s));
    +    NEWS_NETCLOSE(s);		/* End of file, close socket */
    +    if (rawtext) {
    +	RAW_PUTS("Network Error: connection lost\n");
    +    } else {
    +	PUTS("Network Error: connection lost");
    +	PUTC('\n');
    +    }
    +    s = -1;			/* End of file on response */
    +}
    +
    +/*
    + *  Determine if a line is a valid header line.			valid_header
    + *  -------------------------------------------
    + */
    +static BOOLEAN valid_header(char *line)
    +{
    +    char *colon, *space;
    +
    +    /*
    +     * Blank or tab in first position implies this is a continuation header.
    +     */
    +    if (line[0] == ' ' || line[0] == '\t')
    +	return (TRUE);
    +
    +    /*
    +     * Just check for initial letter, colon, and space to make sure we discard
    +     * only invalid headers.
    +     */
    +    colon = StrChr(line, ':');
    +    space = StrChr(line, ' ');
    +    if (isalpha(UCH(line[0])) && colon && space == colon + 1)
    +	return (TRUE);
    +
    +    /*
    +     * Anything else is a bad header -- it should be ignored.
    +     */
    +    return (FALSE);
    +}
    +
    +/*	post in an Article					post_article
    + *	------------------
    + *			(added by FM, modeled on Lynx's previous mini inews)
    + *
    + *	Note the termination condition of a single dot on a line by itself.
    + *
    + *  On entry,
    + *	s		Global socket number is OK
    + *	postfile	file with header and article to post.
    + */
    +static void post_article(char *postfile)
    +{
    +    char line[512];
    +    char buf[512];
    +    char crlf[3];
    +    char *cp;
    +    int status;
    +    FILE *fd;
    +    int in_header = 1, seen_header = 0, seen_fromline = 0;
    +    int blen = 0, llen = 0;
    +
    +    /*
    +     * Open the temporary file with the nntp headers and message body.  - FM
    +     */
    +    if ((fd = fopen(NonNull(postfile), TXT_R)) == NULL) {
    +	HTAlert(FAILED_CANNOT_OPEN_POST);
    +	return;
    +    }
    +
    +    /*
    +     * Read the temporary file and post in maximum 512 byte chunks.  - FM
    +     */
    +    buf[0] = '\0';
    +    sprintf(crlf, "%c%c", CR, LF);
    +    while (fgets(line, (int) sizeof(line) - 2, fd) != NULL) {
    +	if ((cp = StrChr(line, '\n')) != NULL)
    +	    *cp = '\0';
    +	if (line[0] == '.') {
    +	    /*
    +	     * A single '.' means end of transmission for nntp.  Lead dots on
    +	     * lines normally are trimmed and the EOF is not registered if the
    +	     * dot was not followed by CRLF.  We prepend an extra dot for any
    +	     * line beginning with one, to retain the one intended, as well as
    +	     * avoid a false EOF signal.  We know we have room for it in the
    +	     * buffer, because we normally send when it would exceed 510.  - FM
    +	     */
    +	    strcat(buf, ".");
    +	    blen++;
    +	}
    +	llen = (int) strlen(line);
    +	if (in_header && !strncasecomp(line, "From:", 5)) {
    +	    seen_header = 1;
    +	    seen_fromline = 1;
    +	}
    +	if (in_header && line[0] == '\0') {
    +	    if (seen_header) {
    +		in_header = 0;
    +		if (!seen_fromline) {
    +		    if (blen >= (int) sizeof(buf) - 35) {
    +			IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
    +			buf[blen = 0] = 0;
    +		    }
    +		    strcat(buf, "From: anonymous@nowhere.you.know");
    +		    strcat(buf, crlf);
    +		    blen += 34;
    +		}
    +	    } else {
    +		continue;
    +	    }
    +	} else if (in_header) {
    +	    if (valid_header(line)) {
    +		seen_header = 1;
    +	    } else {
    +		continue;
    +	    }
    +	}
    +	strcat(line, crlf);
    +	llen += 2;
    +	if ((blen + llen) >= (int) sizeof(buf) - 1) {
    +	    IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
    +	    buf[blen = 0] = 0;
    +	}
    +	strcat(buf, line);
    +	blen += llen;
    +    }
    +    fclose(fd);
    +    HTSYS_remove(postfile);
    +
    +    /*
    +     * Send the nntp EOF and get the server's response.  - FM
    +     */
    +    if (blen >= (int) sizeof(buf) - 4) {
    +	IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
    +	buf[blen = 0] = 0;
    +    }
    +    strcat(buf, ".");
    +    strcat(buf, crlf);
    +    blen += 3;
    +    IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
    +
    +    status = response(NULL);
    +    if (status == 240) {
    +	/*
    +	 * Successful post.  - FM
    +	 */
    +	HTProgress(response_text);
    +    } else {
    +	/*
    +	 * Shucks, something went wrong.  - FM
    +	 */
    +	HTAlert(response_text);
    +    }
    +}
    +
    +#ifdef NEWS_DEBUG
    +/* for DEBUG 1997/11/07 (Fri) 17:20:16 */
    +void debug_print(unsigned char *p)
    +{
    +    while (*p) {
    +	if (*p == '\0')
    +	    break;
    +	if (*p == 0x1b)
    +	    printf("[ESC]");
    +	else if (*p == '\n')
    +	    printf("[NL]");
    +	else if (*p < ' ' || *p >= 0x80)
    +	    printf("(%02x)", *p);
    +	else
    +	    putchar(*p);
    +	p++;
    +    }
    +    printf("]\n");
    +}
    +#endif
    +
    +static char *decode_mime(char **str)
    +{
    +    static char empty[] = "";
    +
    +#ifdef SH_EX
    +    if (HTCJK != JAPANESE)
    +	return *str;
    +#endif
    +    HTmmdecode(str, *str);
    +    return HTrjis(str, *str) ? *str : empty;
    +}
    +
    +/*	Read in an Article					read_article
    + *	------------------
    + *
    + *	Note the termination condition of a single dot on a line by itself.
    + *	RFC 977 specifies that the line "folding" of RFC850 is not used, so we
    + *	do not handle it here.
    + *
    + * On entry,
    + *	s	Global socket number is OK
    + *	HT	Global hypertext object is ready for appending text
    + */
    +static int read_article(HTParentAnchor *thisanchor)
    +{
    +    char line[LINE_LENGTH + 1];
    +    char *full_line = NULL;
    +    char *subject = NULL;	/* Subject string           */
    +    char *from = NULL;		/* From string              */
    +    char *replyto = NULL;	/* Reply-to string          */
    +    char *date = NULL;		/* Date string              */
    +    char *organization = NULL;	/* Organization string      */
    +    char *references = NULL;	/* Hrefs for other articles */
    +    char *newsgroups = NULL;	/* Newsgroups list          */
    +    char *followupto = NULL;	/* Followup list            */
    +    char *href = NULL;
    +    char *p = line;
    +    char *cp;
    +    const char *ccp;
    +    BOOL done = NO;
    +
    +    /*
    +     * Read in the HEADer of the article.
    +     *
    +     * The header fields are either ignored, or formatted and put into the
    +     * text.
    +     */
    +    if (!diagnostic && !rawtext) {
    +	while (!done) {
    +	    int ich = NEXT_CHAR;
    +
    +	    *p++ = (char) ich;
    +	    if (ich == EOF) {
    +		if (interrupted_in_htgetcharacter) {
    +		    interrupted_in_htgetcharacter = 0;
    +		    CTRACE((tfp,
    +			    "HTNews: Interrupted on read, closing socket %d\n",
    +			    s));
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		    return (HT_INTERRUPTED);
    +		}
    +		abort_socket();	/* End of file, close socket */
    +		return (HT_LOADED);	/* End of file on response */
    +	    }
    +	    if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
    +		*--p = '\0';	/* Terminate the string */
    +		CTRACE((tfp, "H %s\n", line));
    +
    +		if (line[0] == '\t' || line[0] == ' ') {
    +		    int i = 0;
    +
    +		    while (line[i]) {
    +			if (line[i] == '\t')
    +			    line[i] = ' ';
    +			i++;
    +		    }
    +		    if (full_line == NULL) {
    +			StrAllocCopy(full_line, line);
    +		    } else {
    +			StrAllocCat(full_line, line);
    +		    }
    +		} else {
    +		    StrAllocCopy(full_line, line);
    +		}
    +
    +		if (full_line[0] == '.') {
    +		    /*
    +		     * End of article?
    +		     */
    +		    if (UCH(full_line[1]) < ' ') {
    +			done = YES;
    +			break;
    +		    }
    +		} else if (UCH(full_line[0]) < ' ') {
    +		    break;	/* End of Header? */
    +
    +		} else if (match(full_line, "SUBJECT:")) {
    +		    StrAllocCopy(subject, HTStrip(StrChr(full_line, ':') + 1));
    +		    decode_mime(&subject);
    +		} else if (match(full_line, "DATE:")) {
    +		    StrAllocCopy(date, HTStrip(StrChr(full_line, ':') + 1));
    +
    +		} else if (match(full_line, "ORGANIZATION:")) {
    +		    StrAllocCopy(organization,
    +				 HTStrip(StrChr(full_line, ':') + 1));
    +		    decode_mime(&organization);
    +
    +		} else if (match(full_line, "FROM:")) {
    +		    StrAllocCopy(from, HTStrip(StrChr(full_line, ':') + 1));
    +		    decode_mime(&from);
    +
    +		} else if (match(full_line, "REPLY-TO:")) {
    +		    StrAllocCopy(replyto, HTStrip(StrChr(full_line, ':') + 1));
    +		    decode_mime(&replyto);
    +
    +		} else if (match(full_line, "NEWSGROUPS:")) {
    +		    StrAllocCopy(newsgroups, HTStrip(StrChr(full_line, ':') + 1));
    +
    +		} else if (match(full_line, "REFERENCES:")) {
    +		    StrAllocCopy(references, HTStrip(StrChr(full_line, ':') + 1));
    +
    +		} else if (match(full_line, "FOLLOWUP-TO:")) {
    +		    StrAllocCopy(followupto, HTStrip(StrChr(full_line, ':') + 1));
    +
    +		} else if (match(full_line, "MESSAGE-ID:")) {
    +		    char *msgid = HTStrip(full_line + 11);
    +
    +		    if (msgid[0] == '<' && msgid[strlen(msgid) - 1] == '>') {
    +			msgid[strlen(msgid) - 1] = '\0';	/* Chop > */
    +			msgid++;	/* Chop < */
    +			HTAnchor_setMessageID(thisanchor, msgid);
    +		    }
    +
    +		}		/* end if match */
    +		p = line;	/* Restart at beginning */
    +	    }			/* if end of line */
    +	}			/* Loop over characters */
    +	FREE(full_line);
    +
    +	START(HTML_HEAD);
    +	PUTC('\n');
    +	START(HTML_TITLE);
    +	if (subject && *subject != '\0')
    +	    PUTS(subject);
    +	else
    +	    PUTS("No Subject");
    +	END(HTML_TITLE);
    +	PUTC('\n');
    +	/*
    +	 * Put in the owner as a link rel.
    +	 */
    +	if (from || replyto) {
    +	    char *temp = NULL;
    +
    +	    StrAllocCopy(temp, author_address(replyto ? replyto : from));
    +	    StrAllocCopy(href, STR_MAILTO_URL);
    +	    if (StrChr(temp, '%') || StrChr(temp, '?')) {
    +		cp = HTEscape(temp, URL_XPALPHAS);
    +		StrAllocCat(href, cp);
    +		FREE(cp);
    +	    } else {
    +		StrAllocCat(href, temp);
    +	    }
    +	    start_link(href, "made");
    +	    PUTC('\n');
    +	    FREE(temp);
    +	}
    +	END(HTML_HEAD);
    +	PUTC('\n');
    +
    +	START(HTML_H1);
    +	if (subject && *subject != '\0')
    +	    PUTS(subject);
    +	else
    +	    PUTS("No Subject");
    +	END(HTML_H1);
    +	PUTC('\n');
    +
    +	if (subject)
    +	    FREE(subject);
    +
    +	START(HTML_DLC);
    +	PUTC('\n');
    +
    +	if (from || replyto) {
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("From:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    if (from)
    +		PUTS(from);
    +	    else
    +		PUTS(replyto);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +
    +	    if (!replyto)
    +		StrAllocCopy(replyto, from);
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Reply to:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    start_anchor(href);
    +	    if (*replyto != '<')
    +		PUTS(author_name(replyto));
    +	    else
    +		PUTS(author_address(replyto));
    +	    END(HTML_A);
    +	    START(HTML_BR);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +
    +	    FREE(from);
    +	    FREE(replyto);
    +	}
    +
    +	if (date) {
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Date:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    PUTS(date);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +	    FREE(date);
    +	}
    +
    +	if (organization) {
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Organization:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    PUTS(organization);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +	    FREE(organization);
    +	}
    +
    +	/* sanitize some headers - kw */
    +	if (newsgroups &&
    +	    ((cp = StrChr(newsgroups, '/')) ||
    +	     (cp = StrChr(newsgroups, '(')))) {
    +	    *cp = '\0';
    +	}
    +	if (newsgroups && !*newsgroups) {
    +	    FREE(newsgroups);
    +	}
    +	if (followupto &&
    +	    ((cp = StrChr(followupto, '/')) ||
    +	     (cp = StrChr(followupto, '(')))) {
    +	    *cp = '\0';
    +	}
    +	if (followupto && !*followupto) {
    +	    FREE(followupto);
    +	}
    +
    +	if (newsgroups && HTCanPost) {
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Newsgroups:");
    +	    END(HTML_B);
    +	    PUTC('\n');
    +	    MAYBE_END(HTML_DT);
    +	    START(HTML_DD);
    +	    write_anchors(newsgroups);
    +	    MAYBE_END(HTML_DD);
    +	    PUTC('\n');
    +	}
    +
    +	if (followupto && !strcasecomp(followupto, "poster")) {
    +	    /*
    +	     * "Followup-To:  poster" has special meaning.  Don't use it to
    +	     * construct a newsreply link.  -kw
    +	     */
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Followup to:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    if (href) {
    +		start_anchor(href);
    +		PUTS("poster");
    +		END(HTML_A);
    +	    } else {
    +		PUTS("poster");
    +	    }
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +	    FREE(followupto);
    +	}
    +
    +	if (newsgroups && HTCanPost) {
    +	    /*
    +	     * We have permission to POST to this host, so add a link for
    +	     * posting followups for this article.  - FM
    +	     */
    +	    if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
    +		StrAllocCopy(href, "snewsreply://");
    +	    else
    +		StrAllocCopy(href, "newsreply://");
    +	    StrAllocCat(href, NewsHost);
    +	    StrAllocCat(href, "/");
    +	    StrAllocCat(href, (followupto ? followupto : newsgroups));
    +	    if (*href == 'n' &&
    +		(ccp = HTAnchor_messageID(thisanchor)) && *ccp) {
    +		StrAllocCat(href, ";ref=");
    +		if (StrChr(ccp, '<') || StrChr(ccp, '&') ||
    +		    StrChr(ccp, ' ') || StrChr(ccp, ':') ||
    +		    StrChr(ccp, '/') || StrChr(ccp, '%') ||
    +		    StrChr(ccp, ';')) {
    +		    char *cp1 = HTEscape(ccp, URL_XPALPHAS);
    +
    +		    StrAllocCat(href, cp1);
    +		    FREE(cp1);
    +		} else {
    +		    StrAllocCat(href, ccp);
    +		}
    +	    }
    +
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("Followup to:");
    +	    END(HTML_B);
    +	    PUTC(' ');
    +	    start_anchor(href);
    +	    if (StrChr((followupto ? followupto : newsgroups), ',')) {
    +		PUTS("newsgroups");
    +	    } else {
    +		PUTS("newsgroup");
    +	    }
    +	    END(HTML_A);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +	}
    +	FREE(newsgroups);
    +	FREE(followupto);
    +
    +	if (references) {
    +	    START(HTML_DT);
    +	    START(HTML_B);
    +	    PUTS("References:");
    +	    END(HTML_B);
    +	    MAYBE_END(HTML_DT);
    +	    PUTC('\n');
    +	    START(HTML_DD);
    +	    write_anchors(references);
    +	    MAYBE_END(HTML_DD);
    +	    PUTC('\n');
    +	    FREE(references);
    +	}
    +
    +	END(HTML_DLC);
    +	PUTC('\n');
    +	FREE(href);
    +    }
    +
    +    if (rawtext) {
    +	/*
    +	 * No tags, and never do a PUTC.  - kw
    +	 */
    +	;
    +    } else if (diagnostic) {
    +	/*
    +	 * Read in the HEAD and BODY of the Article as XMP formatted text.  -
    +	 * FM
    +	 */
    +	START(HTML_XMP);
    +	PUTC('\n');
    +    } else {
    +	/*
    +	 * Read in the BODY of the Article as PRE formatted text.  - FM
    +	 */
    +	START(HTML_PRE);
    +	PUTC('\n');
    +    }
    +
    +    p = line;
    +    while (!done) {
    +	int ich = NEXT_CHAR;
    +
    +	*p++ = (char) ich;
    +	if (ich == EOF) {
    +	    if (interrupted_in_htgetcharacter) {
    +		interrupted_in_htgetcharacter = 0;
    +		CTRACE((tfp,
    +			"HTNews: Interrupted on read, closing socket %d\n",
    +			s));
    +		NEWS_NETCLOSE(s);
    +		s = -1;
    +		return (HT_INTERRUPTED);
    +	    }
    +	    abort_socket();	/* End of file, close socket */
    +	    return (HT_LOADED);	/* End of file on response */
    +	}
    +	if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
    +	    *p = '\0';		/* Terminate the string */
    +	    CTRACE((tfp, "B %s", line));
    +#ifdef NEWS_DEBUG		/* 1997/11/09 (Sun) 15:56:11 */
    +	    debug_print(line);	/* @@@ */
    +#endif
    +	    if (line[0] == '.') {
    +		/*
    +		 * End of article?
    +		 */
    +		if (UCH(line[1]) < ' ') {
    +		    break;
    +		} else {	/* Line starts with dot */
    +		    if (rawtext) {
    +			RAW_PUTS(&line[1]);
    +		    } else {
    +			PUTS(&line[1]);		/* Ignore first dot */
    +		    }
    +		}
    +	    } else {
    +		if (rawtext) {
    +		    RAW_PUTS(line);
    +		} else if (diagnostic || !scan_for_buried_news_references) {
    +		    /*
    +		     * All lines are passed as unmodified source.  - FM
    +		     */
    +		    PUTS(line);
    +		} else {
    +		    /*
    +		     * Normal lines are scanned for buried references to other
    +		     * articles.  Unfortunately, it could pick up mail
    +		     * addresses as well!  It also can corrupt uuencoded
    +		     * messages!  So we don't do this when fetching articles as
    +		     * WWW_SOURCE or when downloading (diagnostic is TRUE) or
    +		     * if the client has set scan_for_buried_news_references to
    +		     * FALSE.  Otherwise, we convert all "<...@...>" strings
    +		     * preceded by "rticle " to "news:...@..." links, and any
    +		     * strings that look like URLs to links.  - FM
    +		     */
    +		    char *l = line;
    +		    char *p2;
    +
    +		    while ((p2 = strstr(l, "rticle <")) != NULL) {
    +			char *q = strrchr(p2, '>');
    +			char *at = strrchr(p2, '@');
    +
    +			if (q && at && at < q) {
    +			    char c = q[1];
    +
    +			    q[1] = 0;	/* chop up */
    +			    p2 += 7;
    +			    *p2 = 0;
    +			    while (*l) {
    +				if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
    +				    StrNCmp(l, "snews://", 8) &&
    +				    StrNCmp(l, "nntp://", 7) &&
    +				    StrNCmp(l, "snewspost:", 10) &&
    +				    StrNCmp(l, "snewsreply:", 11) &&
    +				    StrNCmp(l, "newspost:", 9) &&
    +				    StrNCmp(l, "newsreply:", 10) &&
    +				    StrNCmp(l, "ftp://", 6) &&
    +				    StrNCmp(l, "file:/", 6) &&
    +				    StrNCmp(l, "finger://", 9) &&
    +				    StrNCmp(l, "http://", 7) &&
    +				    StrNCmp(l, "https://", 8) &&
    +				    StrNCmp(l, "wais://", 7) &&
    +				    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
    +				    StrNCmp(l, "cso://", 6) &&
    +				    StrNCmp(l, "gopher://", 9)) {
    +				    PUTC(*l++);
    +				} else {
    +				    StrAllocCopy(href, l);
    +				    start_anchor(strtok(href, " \r\n\t,>)\""));
    +				    while (*l && !StrChr(" \r\n\t,>)\"", *l))
    +					PUTC(*l++);
    +				    END(HTML_A);
    +				    FREE(href);
    +				}
    +			    }
    +			    *p2 = '<';	/* again */
    +			    *q = 0;
    +			    start_anchor(p2 + 1);
    +			    *q = '>';	/* again */
    +			    PUTS(p2);
    +			    END(HTML_A);
    +			    q[1] = c;	/* again */
    +			    l = q + 1;
    +			} else {
    +			    break;	/* line has unmatched <> */
    +			}
    +		    }
    +		    while (*l) {	/* Last bit of the line */
    +			if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
    +			    StrNCmp(l, "snews://", 8) &&
    +			    StrNCmp(l, "nntp://", 7) &&
    +			    StrNCmp(l, "snewspost:", 10) &&
    +			    StrNCmp(l, "snewsreply:", 11) &&
    +			    StrNCmp(l, "newspost:", 9) &&
    +			    StrNCmp(l, "newsreply:", 10) &&
    +			    StrNCmp(l, "ftp://", 6) &&
    +			    StrNCmp(l, "file:/", 6) &&
    +			    StrNCmp(l, "finger://", 9) &&
    +			    StrNCmp(l, "http://", 7) &&
    +			    StrNCmp(l, "https://", 8) &&
    +			    StrNCmp(l, "wais://", 7) &&
    +			    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
    +			    StrNCmp(l, "cso://", 6) &&
    +			    StrNCmp(l, "gopher://", 9))
    +			    PUTC(*l++);
    +			else {
    +			    StrAllocCopy(href, l);
    +			    start_anchor(strtok(href, " \r\n\t,>)\""));
    +			    while (*l && !StrChr(" \r\n\t,>)\"", *l))
    +				PUTC(*l++);
    +			    END(HTML_A);
    +			    FREE(href);
    +			}
    +		    }
    +		}		/* if diagnostic or not scan_for_buried_news_references */
    +	    }			/* if not dot */
    +	    p = line;		/* Restart at beginning */
    +	}			/* if end of line */
    +    }				/* Loop over characters */
    +
    +    if (rawtext)
    +	return (HT_LOADED);
    +
    +    if (diagnostic)
    +	END(HTML_XMP);
    +    else
    +	END(HTML_PRE);
    +    PUTC('\n');
    +    return (HT_LOADED);
    +}
    +
    +/*	Read in a List of Newsgroups
    + *	----------------------------
    + *
    + *  Note the termination condition of a single dot on a line by itself.
    + *  RFC 977 specifies that the line "folding" of RFC850 is not used,
    + *  so we do not handle it here.
    + */
    +static int read_list(char *arg)
    +{
    +    char line[LINE_LENGTH + 1];
    +    char *p;
    +    BOOL done = NO;
    +    BOOL head = NO;
    +    BOOL tail = NO;
    +    BOOL skip_this_line = NO;
    +    BOOL skip_rest_of_line = NO;
    +    int listing = 0;
    +    char *pattern = NULL;
    +    int len = 0;
    +
    +    /*
    +     * Support head or tail matches for groups to list.  - FM
    +     */
    +    if (arg && strlen(arg) > 1) {
    +	if (*arg == '*') {
    +	    tail = YES;
    +	    StrAllocCopy(pattern, (arg + 1));
    +	} else if (arg[strlen(arg) - 1] == '*') {
    +	    head = YES;
    +	    StrAllocCopy(pattern, arg);
    +	    pattern[strlen(pattern) - 1] = '\0';
    +	}
    +	if (tail || head) {
    +	    len = (int) strlen(pattern);
    +	}
    +
    +    }
    +
    +    /*
    +     * Read the server's reply.
    +     *
    +     * The lines are scanned for newsgroup names and descriptions.
    +     */
    +    START(HTML_HEAD);
    +    PUTC('\n');
    +    START(HTML_TITLE);
    +    PUTS("Newsgroups");
    +    END(HTML_TITLE);
    +    PUTC('\n');
    +    END(HTML_HEAD);
    +    PUTC('\n');
    +    START(HTML_H1);
    +    PUTS("Newsgroups");
    +    END(HTML_H1);
    +    PUTC('\n');
    +    *(p = line) = '\0';
    +    START(HTML_DLC);
    +    PUTC('\n');
    +    while (!done) {
    +	int ich = NEXT_CHAR;
    +	char ch = (char) ich;
    +
    +	if (ich == EOF) {
    +	    if (interrupted_in_htgetcharacter) {
    +		interrupted_in_htgetcharacter = 0;
    +		CTRACE((tfp,
    +			"HTNews: Interrupted on read, closing socket %d\n",
    +			s));
    +		NEWS_NETCLOSE(s);
    +		s = -1;
    +		return (HT_INTERRUPTED);
    +	    }
    +	    abort_socket();	/* End of file, close socket */
    +	    FREE(pattern);
    +	    return (HT_LOADED);	/* End of file on response */
    +	} else if (skip_this_line) {
    +	    if (ch == LF) {
    +		skip_this_line = skip_rest_of_line = NO;
    +		p = line;
    +	    }
    +	    continue;
    +	} else if (skip_rest_of_line) {
    +	    if (ch != LF) {
    +		continue;
    +	    }
    +	} else if (p == &line[LINE_LENGTH]) {
    +	    CTRACE((tfp, "b %.*s%c[...]\n", (LINE_LENGTH), line, ch));
    +	    *p = '\0';
    +	    if (ch == LF) {
    +		;		/* Will be dealt with below */
    +	    } else if (WHITE(ch)) {
    +		ch = LF;	/* May treat as line without description */
    +		skip_this_line = YES;	/* ...and ignore until LF */
    +	    } else if (StrChr(line, ' ') == NULL &&
    +		       StrChr(line, '\t') == NULL) {
    +		/* No separator found */
    +		CTRACE((tfp, "HTNews..... group name too long, discarding.\n"));
    +		skip_this_line = YES;	/* ignore whole line */
    +		continue;
    +	    } else {
    +		skip_rest_of_line = YES;	/* skip until ch == LF found */
    +	    }
    +	} else {
    +	    *p++ = ch;
    +	}
    +	if (ch == LF) {
    +	    skip_rest_of_line = NO;	/* done, reset flag */
    +	    *p = '\0';		/* Terminate the string */
    +	    CTRACE((tfp, "B %s", line));
    +	    if (line[0] == '.') {
    +		/*
    +		 * End of article?
    +		 */
    +		if (UCH(line[1]) < ' ') {
    +		    break;
    +		} else {	/* Line starts with dot */
    +		    START(HTML_DT);
    +		    PUTS(&line[1]);
    +		    MAYBE_END(HTML_DT);
    +		}
    +	    } else if (line[0] == '#') {	/* Comment? */
    +		p = line;	/* Restart at beginning */
    +		continue;
    +	    } else {
    +		/*
    +		 * Normal lines are scanned for references to newsgroups.
    +		 */
    +		int i = 0;
    +
    +		/* find whitespace if it exits */
    +		for (; line[i] != '\0' && !WHITE(line[i]); i++) ;	/* null body */
    +
    +		if (line[i] != '\0') {
    +		    line[i] = '\0';
    +		    if ((head && strncasecomp(line, pattern, len)) ||
    +			(tail && (i < len ||
    +				  strcasecomp((line + (i - len)), pattern)))) {
    +			p = line;	/* Restart at beginning */
    +			continue;
    +		    }
    +		    START(HTML_DT);
    +		    write_anchor(line, line);
    +		    listing++;
    +		    MAYBE_END(HTML_DT);
    +		    PUTC('\n');
    +		    START(HTML_DD);
    +		    PUTS(&line[i + 1]);		/* put description */
    +		    MAYBE_END(HTML_DD);
    +		} else {
    +		    if ((head && strncasecomp(line, pattern, len)) ||
    +			(tail && (i < len ||
    +				  strcasecomp((line + (i - len)), pattern)))) {
    +			p = line;	/* Restart at beginning */
    +			continue;
    +		    }
    +		    START(HTML_DT);
    +		    write_anchor(line, line);
    +		    MAYBE_END(HTML_DT);
    +		    listing++;
    +		}
    +	    }			/* if not dot */
    +	    p = line;		/* Restart at beginning */
    +	}			/* if end of line */
    +    }				/* Loop over characters */
    +    if (!listing) {
    +	char *msg = NULL;
    +
    +	START(HTML_DT);
    +	HTSprintf0(&msg, gettext("No matches for: %s"), arg);
    +	PUTS(msg);
    +	MAYBE_END(HTML_DT);
    +	FREE(msg);
    +    }
    +    END(HTML_DLC);
    +    PUTC('\n');
    +    FREE(pattern);
    +    return (HT_LOADED);
    +}
    +
    +/*	Read in a Newsgroup
    + *	-------------------
    + *
    + *  Unfortunately, we have to ask for each article one by one if we
    + *  want more than one field.
    + *
    + */
    +static int read_group(const char *groupName,
    +		      int first_required,
    +		      int last_required)
    +{
    +    char line[LINE_LENGTH + 1];
    +    char *author = NULL;
    +    char *subject = NULL;
    +    char *date = NULL;
    +    int i;
    +    char *p;
    +    BOOL done;
    +
    +    char buffer[LINE_LENGTH + 1];
    +    char *temp = NULL;
    +    char *reference = NULL;	/* Href for article */
    +    int art;			/* Article number WITHIN GROUP */
    +    int status, count, first, last;	/* Response fields */
    +
    +    START(HTML_HEAD);
    +    PUTC('\n');
    +    START(HTML_TITLE);
    +    PUTS("Newsgroup ");
    +    PUTS(groupName);
    +    END(HTML_TITLE);
    +    PUTC('\n');
    +    END(HTML_HEAD);
    +    PUTC('\n');
    +
    +    sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
    +    CTRACE((tfp, "Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
    +	    status, count, first, last, first_required, last_required));
    +    if (last == 0) {
    +	PUTS(gettext("\nNo articles in this group.\n"));
    +	goto add_post;
    +    }
    +#define FAST_THRESHOLD 100	/* Above this, read IDs fast */
    +#define CHOP_THRESHOLD 50	/* Above this, chop off the rest */
    +
    +    if (first_required < first)
    +	first_required = first;	/* clip */
    +    if ((last_required == 0) || (last_required > last))
    +	last_required = last;
    +
    +    if (last_required < first_required) {
    +	PUTS(gettext("\nNo articles in this range.\n"));
    +	goto add_post;
    +    }
    +
    +    if (last_required - first_required + 1 > HTNewsMaxChunk) {	/* Trim this block */
    +	first_required = last_required - HTNewsChunkSize + 1;
    +    }
    +    CTRACE((tfp, "    Chunk will be (%d-%d)\n",
    +	    first_required, last_required));
    +
    +    /*
    +     * Set window title.
    +     */
    +    HTSprintf0(&temp, gettext("%s,  Articles %d-%d"),
    +	       groupName, first_required, last_required);
    +    START(HTML_H1);
    +    PUTS(temp);
    +    FREE(temp);
    +    END(HTML_H1);
    +    PUTC('\n');
    +
    +    /*
    +     * Link to earlier articles.
    +     */
    +    if (first_required > first) {
    +	int before;		/* Start of one before */
    +
    +	if (first_required - HTNewsMaxChunk <= first)
    +	    before = first;
    +	else
    +	    before = first_required - HTNewsChunkSize;
    +	HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
    +		   before, first_required - 1);
    +	CTRACE((tfp, "    Block before is %s\n", dbuf));
    +	PUTC('(');
    +	start_anchor(dbuf);
    +	PUTS(gettext("Earlier articles"));
    +	END(HTML_A);
    +	PUTS("...)\n");
    +	START(HTML_P);
    +	PUTC('\n');
    +    }
    +
    +    done = NO;
    +
    +/*#define USE_XHDR*/
    +#ifdef USE_XHDR
    +    if (count > FAST_THRESHOLD) {
    +	HTSprintf0(&temp,
    +		   gettext("\nThere are about %d articles currently available in %s, IDs as follows:\n\n"),
    +		   count, groupName);
    +	PUTS(temp);
    +	FREE(temp);
    +	sprintf(buffer, "XHDR Message-ID %d-%d%c%c", first, last, CR, LF);
    +	status = response(buffer);
    +	if (status == 221) {
    +	    p = line;
    +	    while (!done) {
    +		int ich = NEXT_CHAR;
    +
    +		*p++ = ich;
    +		if (ich == EOF) {
    +		    if (interrupted_in_htgetcharacter) {
    +			interrupted_in_htgetcharacter = 0;
    +			CTRACE((tfp,
    +				"HTNews: Interrupted on read, closing socket %d\n",
    +				s));
    +			NEWS_NETCLOSE(s);
    +			s = -1;
    +			return (HT_INTERRUPTED);
    +		    }
    +		    abort_socket();	/* End of file, close socket */
    +		    return (HT_LOADED);		/* End of file on response */
    +		}
    +		if (((char) ich == '\n') || (p == &line[LINE_LENGTH])) {
    +		    *p = '\0';	/* Terminate the string */
    +		    CTRACE((tfp, "X %s", line));
    +		    if (line[0] == '.') {
    +			/*
    +			 * End of article?
    +			 */
    +			if (UCH(line[1]) < ' ') {
    +			    done = YES;
    +			    break;
    +			} else {	/* Line starts with dot */
    +			    /* Ignore strange line */
    +			}
    +		    } else {
    +			/*
    +			 * Normal lines are scanned for references to articles.
    +			 */
    +			char *space = StrChr(line, ' ');
    +
    +			if (space++)
    +			    write_anchor(space, space);
    +		    }		/* if not dot */
    +		    p = line;	/* Restart at beginning */
    +		}		/* if end of line */
    +	    }			/* Loop over characters */
    +
    +	    /* leaving loop with "done" set */
    +	}			/* Good status */
    +    }
    +#endif /* USE_XHDR */
    +
    +    /*
    +     * Read newsgroup using individual fields.
    +     */
    +    if (!done) {
    +	START(HTML_B);
    +	if (first == first_required && last == last_required)
    +	    PUTS(gettext("All available articles in "));
    +	else
    +	    PUTS("Articles in ");
    +	PUTS(groupName);
    +	END(HTML_B);
    +	PUTC('\n');
    +	if (LYListNewsNumbers)
    +	    start_list(first_required);
    +	else
    +	    START(HTML_UL);
    +	for (art = first_required; art <= last_required; art++) {
    +/*#define OVERLAP*/
    +#ifdef OVERLAP
    +	    /*
    +	     * With this code we try to keep the server running flat out by
    +	     * queuing just one extra command ahead of time.  We assume (1)
    +	     * that the server won't abort if it gets input during output, and
    +	     * (2) that TCP buffering is enough for the two commands.  Both
    +	     * these assumptions seem very reasonable.  However, we HAVE had a
    +	     * hangup with a loaded server.
    +	     */
    +	    if (art == first_required) {
    +		if (art == last_required) {	/* Only one */
    +		    sprintf(buffer, "HEAD %d%c%c",
    +			    art, CR, LF);
    +		    status = response(buffer);
    +		} else {	/* First of many */
    +		    sprintf(buffer, "HEAD %d%c%cHEAD %d%c%c",
    +			    art, CR, LF, art + 1, CR, LF);
    +		    status = response(buffer);
    +		}
    +	    } else if (art == last_required) {	/* Last of many */
    +		status = response(NULL);
    +	    } else {		/* Middle of many */
    +		sprintf(buffer, "HEAD %d%c%c", art + 1, CR, LF);
    +		status = response(buffer);
    +	    }
    +#else /* Not OVERLAP: */
    +	    sprintf(buffer, "HEAD %d%c%c", art, CR, LF);
    +	    status = response(buffer);
    +#endif /* OVERLAP */
    +	    /*
    +	     * Check for a good response (221) for the HEAD request, and if so,
    +	     * parse it.  Otherwise, indicate the error so that the number of
    +	     * listings corresponds to what's claimed for the range, and if we
    +	     * are listing numbers via an ordered list, they stay in synchrony
    +	     * with the article numbers.  - FM
    +	     */
    +	    if (status == 221) {	/* Head follows - parse it: */
    +		p = line;	/* Write pointer */
    +		done = NO;
    +		while (!done) {
    +		    int ich = NEXT_CHAR;
    +
    +		    *p++ = (char) ich;
    +		    if (ich == EOF) {
    +			if (interrupted_in_htgetcharacter) {
    +			    interrupted_in_htgetcharacter = 0;
    +			    CTRACE((tfp,
    +				    "HTNews: Interrupted on read, closing socket %d\n",
    +				    s));
    +			    NEWS_NETCLOSE(s);
    +			    s = -1;
    +			    return (HT_INTERRUPTED);
    +			}
    +			abort_socket();		/* End of file, close socket */
    +			return (HT_LOADED);	/* End of file on response */
    +		    }
    +		    if (((char) ich == LF) ||
    +			(p == &line[LINE_LENGTH])) {
    +
    +			*--p = '\0';	/* Terminate  & chop LF */
    +			p = line;	/* Restart at beginning */
    +			CTRACE((tfp, "G %s\n", line));
    +			switch (line[0]) {
    +
    +			case '.':
    +			    /*
    +			     * End of article?
    +			     */
    +			    done = (BOOL) (UCH(line[1]) < ' ');
    +			    break;
    +
    +			case 'S':
    +			case 's':
    +			    if (match(line, "SUBJECT:")) {
    +				StrAllocCopy(subject, line + 9);
    +				decode_mime(&subject);
    +			    }
    +			    break;
    +
    +			case 'M':
    +			case 'm':
    +			    if (match(line, "MESSAGE-ID:")) {
    +				char *addr = HTStrip(line + 11) + 1;	/* Chop < */
    +
    +				addr[strlen(addr) - 1] = '\0';	/* Chop > */
    +				StrAllocCopy(reference, addr);
    +			    }
    +			    break;
    +
    +			case 'f':
    +			case 'F':
    +			    if (match(line, "FROM:")) {
    +				char *p2;
    +
    +				StrAllocCopy(author, StrChr(line, ':') + 1);
    +				decode_mime(&author);
    +				p2 = author + strlen(author) - 1;
    +				if (*p2 == LF)
    +				    *p2 = '\0';		/* Chop off newline */
    +			    }
    +			    break;
    +
    +			case 'd':
    +			case 'D':
    +			    if (LYListNewsDates && match(line, "DATE:")) {
    +				StrAllocCopy(date,
    +					     HTStrip(StrChr(line, ':') + 1));
    +			    }
    +			    break;
    +
    +			}	/* end switch on first character */
    +		    }		/* if end of line */
    +		}		/* Loop over characters */
    +
    +		PUTC('\n');
    +		START(HTML_LI);
    +		p = decode_mime(&subject);
    +		HTSprintf0(&temp, "\"%s\"", NonNull(p));
    +		if (reference) {
    +		    write_anchor(temp, reference);
    +		    FREE(reference);
    +		} else {
    +		    PUTS(temp);
    +		}
    +		FREE(temp);
    +
    +		if (author != NULL) {
    +		    PUTS(" - ");
    +		    if (LYListNewsDates)
    +			START(HTML_I);
    +		    PUTS(decode_mime(&author));
    +		    if (LYListNewsDates)
    +			END(HTML_I);
    +		    FREE(author);
    +		}
    +		if (date) {
    +		    if (!diagnostic) {
    +			for (i = 0; date[i]; i++) {
    +			    if (date[i] == ' ') {
    +				date[i] = HT_NON_BREAK_SPACE;
    +			    }
    +			}
    +		    }
    +		    sprintf(buffer, " [%.*s]", (int) (sizeof(buffer) - 4), date);
    +		    PUTS(buffer);
    +		    FREE(date);
    +		}
    +		MAYBE_END(HTML_LI);
    +		/*
    +		 * Indicate progress!  @@@@@@
    +		 */
    +	    } else if (status == HT_INTERRUPTED) {
    +		interrupted_in_htgetcharacter = 0;
    +		CTRACE((tfp,
    +			"HTNews: Interrupted on read, closing socket %d\n",
    +			s));
    +		NEWS_NETCLOSE(s);
    +		s = -1;
    +		return (HT_INTERRUPTED);
    +	    } else {
    +		/*
    +		 * Use the response text on error.  - FM
    +		 */
    +		PUTC('\n');
    +		START(HTML_LI);
    +		START(HTML_I);
    +		if (LYListNewsNumbers)
    +		    LYStrNCpy(buffer, "Status:", sizeof(buffer) - 1);
    +		else
    +		    sprintf(buffer, "Status (ARTICLE %d):", art);
    +		PUTS(buffer);
    +		END(HTML_I);
    +		PUTC(' ');
    +		PUTS(response_text);
    +		MAYBE_END(HTML_LI);
    +	    }			/* Handle response to HEAD request */
    +	}			/* Loop over article */
    +	FREE(author);
    +	FREE(subject);
    +    }				/* If read headers */
    +    PUTC('\n');
    +    if (LYListNewsNumbers)
    +	END(HTML_OL);
    +    else
    +	END(HTML_UL);
    +    PUTC('\n');
    +
    +    /*
    +     * Link to later articles.
    +     */
    +    if (last_required < last) {
    +	int after;		/* End of article after */
    +
    +	after = last_required + HTNewsChunkSize;
    +	if (after == last)
    +	    HTSprintf0(&dbuf, "%s%s", NewsHREF, groupName);	/* original group */
    +	else
    +	    HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
    +		       last_required + 1, after);
    +	CTRACE((tfp, "    Block after is %s\n", dbuf));
    +	PUTC('(');
    +	start_anchor(dbuf);
    +	PUTS(gettext("Later articles"));
    +	END(HTML_A);
    +	PUTS("...)\n");
    +    }
    +
    +  add_post:
    +    if (HTCanPost) {
    +	/*
    +	 * We have permission to POST to this host, so add a link for posting
    +	 * messages to this newsgroup.  - FM
    +	 */
    +	char *href = NULL;
    +
    +	START(HTML_HR);
    +	PUTC('\n');
    +	if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
    +	    StrAllocCopy(href, "snewspost://");
    +	else
    +	    StrAllocCopy(href, "newspost://");
    +	StrAllocCat(href, NewsHost);
    +	StrAllocCat(href, "/");
    +	StrAllocCat(href, groupName);
    +	start_anchor(href);
    +	PUTS(gettext("Post to "));
    +	PUTS(groupName);
    +	END(HTML_A);
    +	FREE(href);
    +    } else {
    +	START(HTML_HR);
    +    }
    +    PUTC('\n');
    +    return (HT_LOADED);
    +}
    +
    +/*	Load by name.						HTLoadNews
    + *	=============
    + */
    +static int HTLoadNews(const char *arg,
    +		      HTParentAnchor *anAnchor,
    +		      HTFormat format_out,
    +		      HTStream *stream)
    +{
    +    char command[262];		/* The whole command */
    +    char proxycmd[260];		/* The proxy command */
    +    char groupName[GROUP_NAME_LENGTH];	/* Just the group name */
    +    int status;			/* tcp return */
    +    int retries;		/* A count of how hard we have tried */
    +    BOOL normal_url;		/* Flag: "news:" or "nntp:" (physical) URL */
    +    BOOL group_wanted;		/* Flag: group was asked for, not article */
    +    BOOL list_wanted;		/* Flag: list was asked for, not article */
    +    BOOL post_wanted;		/* Flag: new post to group was asked for */
    +    BOOL reply_wanted;		/* Flag: followup post was asked for */
    +    BOOL spost_wanted;		/* Flag: new SSL post to group was asked for */
    +    BOOL sreply_wanted;		/* Flag: followup SSL post was asked for */
    +    BOOL head_wanted = NO;	/* Flag: want HEAD of single article */
    +    int first, last;		/* First and last articles asked for */
    +    char *cp = 0;
    +    char *ListArg = NULL;
    +    char *ProxyHost = NULL;
    +    char *ProxyHREF = NULL;
    +    char *postfile = NULL;
    +
    +#ifdef USE_SSL
    +    char SSLprogress[256];
    +#endif /* USE_SSL */
    +
    +    diagnostic = (format_out == WWW_SOURCE ||	/* set global flag */
    +		  format_out == WWW_DOWNLOAD ||
    +		  format_out == WWW_DUMP);
    +    rawtext = NO;
    +
    +    CTRACE((tfp, "HTNews: Looking for %s\n", arg));
    +
    +    if (!initialized)
    +	initialized = initialize();
    +    if (!initialized)
    +	return -1;		/* FAIL */
    +
    +    FREE(NewsHREF);
    +    command[0] = '\0';
    +    command[sizeof(command) - 1] = '\0';
    +    proxycmd[0] = '\0';
    +    proxycmd[sizeof(proxycmd) - 1] = '\0';
    +
    +    {
    +	const char *p1;
    +
    +	/*
    +	 * We will ask for the document, omitting the host name & anchor.
    +	 *
    +	 * Syntax of address is
    +	 * xxx@yyy                 Article
    +	 *                Same article
    +	 * xxxxx                   News group (no "@")
    +	 * group/n1-n2             Articles n1 to n2 in group
    +	 */
    +	normal_url = (BOOL) (!StrNCmp(arg, STR_NEWS_URL, LEN_NEWS_URL) ||
    +			     !StrNCmp(arg, "nntp:", 5));
    +	spost_wanted = (BOOL) (!normal_url && strstr(arg, "snewspost:") != NULL);
    +	sreply_wanted = (BOOL) (!(normal_url || spost_wanted) &&
    +				strstr(arg, "snewsreply:") != NULL);
    +	post_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted) &&
    +			      strstr(arg, "newspost:") != NULL);
    +	reply_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted ||
    +				 post_wanted) &&
    +			       strstr(arg, "newsreply:") != NULL);
    +	group_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
    +				  post_wanted || reply_wanted) &&
    +				StrChr(arg, '@') == NULL) &&
    +			       (StrChr(arg, '*') == NULL));
    +	list_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
    +				 post_wanted || reply_wanted ||
    +				 group_wanted) &&
    +			       StrChr(arg, '@') == NULL) &&
    +			      (StrChr(arg, '*') != NULL));
    +
    +#ifndef USE_SSL
    +	if (!strncasecomp(arg, "snewspost:", 10) ||
    +	    !strncasecomp(arg, "snewsreply:", 11)) {
    +	    HTAlert(FAILED_CANNOT_POST_SSL);
    +	    return HT_NOT_LOADED;
    +	}
    +#endif /* !USE_SSL */
    +	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
    +	    /*
    +	     * Make sure we have a non-zero path for the newsgroup(s).  - FM
    +	     */
    +	    if ((p1 = strrchr(arg, '/')) != NULL) {
    +		p1++;
    +	    } else if ((p1 = strrchr(arg, ':')) != NULL) {
    +		p1++;
    +	    }
    +	    if (!(p1 && *p1)) {
    +		HTAlert(WWW_ILLEGAL_URL_MESSAGE);
    +		return (HT_NO_DATA);
    +	    }
    +	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, HTNewsHost);
    +	    } else {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, cp);
    +	    }
    +	    FREE(cp);
    +	    HTSprintf0(&NewsHREF, "%s://%.*s/",
    +		       (post_wanted ?
    +			"newspost" :
    +			(reply_wanted ?
    +			 "newreply" :
    +			 (spost_wanted ?
    +			  "snewspost" : "snewsreply"))),
    +		       (int) sizeof(command) - 15, NewsHost);
    +
    +	    /*
    +	     * If the SSL daemon is being used as a proxy, reset p1 to the
    +	     * start of the proxied URL rather than to the start of the
    +	     * newsgroup(s).  - FM
    +	     */
    +	    if (spost_wanted && strncasecomp(arg, "snewspost:", 10))
    +		p1 = strstr(arg, "snewspost:");
    +	    if (sreply_wanted && strncasecomp(arg, "snewsreply:", 11))
    +		p1 = strstr(arg, "snewsreply:");
    +
    +	    /* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
    +	    /*
    +	     * Don't use HTParse because news:  access doesn't follow
    +	     * traditional rules.  For instance, if the article reference
    +	     * contains a '#', the rest of it is lost -- JFG 10/7/92, from a
    +	     * bug report
    +	     */
    +	} else if (isNNTP_URL(arg)) {
    +	    if (((*(arg + 5) == '\0') ||
    +		 (!strcmp((arg + 5), "/") ||
    +		  !strcmp((arg + 5), "//") ||
    +		  !strcmp((arg + 5), "///"))) ||
    +		((!StrNCmp((arg + 5), "//", 2)) &&
    +		 (!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
    +		p1 = "*";
    +		group_wanted = FALSE;
    +		list_wanted = TRUE;
    +	    } else if (*(arg + 5) != '/') {
    +		p1 = (arg + 5);
    +	    } else if (*(arg + 5) == '/' && *(arg + 6) != '/') {
    +		p1 = (arg + 6);
    +	    } else {
    +		p1 = (cp ? (cp + 1) : (arg + 6));
    +	    }
    +	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, HTNewsHost);
    +	    } else {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, cp);
    +	    }
    +	    FREE(cp);
    +	    SnipIn2(command, "%s//%.*s/", STR_NNTP_URL, 9, NewsHost);
    +	    StrAllocCopy(NewsHREF, command);
    +	} else if (!strncasecomp(arg, STR_SNEWS_URL, 6)) {
    +#ifdef USE_SSL
    +	    if (((*(arg + 6) == '\0') ||
    +		 (!strcmp((arg + 6), "/") ||
    +		  !strcmp((arg + 6), "//") ||
    +		  !strcmp((arg + 6), "///"))) ||
    +		((!StrNCmp((arg + 6), "//", 2)) &&
    +		 (!(cp = StrChr((arg + 8), '/')) || *(cp + 1) == '\0'))) {
    +		p1 = "*";
    +		group_wanted = FALSE;
    +		list_wanted = TRUE;
    +	    } else if (*(arg + 6) != '/') {
    +		p1 = (arg + 6);
    +	    } else if (*(arg + 6) == '/' && *(arg + 7) != '/') {
    +		p1 = (arg + 7);
    +	    } else {
    +		p1 = (cp ? (cp + 1) : (arg + 7));
    +	    }
    +	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, HTNewsHost);
    +	    } else {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, cp);
    +	    }
    +	    FREE(cp);
    +	    sprintf(command, "%s//%.250s/", STR_SNEWS_URL, NewsHost);
    +	    StrAllocCopy(NewsHREF, command);
    +#else
    +	    HTAlert(gettext("This client does not contain support for SNEWS URLs."));
    +	    return HT_NOT_LOADED;
    +#endif /* USE_SSL */
    +	} else if (!strncasecomp(arg, "news:/", 6)) {
    +	    if (((*(arg + 6) == '\0') ||
    +		 !strcmp((arg + 6), "/") ||
    +		 !strcmp((arg + 6), "//")) ||
    +		((*(arg + 6) == '/') &&
    +		 (!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
    +		p1 = "*";
    +		group_wanted = FALSE;
    +		list_wanted = TRUE;
    +	    } else if (*(arg + 6) != '/') {
    +		p1 = (arg + 6);
    +	    } else {
    +		p1 = (cp ? (cp + 1) : (arg + 6));
    +	    }
    +	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, HTNewsHost);
    +	    } else {
    +		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		StrAllocCopy(NewsHost, cp);
    +	    }
    +	    FREE(cp);
    +	    SnipIn(command, "news://%.*s/", 9, NewsHost);
    +	    StrAllocCopy(NewsHREF, command);
    +	} else {
    +	    p1 = (arg + 5);	/* Skip "news:" prefix */
    +	    if (*p1 == '\0') {
    +		p1 = "*";
    +		group_wanted = FALSE;
    +		list_wanted = TRUE;
    +	    }
    +	    if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
    +		NEWS_NETCLOSE(s);
    +		s = -1;
    +	    }
    +	    StrAllocCopy(NewsHost, HTNewsHost);
    +	    StrAllocCopy(NewsHREF, STR_NEWS_URL);
    +	}
    +
    +	/*
    +	 * Set up any proxy for snews URLs that returns NNTP responses for Lynx
    +	 * to convert to HTML, instead of doing the conversion itself, and for
    +	 * handling posts or followups.  - TZ & FM
    +	 */
    +	if (!strncasecomp(p1, STR_SNEWS_URL, 6) ||
    +	    !strncasecomp(p1, "snewspost:", 10) ||
    +	    !strncasecomp(p1, "snewsreply:", 11)) {
    +	    StrAllocCopy(ProxyHost, NewsHost);
    +	    if ((cp = HTParse(p1, "", PARSE_HOST)) != NULL && *cp != '\0') {
    +		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, cp);
    +		StrAllocCopy(NewsHost, cp);
    +	    } else {
    +		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, NewsHost);
    +	    }
    +	    command[sizeof(command) - 2] = '\0';
    +	    FREE(cp);
    +	    sprintf(proxycmd, "GET %.*s%c%c%c%c",
    +		    (int) sizeof(proxycmd) - 9, command,
    +		    CR, LF, CR, LF);
    +	    CTRACE((tfp, "HTNews: Proxy command is '%.*s'\n",
    +		    (int) (strlen(proxycmd) - 4), proxycmd));
    +	    strcat(command, "/");
    +	    StrAllocCopy(ProxyHREF, NewsHREF);
    +	    StrAllocCopy(NewsHREF, command);
    +	    if (spost_wanted || sreply_wanted) {
    +		/*
    +		 * Reset p1 so that it points to the newsgroup(s).
    +		 */
    +		if ((p1 = strrchr(arg, '/')) != NULL) {
    +		    p1++;
    +		} else {
    +		    p1 = (strrchr(arg, ':') + 1);
    +		}
    +	    } else {
    +		char *cp2;
    +
    +		/*
    +		 * Reset p1 so that it points to the newsgroup (or a wildcard),
    +		 * or the article.
    +		 */
    +		if (!(cp2 = strrchr((p1 + 6), '/')) || *(cp2 + 1) == '\0') {
    +		    p1 = "*";
    +		    group_wanted = FALSE;
    +		    list_wanted = TRUE;
    +		} else {
    +		    p1 = (cp2 + 1);
    +		}
    +	    }
    +	}
    +
    +	/*
    +	 * Set up command for a post, listing, or article request.  - FM
    +	 */
    +	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
    +	    strcpy(command, "POST");
    +	} else if (list_wanted) {
    +	    if (strlen(p1) > 249) {
    +		FREE(ProxyHost);
    +		FREE(ProxyHREF);
    +		HTAlert(URL_TOO_LONG);
    +		return -400;
    +	    }
    +	    SnipIn(command, "XGTITLE %.*s", 11, p1);
    +	} else if (group_wanted) {
    +	    char *slash = StrChr(p1, '/');
    +
    +	    first = 0;
    +	    last = 0;
    +	    if (slash) {
    +		*slash = '\0';
    +		if (strlen(p1) >= sizeof(groupName)) {
    +		    FREE(ProxyHost);
    +		    FREE(ProxyHREF);
    +		    HTAlert(URL_TOO_LONG);
    +		    return -400;
    +		}
    +		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
    +		*slash = '/';
    +		(void) sscanf(slash + 1, "%d-%d", &first, &last);
    +		if ((first > 0) && (isdigit(UCH(*(slash + 1)))) &&
    +		    (StrChr(slash + 1, '-') == NULL || first == last)) {
    +		    /*
    +		     * We got a number greater than 0, which will be loaded as
    +		     * first, and either no range or the range computes to
    +		     * zero, so make last negative, as a flag to select the
    +		     * group and then fetch an article by number (first)
    +		     * instead of by messageID.  - FM
    +		     */
    +		    last = -1;
    +		}
    +	    } else {
    +		if (strlen(p1) >= sizeof(groupName)) {
    +		    FREE(ProxyHost);
    +		    FREE(ProxyHREF);
    +		    HTAlert(URL_TOO_LONG);
    +		    return -400;
    +		}
    +		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
    +	    }
    +	    SnipIn(command, "GROUP %.*s", 9, groupName);
    +	} else {
    +	    size_t add_open = (size_t) (StrChr(p1, '<') == 0);
    +	    size_t add_close = (size_t) (StrChr(p1, '>') == 0);
    +
    +	    if (strlen(p1) + add_open + add_close >= 252) {
    +		FREE(ProxyHost);
    +		FREE(ProxyHREF);
    +		HTAlert(URL_TOO_LONG);
    +		return -400;
    +	    }
    +	    sprintf(command, "ARTICLE %s%.*s%s",
    +		    add_open ? "<" : "",
    +		    (int) (sizeof(command) - (11 + add_open + add_close)),
    +		    p1,
    +		    add_close ? ">" : "");
    +	}
    +
    +	{
    +	    char *p = command + strlen(command);
    +
    +	    /*
    +	     * Terminate command with CRLF, as in RFC 977.
    +	     */
    +	    *p++ = CR;		/* Macros to be correct on Mac */
    +	    *p++ = LF;
    +	    *p = 0;
    +	}
    +	StrAllocCopy(ListArg, p1);
    +    }				/* scope of p1 */
    +
    +    if (!*arg) {
    +	FREE(NewsHREF);
    +	FREE(ProxyHost);
    +	FREE(ProxyHREF);
    +	FREE(ListArg);
    +	return NO;		/* Ignore if no name */
    +    }
    +
    +    if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted ||
    +	  (group_wanted && last != -1) || list_wanted)) {
    +	head_wanted = anAnchor->isHEAD;
    +	if (head_wanted && !StrNCmp(command, "ARTICLE ", 8)) {
    +	    /* overwrite "ARTICLE" - hack... */
    +	    strcpy(command, "HEAD ");
    +	    for (cp = command + 5;; cp++)
    +		if ((*cp = *(cp + 3)) == '\0')
    +		    break;
    +	}
    +	rawtext = (BOOL) (head_wanted || keep_mime_headers);
    +    }
    +    if (rawtext) {
    +	rawtarget = HTStreamStack(WWW_PLAINTEXT,
    +				  format_out,
    +				  stream, anAnchor);
    +	if (!rawtarget) {
    +	    FREE(NewsHost);
    +	    FREE(NewsHREF);
    +	    FREE(ProxyHost);
    +	    FREE(ProxyHREF);
    +	    FREE(ListArg);
    +	    HTAlert(gettext("No target for raw text!"));
    +	    return (HT_NOT_LOADED);
    +	}			/* Copy routine entry points */
    +	rawtargetClass = *rawtarget->isa;
    +    } else
    +	/*
    +	 * Make a hypertext object with an anchor list.
    +	 */
    +    if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted)) {
    +	target = HTML_new(anAnchor, format_out, stream);
    +	targetClass = *target->isa;	/* Copy routine entry points */
    +    }
    +
    +    /*
    +     * Now, let's get a stream setup up from the NewsHost.
    +     */
    +    for (retries = 0; retries < 2; retries++) {
    +	if (s < 0) {
    +	    /* CONNECTING to news host */
    +	    char url[260];
    +
    +	    if (!strcmp(NewsHREF, STR_NEWS_URL)) {
    +		SnipIn(url, "lose://%.*s/", 9, NewsHost);
    +	    } else if (ProxyHREF) {
    +		SnipIn(url, "%.*s", 1, ProxyHREF);
    +	    } else {
    +		SnipIn(url, "%.*s", 1, NewsHREF);
    +	    }
    +	    CTRACE((tfp, "News: doing HTDoConnect on '%s'\n", url));
    +
    +	    _HTProgress(gettext("Connecting to NewsHost ..."));
    +
    +#ifdef USE_SSL
    +	    if (!using_proxy &&
    +		(!StrNCmp(arg, STR_SNEWS_URL, 6) ||
    +		 !StrNCmp(arg, "snewspost:", 10) ||
    +		 !StrNCmp(arg, "snewsreply:", 11)))
    +		status = HTDoConnect(url, "NNTPS", SNEWS_PORT, &s);
    +	    else
    +		status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
    +#else
    +	    status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
    +#endif /* USE_SSL */
    +
    +	    if (status == HT_INTERRUPTED) {
    +		/*
    +		 * Interrupt cleanly.
    +		 */
    +		CTRACE((tfp,
    +			"HTNews: Interrupted on connect; recovering cleanly.\n"));
    +		_HTProgress(CONNECTION_INTERRUPTED);
    +		if (!(post_wanted || reply_wanted ||
    +		      spost_wanted || sreply_wanted)) {
    +		    ABORT_TARGET;
    +		}
    +		FREE(NewsHost);
    +		FREE(NewsHREF);
    +		FREE(ProxyHost);
    +		FREE(ProxyHREF);
    +		FREE(ListArg);
    +#ifdef USE_SSL
    +		if (Handle) {
    +		    SSL_free(Handle);
    +		    Handle = NULL;
    +		}
    +#endif /* USE_SSL */
    +		if (postfile) {
    +		    HTSYS_remove(postfile);
    +		    FREE(postfile);
    +		}
    +		return HT_NOT_LOADED;
    +	    }
    +	    if (status < 0) {
    +		NEWS_NETCLOSE(s);
    +		s = -1;
    +		CTRACE((tfp, "HTNews: Unable to connect to news host.\n"));
    +		if (retries < 1)
    +		    continue;
    +		if (!(post_wanted || reply_wanted ||
    +		      spost_wanted || sreply_wanted)) {
    +		    ABORT_TARGET;
    +		}
    +		HTSprintf0(&dbuf, gettext("Could not access %s."), NewsHost);
    +		FREE(NewsHost);
    +		FREE(NewsHREF);
    +		FREE(ProxyHost);
    +		FREE(ProxyHREF);
    +		FREE(ListArg);
    +		if (postfile) {
    +		    HTSYS_remove(postfile);
    +		    FREE(postfile);
    +		}
    +		return HTLoadError(stream, 500, dbuf);
    +	    } else {
    +		CTRACE((tfp, "HTNews: Connected to news host %s.\n",
    +			NewsHost));
    +#ifdef USE_SSL
    +		/*
    +		 * If this is an snews url, then do the SSL stuff here
    +		 */
    +		if (!using_proxy &&
    +		    (!StrNCmp(url, "snews", 5) ||
    +		     !StrNCmp(url, "snewspost:", 10) ||
    +		     !StrNCmp(url, "snewsreply:", 11))) {
    +		    Handle = HTGetSSLHandle();
    +		    SSL_set_fd(Handle, s);
    +		    HTSSLInitPRNG();
    +		    status = SSL_connect(Handle);
    +
    +		    if (status <= 0) {
    +			unsigned long SSLerror;
    +
    +			CTRACE((tfp,
    +				"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
    +				url, status));
    +			SSL_load_error_strings();
    +			while ((SSLerror = ERR_get_error()) != 0) {
    +			    CTRACE((tfp, "HTNews: SSL: %s\n",
    +				    ERR_error_string(SSLerror, NULL)));
    +			}
    +			HTAlert("Unable to make secure connection to remote host.");
    +			NEWS_NETCLOSE(s);
    +			s = -1;
    +			if (!(post_wanted || reply_wanted ||
    +			      spost_wanted || sreply_wanted))
    +			    (*targetClass._abort) (target, NULL);
    +			FREE(NewsHost);
    +			FREE(NewsHREF);
    +			FREE(ProxyHost);
    +			FREE(ProxyHREF);
    +			FREE(ListArg);
    +			if (postfile) {
    +#ifdef VMS
    +			    while (remove(postfile) == 0) ;	/* loop through all versions */
    +#else
    +			    remove(postfile);
    +#endif /* VMS */
    +			    FREE(postfile);
    +			}
    +			return HT_NOT_LOADED;
    +		    }
    +		    sprintf(SSLprogress,
    +			    "Secure %d-bit %s (%s) NNTP connection",
    +			    SSL_get_cipher_bits(Handle, NULL),
    +			    SSL_get_cipher_version(Handle),
    +			    SSL_get_cipher(Handle));
    +		    _HTProgress(SSLprogress);
    +		}
    +#endif /* USE_SSL */
    +		HTInitInput(s);	/* set up buffering */
    +		if (proxycmd[0]) {
    +		    status = (int) NEWS_NETWRITE(s, proxycmd, (int) strlen(proxycmd));
    +		    CTRACE((tfp,
    +			    "HTNews: Proxy command returned status '%d'.\n",
    +			    status));
    +		}
    +		if (((status = response(NULL)) / 100) != 2) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		    if (status == HT_INTERRUPTED) {
    +			_HTProgress(CONNECTION_INTERRUPTED);
    +			if (!(post_wanted || reply_wanted ||
    +			      spost_wanted || sreply_wanted)) {
    +			    ABORT_TARGET;
    +			}
    +			FREE(NewsHost);
    +			FREE(NewsHREF);
    +			FREE(ProxyHost);
    +			FREE(ProxyHREF);
    +			FREE(ListArg);
    +			if (postfile) {
    +			    HTSYS_remove(postfile);
    +			    FREE(postfile);
    +			}
    +			return (HT_NOT_LOADED);
    +		    }
    +		    if (retries < 1)
    +			continue;
    +		    FREE(ProxyHost);
    +		    FREE(ProxyHREF);
    +		    FREE(ListArg);
    +		    FREE(postfile);
    +		    if (!(post_wanted || reply_wanted ||
    +			  spost_wanted || sreply_wanted)) {
    +			ABORT_TARGET;
    +		    }
    +		    if (response_text[0]) {
    +			HTSprintf0(&dbuf,
    +				   gettext("Can't read news info.  News host %.20s responded: %.200s"),
    +				   NewsHost, response_text);
    +		    } else {
    +			HTSprintf0(&dbuf,
    +				   gettext("Can't read news info, empty response from host %s"),
    +				   NewsHost);
    +		    }
    +		    return HTLoadError(stream, 500, dbuf);
    +		}
    +		if (status == 200) {
    +		    HTCanPost = TRUE;
    +		} else {
    +		    HTCanPost = FALSE;
    +		    if (post_wanted || reply_wanted ||
    +			spost_wanted || sreply_wanted) {
    +			HTAlert(CANNOT_POST);
    +			FREE(NewsHREF);
    +			if (ProxyHREF) {
    +			    StrAllocCopy(NewsHost, ProxyHost);
    +			    FREE(ProxyHost);
    +			    FREE(ProxyHREF);
    +			}
    +			FREE(ListArg);
    +			if (postfile) {
    +			    HTSYS_remove(postfile);
    +			    FREE(postfile);
    +			}
    +			return (HT_NOT_LOADED);
    +		    }
    +		}
    +	    }
    +	}
    +	/* If needed opening */
    +	if (post_wanted || reply_wanted ||
    +	    spost_wanted || sreply_wanted) {
    +	    if (!HTCanPost) {
    +		HTAlert(CANNOT_POST);
    +		FREE(NewsHREF);
    +		if (ProxyHREF) {
    +		    StrAllocCopy(NewsHost, ProxyHost);
    +		    FREE(ProxyHost);
    +		    FREE(ProxyHREF);
    +		}
    +		FREE(ListArg);
    +		if (postfile) {
    +		    HTSYS_remove(postfile);
    +		    FREE(postfile);
    +		}
    +		return (HT_NOT_LOADED);
    +	    }
    +	    if (postfile == NULL) {
    +		postfile = LYNewsPost(ListArg,
    +				      (reply_wanted || sreply_wanted));
    +	    }
    +	    if (postfile == NULL) {
    +		HTProgress(CANCELLED);
    +		FREE(NewsHREF);
    +		if (ProxyHREF) {
    +		    StrAllocCopy(NewsHost, ProxyHost);
    +		    FREE(ProxyHost);
    +		    FREE(ProxyHREF);
    +		}
    +		FREE(ListArg);
    +		return (HT_NOT_LOADED);
    +	    }
    +	} else {
    +	    /*
    +	     * Ensure reader mode, but don't bother checking the status for
    +	     * anything but HT_INTERRUPTED or a 480 Authorization request,
    +	     * because if the reader mode command is not needed, the server
    +	     * probably returned a 500, which is irrelevant at this point.  -
    +	     * FM
    +	     */
    +	    char buffer[20];
    +
    +	    sprintf(buffer, "mode reader%c%c", CR, LF);
    +	    if ((status = response(buffer)) == HT_INTERRUPTED) {
    +		_HTProgress(CONNECTION_INTERRUPTED);
    +		break;
    +	    }
    +	    if (status == 480) {
    +		NNTPAuthResult auth_result = HTHandleAuthInfo(NewsHost);
    +
    +		if (auth_result == NNTPAUTH_CLOSE) {
    +		    if (s != -1 && !(ProxyHost || ProxyHREF)) {
    +			NEWS_NETCLOSE(s);
    +			s = -1;
    +		    }
    +		}
    +		if (auth_result != NNTPAUTH_OK) {
    +		    break;
    +		}
    +		if (response(buffer) == HT_INTERRUPTED) {
    +		    _HTProgress(CONNECTION_INTERRUPTED);
    +		    break;
    +		}
    +	    }
    +	}
    +
    +      Send_NNTP_command:
    +#ifdef NEWS_DEB
    +	if (postfile)
    +	    printf("postfile = %s, command = %s", postfile, command);
    +	else
    +	    printf("command = %s", command);
    +#endif
    +	if ((status = response(command)) == HT_INTERRUPTED) {
    +	    _HTProgress(CONNECTION_INTERRUPTED);
    +	    break;
    +	}
    +	if (status < 0) {
    +	    if (retries < 1) {
    +		continue;
    +	    } else {
    +		break;
    +	    }
    +	}
    +	/*
    +	 * For some well known error responses which are expected to occur in
    +	 * normal use, break from the loop without retrying and without closing
    +	 * the connection.  It is unlikely that these are leftovers from a
    +	 * timed-out connection (but we do some checks to see whether the
    +	 * response corresponds to the last command), or that they will give
    +	 * anything else when automatically retried.  - kw
    +	 */
    +	if (status == 411 && group_wanted &&
    +	    !StrNCmp(command, "GROUP ", 6) &&
    +	    !strncasecomp(response_text + 3, " No such group ", 15) &&
    +	    !strcmp(response_text + 18, groupName)) {
    +
    +	    HTAlert(response_text);
    +	    break;
    +	} else if (status == 430 && !group_wanted && !list_wanted &&
    +		   !StrNCmp(command, "ARTICLE <", 9) &&
    +		   !strcasecomp(response_text + 3, " No such article")) {
    +
    +	    HTAlert(response_text);
    +	    break;
    +	}
    +	if ((status / 100) != 2 &&
    +	    status != 340 &&
    +	    status != 480) {
    +	    if (retries) {
    +		if (list_wanted && !StrNCmp(command, "XGTITLE", 7)) {
    +		    sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
    +		    goto Send_NNTP_command;
    +		}
    +		HTAlert(response_text);
    +	    } else {
    +		_HTProgress(response_text);
    +	    }
    +	    NEWS_NETCLOSE(s);
    +	    s = -1;
    +	    /*
    +	     * Message might be a leftover "Timeout-disconnected", so try again
    +	     * if the retries maximum has not been reached.
    +	     */
    +	    continue;
    +	}
    +
    +	/*
    +	 * Post or load a group, article, etc
    +	 */
    +	if (status == 480) {
    +	    NNTPAuthResult auth_result;
    +
    +	    /*
    +	     * Some servers return 480 for a failed XGTITLE.  - FM
    +	     */
    +	    if (list_wanted && !StrNCmp(command, "XGTITLE", 7) &&
    +		strstr(response_text, "uthenticat") == NULL &&
    +		strstr(response_text, "uthor") == NULL) {
    +		sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
    +		goto Send_NNTP_command;
    +	    }
    +	    /*
    +	     * Handle Authorization.  - FM
    +	     */
    +	    if ((auth_result = HTHandleAuthInfo(NewsHost)) == NNTPAUTH_OK) {
    +		goto Send_NNTP_command;
    +	    } else if (auth_result == NNTPAUTH_CLOSE) {
    +		if (s != -1 && !(ProxyHost || ProxyHREF)) {
    +		    NEWS_NETCLOSE(s);
    +		    s = -1;
    +		}
    +		if (retries < 1)
    +		    continue;
    +	    }
    +	    status = HT_NOT_LOADED;
    +	} else if (post_wanted || reply_wanted ||
    +		   spost_wanted || sreply_wanted) {
    +	    /*
    +	     * Handle posting of an article.  - FM
    +	     */
    +	    if (status != 340) {
    +		HTAlert(CANNOT_POST);
    +		if (postfile) {
    +		    HTSYS_remove(postfile);
    +		}
    +	    } else {
    +		post_article(postfile);
    +	    }
    +	    FREE(postfile);
    +	    status = HT_NOT_LOADED;
    +	} else if (list_wanted) {
    +	    /*
    +	     * List available newsgroups.  - FM
    +	     */
    +	    _HTProgress(gettext("Reading list of available newsgroups."));
    +	    status = read_list(ListArg);
    +	} else if (group_wanted) {
    +	    /*
    +	     * List articles in a news group.  - FM
    +	     */
    +	    if (last < 0) {
    +		/*
    +		 * We got one article number rather than a range following the
    +		 * slash which followed the group name, or the range was zero,
    +		 * so now that we have selected that group, load ARTICLE and
    +		 * the the number (first) as the command and go back to send it
    +		 * and check the response.  - FM
    +		 */
    +		sprintf(command, "%s %d%c%c",
    +			head_wanted ? "HEAD" : "ARTICLE",
    +			first, CR, LF);
    +		group_wanted = FALSE;
    +		retries = 2;
    +		goto Send_NNTP_command;
    +	    }
    +	    _HTProgress(gettext("Reading list of articles in newsgroup."));
    +	    status = read_group(groupName, first, last);
    +	} else {
    +	    /*
    +	     * Get an article from a news group.  - FM
    +	     */
    +	    _HTProgress(gettext("Reading news article."));
    +	    status = read_article(anAnchor);
    +	}
    +	if (status == HT_INTERRUPTED) {
    +	    _HTProgress(CONNECTION_INTERRUPTED);
    +	    status = HT_LOADED;
    +	}
    +	if (!(post_wanted || reply_wanted ||
    +	      spost_wanted || sreply_wanted)) {
    +	    if (status == HT_NOT_LOADED) {
    +		ABORT_TARGET;
    +	    } else {
    +		FREE_TARGET;
    +	    }
    +	}
    +	FREE(NewsHREF);
    +	if (ProxyHREF) {
    +	    StrAllocCopy(NewsHost, ProxyHost);
    +	    FREE(ProxyHost);
    +	    FREE(ProxyHREF);
    +	}
    +	FREE(ListArg);
    +	if (postfile) {
    +	    HTSYS_remove(postfile);
    +	    FREE(postfile);
    +	}
    +	return status;
    +    }				/* Retry loop */
    +
    +#if 0
    +    HTAlert(gettext("Sorry, could not load requested news."));
    +    NXRunAlertPanel(NULL, "Sorry, could not load `%s'.", NULL, NULL, NULL, arg);
    +    /* No -- message earlier will have covered it */
    +#endif
    +
    +    if (!(post_wanted || reply_wanted ||
    +	  spost_wanted || sreply_wanted)) {
    +	ABORT_TARGET;
    +    }
    +    FREE(NewsHREF);
    +    if (ProxyHREF) {
    +	StrAllocCopy(NewsHost, ProxyHost);
    +	FREE(ProxyHost);
    +	FREE(ProxyHREF);
    +    }
    +    FREE(ListArg);
    +    if (postfile) {
    +	HTSYS_remove(postfile);
    +	FREE(postfile);
    +    }
    +    return HT_NOT_LOADED;
    +}
    +
    +/*
    + *  This function clears all authorization information by
    + *  invoking the free_NNTP_AuthInfo() function, which normally
    + *  is invoked at exit.  It allows a browser command to do
    + *  this at any time, for example, if the user is leaving
    + *  the terminal for a period of time, but does not want
    + *  to end the current session.  - FM
    + */
    +void HTClearNNTPAuthInfo(void)
    +{
    +    /*
    +     * Need code to check cached documents and do something to ensure that any
    +     * protected documents no longer can be accessed without a new retrieval. 
    +     * - FM
    +     */
    +
    +    /*
    +     * Now free all of the authorization info.  - FM
    +     */
    +    free_NNTP_AuthInfo();
    +}
    +
    +#ifdef USE_SSL
    +static int HTNewsGetCharacter(void)
    +{
    +    if (!Handle)
    +	return HTGetCharacter();
    +    else
    +	return HTGetSSLCharacter((void *) Handle);
    +}
    +
    +int HTNewsProxyConnect(int sock,
    +		       const char *url,
    +		       HTParentAnchor *anAnchor,
    +		       HTFormat format_out,
    +		       HTStream *sink)
    +{
    +    int status;
    +    const char *arg = url;
    +    char SSLprogress[256];
    +
    +    s = channel_s = sock;
    +    Handle = HTGetSSLHandle();
    +    SSL_set_fd(Handle, s);
    +    HTSSLInitPRNG();
    +    status = SSL_connect(Handle);
    +
    +    if (status <= 0) {
    +	unsigned long SSLerror;
    +
    +	channel_s = -1;
    +	CTRACE((tfp,
    +		"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
    +		url, status));
    +	SSL_load_error_strings();
    +	while ((SSLerror = ERR_get_error()) != 0) {
    +	    CTRACE((tfp, "HTNews: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
    +	}
    +	HTAlert("Unable to make secure connection to remote host.");
    +	NEWS_NETCLOSE(s);
    +	s = -1;
    +	return HT_NOT_LOADED;
    +    }
    +    sprintf(SSLprogress, "Secure %d-bit %s (%s) NNTP connection",
    +	    SSL_get_cipher_bits(Handle, NULL),
    +	    SSL_get_cipher_version(Handle),
    +	    SSL_get_cipher(Handle));
    +    _HTProgress(SSLprogress);
    +    status = HTLoadNews(arg, anAnchor, format_out, sink);
    +    channel_s = -1;
    +    return status;
    +}
    +#endif /* USE_SSL */
    +
    +#ifdef GLOBALDEF_IS_MACRO
    +#define _HTNEWS_C_1_INIT { "news", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTNews, _HTNEWS_C_1_INIT);
    +#define _HTNEWS_C_2_INIT { "nntp", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTNNTP, _HTNEWS_C_2_INIT);
    +#define _HTNEWS_C_3_INIT { "newspost", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTNewsPost, _HTNEWS_C_3_INIT);
    +#define _HTNEWS_C_4_INIT { "newsreply", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTNewsReply, _HTNEWS_C_4_INIT);
    +#define _HTNEWS_C_5_INIT { "snews", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTSNews, _HTNEWS_C_5_INIT);
    +#define _HTNEWS_C_6_INIT { "snewspost", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTSNewsPost, _HTNEWS_C_6_INIT);
    +#define _HTNEWS_C_7_INIT { "snewsreply", HTLoadNews, NULL }
    +GLOBALDEF(HTProtocol, HTSNewsReply, _HTNEWS_C_7_INIT);
    +#else
    +GLOBALDEF HTProtocol HTNews =
    +{"news", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTNNTP =
    +{"nntp", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTNewsPost =
    +{"newspost", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTNewsReply =
    +{"newsreply", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTSNews =
    +{"snews", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTSNewsPost =
    +{"snewspost", HTLoadNews, NULL};
    +GLOBALDEF HTProtocol HTSNewsReply =
    +{"snewsreply", HTLoadNews, NULL};
    +#endif /* GLOBALDEF_IS_MACRO */
    +
    +#endif /* not DISABLE_NEWS */
    diff --git a/WWW/Library/Implementation/HTNews.h b/WWW/Library/Implementation/HTNews.h
    new file mode 100644
    index 0000000..ef9a6e4
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTNews.h
    @@ -0,0 +1,60 @@
    +/*                                  Network News Transfer protocol module for the WWW library
    +                                          HTNEWS
    +                                             
    + */
    +/* History:
    + *      26 Sep 90       Written TBL in Objective-C
    + *      29 Nov 91       Downgraded to C, for portable implementation.
    + */
    +
    +#ifndef HTNEWS_H
    +#define HTNEWS_H
    +
    +#include 
    +#include 
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern int HTNewsChunkSize;
    +    extern int HTNewsMaxChunk;
    +
    +#ifdef GLOBALREF_IS_MACRO
    +    extern GLOBALREF (HTProtocol, HTNews);
    +    extern GLOBALREF (HTProtocol, HTNNTP);
    +    extern GLOBALREF (HTProtocol, HTNewsPost);
    +    extern GLOBALREF (HTProtocol, HTNewsReply);
    +    extern GLOBALREF (HTProtocol, HTSNews);
    +    extern GLOBALREF (HTProtocol, HTSNewsPost);
    +    extern GLOBALREF (HTProtocol, HTSNewsReply);
    +
    +#else
    +    GLOBALREF HTProtocol HTNews;
    +    GLOBALREF HTProtocol HTNNTP;
    +    GLOBALREF HTProtocol HTNewsPost;
    +    GLOBALREF HTProtocol HTNewsReply;
    +    GLOBALREF HTProtocol HTSNews;
    +    GLOBALREF HTProtocol HTSNewsPost;
    +    GLOBALREF HTProtocol HTSNewsReply;
    +#endif				/* GLOBALREF_IS_MACRO */
    +
    +    extern void HTSetNewsHost(const char *value);
    +    extern const char *HTGetNewsHost(void);
    +    extern char *HTNewsHost;
    +
    +    extern void HTClearNNTPAuthInfo(void);
    +
    +#ifdef USE_SSL
    +    extern SSL_CTX *ssl_ctx;
    +
    +    extern int HTNewsProxyConnect(int sock,
    +				  const char *url,
    +				  HTParentAnchor *anAnchor,
    +				  HTFormat format_out,
    +				  HTStream *sink);
    +#endif
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTNEWS_H */
    diff --git a/WWW/Library/Implementation/HTParse.c b/WWW/Library/Implementation/HTParse.c
    new file mode 100644
    index 0000000..2e45441
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTParse.c
    @@ -0,0 +1,1383 @@
    +/*
    + * $LynxId: HTParse.c,v 1.98 2021/07/27 21:29:49 tom Exp $
    + *
    + *		Parse HyperText Document Address		HTParse.c
    + *		================================
    + */
    +
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef HAVE_ALLOCA_H
    +#include 
    +#else
    +#ifdef __MINGW32__
    +#include 
    +#endif /* __MINGW32__ */
    +#endif
    +
    +#ifdef USE_IDN2
    +#include 
    +#define FreeIdna(out)             idn2_free(out)
    +#elif defined(USE_IDNA)
    +#include 
    +#include 
    +#define FreeIdna(out)             idn_free(out)
    +#define IDN2_OK                   IDNA_SUCCESS
    +#endif
    +
    +#define HEX_ESCAPE '%'
    +
    +struct struct_parts {
    +    char *access;
    +    char *host;
    +    char *absolute;
    +    char *relative;
    +    char *search;		/* treated normally as part of path */
    +    char *anchor;
    +};
    +
    +#if 0				/* for debugging */
    +static void show_parts(const char *name, struct struct_parts *parts, int line)
    +{
    +    if (TRACE) {
    +	CTRACE((tfp, "struct_parts(%s) %s@%d\n", name, __FILE__, line));
    +	CTRACE((tfp, "   access   '%s'\n", NONNULL(parts->access)));
    +	CTRACE((tfp, "   host     '%s'\n", NONNULL(parts->host)));
    +	CTRACE((tfp, "   absolute '%s'\n", NONNULL(parts->absolute)));
    +	CTRACE((tfp, "   relative '%s'\n", NONNULL(parts->relative)));
    +	CTRACE((tfp, "   search   '%s'\n", NONNULL(parts->search)));
    +	CTRACE((tfp, "   anchor   '%s'\n", NONNULL(parts->anchor)));
    +    }
    +}
    +#define SHOW_PARTS(name) show_parts(#name, &name, __LINE__)
    +#else
    +#define SHOW_PARTS(name)	/* nothing */
    +#endif
    +
    +/*	Strip white space off a string.				HTStrip()
    + *	-------------------------------
    + *
    + * On exit,
    + *	Return value points to first non-white character, or to 0 if none.
    + *	All trailing white space is OVERWRITTEN with zero.
    + */
    +char *HTStrip(char *s)
    +{
    +#define SPACE(c) ((c == ' ') || (c == '\t') || (c == '\n'))
    +    char *p;
    +
    +    for (p = s; *p; p++) {	/* Find end of string */
    +	;
    +    }
    +    for (p--; p >= s; p--) {
    +	if (SPACE(*p))
    +	    *p = '\0';		/* Zap trailing blanks */
    +	else
    +	    break;
    +    }
    +    while (SPACE(*s))
    +	s++;			/* Strip leading blanks */
    +    return s;
    +}
    +
    +/*	Scan a filename for its constituents.			scan()
    + *	-------------------------------------
    + *
    + * On entry,
    + *	name	points to a document name which may be incomplete.
    + * On exit,
    + *	absolute or relative may be nonzero (but not both).
    + *	host, anchor and access may be nonzero if they were specified.
    + *	Any which are nonzero point to zero terminated strings.
    + */
    +static void scan(char *name,
    +		 struct struct_parts *parts)
    +{
    +    char *after_access;
    +    char *p;
    +
    +    parts->access = NULL;
    +    parts->host = NULL;
    +    parts->absolute = NULL;
    +    parts->relative = NULL;
    +    parts->search = NULL;	/* normally not used - kw */
    +    parts->anchor = NULL;
    +
    +    /*
    +     * Scan left-to-right for a scheme (access).
    +     */
    +    after_access = name;
    +    for (p = name; *p; p++) {
    +	if (*p == ':') {
    +	    *p = '\0';
    +	    parts->access = name;	/* Access name has been specified */
    +	    after_access = (p + 1);
    +	    break;
    +	}
    +	if (*p == '/' || *p == '#' || *p == ';' || *p == '?')
    +	    break;
    +    }
    +
    +    /*
    +     * Scan left-to-right for a fragment (anchor).
    +     */
    +    for (p = after_access; *p; p++) {
    +	if (*p == '#') {
    +	    parts->anchor = (p + 1);
    +	    *p = '\0';		/* terminate the rest */
    +	    break;		/* leave things after first # alone - kw */
    +	}
    +    }
    +
    +    /*
    +     * Scan left-to-right for a host or absolute path.
    +     */
    +    p = after_access;
    +    if (*p == '/') {
    +	if (p[1] == '/') {
    +	    parts->host = (p + 2);	/* host has been specified    */
    +	    *p = '\0';		/* Terminate access           */
    +	    p = StrChr(parts->host, '/');	/* look for end of host name if any */
    +	    if (p != NULL) {
    +		*p = '\0';	/* Terminate host */
    +		parts->absolute = (p + 1);	/* Root has been found */
    +	    } else {
    +		p = StrChr(parts->host, '?');
    +		if (p != NULL) {
    +		    *p = '\0';	/* Terminate host */
    +		    parts->search = (p + 1);
    +		}
    +	    }
    +	} else {
    +	    parts->absolute = (p + 1);	/* Root found but no host */
    +	}
    +    } else {
    +	parts->relative = (*after_access) ?
    +	    after_access : NULL;	/* NULL for "" */
    +    }
    +
    +    /*
    +     * Check schemes that commonly have unescaped hashes.
    +     */
    +    if (parts->access && parts->anchor &&
    +    /* optimize */ StrChr("lnsdLNSD", *parts->access) != NULL) {
    +	if ((!parts->host && strcasecomp(parts->access, "lynxcgi")) ||
    +	    !strcasecomp(parts->access, "nntp") ||
    +	    !strcasecomp(parts->access, "snews") ||
    +	    !strcasecomp(parts->access, "news") ||
    +	    !strcasecomp(parts->access, "data")) {
    +	    /*
    +	     * Access specified but no host and not a lynxcgi URL, so the
    +	     * anchor may not really be one, e.g., news:j462#36487@foo.bar, or
    +	     * it's an nntp or snews URL, or news URL with a host.  Restore the
    +	     * '#' in the address.
    +	     */
    +	    /* but only if we have found a path component of which this will
    +	     * become part. - kw  */
    +	    if (parts->relative || parts->absolute) {
    +		*(parts->anchor - 1) = '#';
    +		parts->anchor = NULL;
    +	    }
    +	}
    +    }
    +}				/*scan */
    +
    +#if defined(HAVE_ALLOCA) && !defined(LY_FIND_LEAKS)
    +#define LYalloca(x)        alloca((size_t)(x))
    +#define LYalloca_free(x)   {}
    +#else
    +#define LYalloca(x)        malloc((size_t)(x))
    +#define LYalloca_free(x)   free((void *)(x))
    +#endif
    +
    +static char *strchr_or_end(char *string, int ch)
    +{
    +    char *result = StrChr(string, ch);
    +
    +    if (result == 0) {
    +	result = string + strlen(string);
    +    }
    +    return result;
    +}
    +
    +/*
    + * Given a host specification that may end with a port number, e.g.,
    + *	foobar:123
    + * point to the ':' which begins the ":port" to make it simple to handle the
    + * substring.
    + *
    + * If no port is found (or a syntax error), return null.
    + */
    +char *HTParsePort(char *host, int *portp)
    +{
    +    int brackets = 0;
    +    char *result = NULL;
    +
    +    *portp = 0;
    +    if (host != NULL) {
    +	while (*host != '\0' && result == 0) {
    +	    switch (*host++) {
    +	    case ':':
    +		if (brackets == 0 && isdigit(UCH(*host))) {
    +		    char *next = NULL;
    +
    +		    *portp = (int) strtol(host, &next, 10);
    +		    if (next != 0 && next != host && *next == '\0') {
    +			result = (host - 1);
    +			CTRACE((tfp, "HTParsePort %d\n", *portp));
    +		    }
    +		}
    +		break;
    +	    case '[':		/* for ipv6 */
    +		++brackets;
    +		break;
    +	    case ']':		/* for ipv6 */
    +		--brackets;
    +		break;
    +	    }
    +	}
    +    }
    +    return result;
    +}
    +
    +#if defined(USE_IDNA) || defined(USE_IDN2)
    +static int hex_decode(int ch)
    +{
    +    int result = -1;
    +
    +    if (ch >= '0' && ch <= '9')
    +	result = (ch - '0');
    +    else if (ch >= 'a' && ch <= 'f')
    +	result = (ch - 'a') + 10;
    +    else if (ch >= 'A' && ch <= 'F')
    +	result = (ch - 'A') + 10;
    +    return result;
    +}
    +
    +/*
    + * Convert in-place the given hostname to IDNA form.  That requires up to 64
    + * characters, and we've allowed for that, with MIN_PARSE.
    + */
    +static void convert_to_idna(char *host)
    +{
    +    size_t length = strlen(host);
    +    char *endhost = host + length;
    +    char *buffer = malloc(length + 1);
    +    char *params = malloc(length + 1);
    +    char *output = NULL;
    +    char *src, *dst;
    +    int code;
    +    int hi, lo;
    +
    +    if (buffer != NULL && params != NULL) {
    +	code = TRUE;
    +	*params = '\0';
    +	for (dst = buffer, src = host; src < endhost; ++dst) {
    +	    int ch = *src++;
    +
    +	    if (RFC_3986_GEN_DELIMS(ch)) {
    +		strcpy(params, src - 1);
    +		*dst = '\0';
    +		break;
    +	    } else if (ch == HEX_ESCAPE) {
    +		if ((src + 1) < endhost
    +		    && (hi = hex_decode(src[0])) >= 0
    +		    && (lo = hex_decode(src[1])) >= 0) {
    +
    +		    *dst = (char) ((hi << 4) | lo);
    +		    src += 2;
    +		} else {
    +		    CTRACE((tfp, "convert_to_idna: `%s' is malformed\n", host));
    +		    code = FALSE;
    +		    break;
    +		}
    +	    } else {
    +		*dst = (char) ch;
    +	    }
    +	}
    +	if (code) {
    +	    *dst = '\0';
    +#ifdef USE_IDN2
    +#if (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003)
    +	    /*
    +	     * Older libidn2 mishandles STD3, stripping underscores.
    +	     */
    +	    if (strchr(buffer, '_') != NULL) {
    +		code = -1;
    +	    } else
    +#endif
    +		switch (LYidnaMode) {
    +		case LYidna2003:
    +		    code = idn2_to_ascii_8z(buffer, &output, IDN2_TRANSITIONAL);
    +		    break;
    +		case LYidna2008:
    +		    /* IDNA2008 rules without the TR46 amendments */
    +		    code = idn2_to_ascii_8z(buffer, &output, 0);
    +		    break;
    +		case LYidnaTR46:
    +		    code = idn2_to_ascii_8z(buffer, &output, IDN2_NONTRANSITIONAL
    +					    | IDN2_NFC_INPUT);
    +		    break;
    +		case LYidnaCompat:
    +		    /* IDNA2008 */
    +		    code = idn2_to_ascii_8z(buffer, &output, IDN2_NONTRANSITIONAL
    +					    | IDN2_NFC_INPUT);
    +		    if (code == IDN2_DISALLOWED) {
    +			/* IDNA2003 - compatible */
    +			code = idn2_to_ascii_8z(buffer, &output, IDN2_TRANSITIONAL);
    +		    }
    +		    break;
    +		}
    +#else
    +	    code = idna_to_ascii_8z(buffer, &output, IDNA_USE_STD3_ASCII_RULES);
    +#endif
    +	    if (code == IDN2_OK) {
    +		CTRACE((tfp, "convert_to_idna: `%s' -> `%s': OK\n", buffer, output));
    +		strcpy(host, output);
    +		strcat(host, params);
    +	    } else {
    +		CTRACE((tfp, "convert_to_idna: `%s': %s\n",
    +			buffer,
    +			idna_strerror((Idna_rc) code)));
    +	    }
    +	    if (output)
    +		FreeIdna(output);
    +	}
    +    }
    +    free(buffer);
    +    free(params);
    +}
    +#define MIN_PARSE 80
    +#else
    +#define MIN_PARSE 8
    +#endif
    +
    +/*	Parse a Name relative to another name.			HTParse()
    + *	--------------------------------------
    + *
    + *	This returns those parts of a name which are given (and requested)
    + *	substituting bits from the related name where necessary.
    + *
    + *	Originally based on RFC 1808, some details in RFC 3986 are used.
    + *
    + * On entry,
    + *	aName		A filename given
    + *	relatedName	A name relative to which aName is to be parsed
    + *	wanted		A mask for the bits which are wanted.
    + *
    + * On exit,
    + *     returns         A pointer to a malloc'd string which MUST BE FREED
    + */
    +char *HTParse(const char *aName,
    +	      const char *relatedName,
    +	      int wanted)
    +{
    +    char *result = NULL;
    +    char *tail = NULL;		/* a pointer to the end of the 'result' string */
    +    char *return_value = NULL;
    +    size_t len, len1, len2;
    +    size_t need;
    +    char *name = NULL;
    +    char *rel = NULL;
    +    char *p, *q;
    +    char *acc_method;
    +    struct struct_parts given, related;
    +
    +    CTRACE((tfp, "HTParse: aName:`%s'\n", aName));
    +    CTRACE((tfp, "   relatedName:`%s'\n", relatedName));
    +
    +    if (wanted & (PARSE_STRICTPATH | PARSE_QUERY)) {	/* if detail wanted... */
    +	if ((wanted & (PARSE_STRICTPATH | PARSE_QUERY))
    +	    == (PARSE_STRICTPATH | PARSE_QUERY))	/* if strictpath AND query */
    +	    wanted |= PARSE_PATH;	/* then treat as if PARSE_PATH wanted */
    +	if (wanted & PARSE_PATH)	/* if PARSE_PATH wanted */
    +	    wanted &= ~(PARSE_STRICTPATH | PARSE_QUERY);	/* ignore details */
    +    }
    +/* *INDENT-OFF* */
    +    CTRACE((tfp, "   want:%s%s%s%s%s%s%s\n",
    +	    wanted & PARSE_PUNCTUATION ? " punc"   : "",
    +	    wanted & PARSE_ANCHOR      ? " anchor" : "",
    +	    wanted & PARSE_PATH        ? " path"   : "",
    +	    wanted & PARSE_HOST        ? " host"   : "",
    +	    wanted & PARSE_ACCESS      ? " access" : "",
    +	    wanted & PARSE_STRICTPATH  ? " PATH"   : "",
    +	    wanted & PARSE_QUERY       ? " QUERY"  : ""));
    +/* *INDENT-ON* */
    +
    +    /*
    +     * Allocate the temporary string. Optimized.
    +     */
    +    len1 = strlen(aName) + 1;
    +    len2 = strlen(relatedName) + 1;
    +    len = len1 + len2 + MIN_PARSE;	/* Lots of space: more than enough */
    +
    +    need = (len * 2 + len1 + len2);
    +    if (need > (size_t) max_uri_size ||
    +	(int) need < (int) len1 ||
    +	(int) need < (int) len2)
    +	return StrAllocCopy(return_value, "");
    +
    +    result = tail = (char *) LYalloca(need);
    +    if (result == NULL) {
    +	outofmem(__FILE__, "HTParse");
    +    }
    +    *result = '\0';
    +    name = result + len;
    +    rel = name + len1;
    +
    +    /*
    +     * Make working copy of the input string to cut up.
    +     */
    +    MemCpy(name, aName, len1);
    +
    +    /*
    +     * Cut up the string into URL fields.
    +     */
    +    scan(name, &given);
    +    SHOW_PARTS(given);
    +
    +    /*
    +     * Now related string.
    +     */
    +    if ((given.access && given.host && given.absolute) || !*relatedName) {
    +	/*
    +	 * Inherit nothing!
    +	 */
    +	related.access = NULL;
    +	related.host = NULL;
    +	related.absolute = NULL;
    +	related.relative = NULL;
    +	related.search = NULL;
    +	related.anchor = NULL;
    +    } else {
    +	MemCpy(rel, relatedName, len2);
    +	scan(rel, &related);
    +    }
    +    SHOW_PARTS(related);
    +
    +    /*
    +     * Handle the scheme (access) field.
    +     */
    +    if (given.access && given.host && !given.relative && !given.absolute) {
    +	if (!strcmp(given.access, "http") ||
    +	    !strcmp(given.access, "https") ||
    +	    !strcmp(given.access, "ftp")) {
    +
    +	    /*
    +	     * Assume root.
    +	     */
    +	    given.absolute = empty_string;
    +	}
    +    }
    +    acc_method = given.access ? given.access : related.access;
    +    if (wanted & PARSE_ACCESS) {
    +	if (acc_method) {
    +	    strcpy(tail, acc_method);
    +	    tail += strlen(tail);
    +	    if (wanted & PARSE_PUNCTUATION) {
    +		*tail++ = ':';
    +		*tail = '\0';
    +	    }
    +	}
    +    }
    +
    +    /*
    +     * If different schemes, inherit nothing.
    +     *
    +     * We'll try complying with RFC 1808 and the Fielding draft, and inherit
    +     * nothing if both schemes are given, rather than only when they differ,
    +     * except for file URLs - FM
    +     *
    +     * After trying it for a while, it's still premature, IHMO, to go along
    +     * with it, so this is back to inheriting for identical schemes whether or
    +     * not they are "file".  If you want to try it again yourself, uncomment
    +     * the strcasecomp() below.  - FM
    +     */
    +    if ((given.access && related.access) &&
    +	(			/* strcasecomp(given.access, "file") || */
    +	    strcmp(given.access, related.access))) {
    +	related.host = NULL;
    +	related.absolute = NULL;
    +	related.relative = NULL;
    +	related.search = NULL;
    +	related.anchor = NULL;
    +    }
    +
    +    /*
    +     * Handle the host field.
    +     */
    +    if (wanted & PARSE_HOST) {
    +	if (given.host || related.host) {
    +	    if (wanted & PARSE_PUNCTUATION) {
    +		*tail++ = '/';
    +		*tail++ = '/';
    +	    }
    +	    strcpy(tail, given.host ? given.host : related.host);
    +	    /*
    +	     * Ignore default port numbers, and trailing dots on FQDNs, which
    +	     * will only cause identical addresses to look different.  (related
    +	     * is already a clean url).
    +	     */
    +	    {
    +		char *p2, *h;
    +		int portnumber;
    +		int gen_delims = 0;
    +
    +		if ((p2 = HTSkipToAt(result, &gen_delims)) != NULL
    +		    && gen_delims == 0) {
    +		    tail = (p2 + 1);
    +		}
    +		p2 = HTParsePort(result, &portnumber);
    +		if (p2 != NULL && acc_method != NULL) {
    +		    /*
    +		     * Port specified.
    +		     */
    +#define ACC_METHOD(a,b) (!strcmp(acc_method, a) && (portnumber == b))
    +		    if (ACC_METHOD("http", 80) ||
    +			ACC_METHOD("https", 443) ||
    +			ACC_METHOD("gopher", 70) ||
    +			ACC_METHOD("ftp", 21) ||
    +			ACC_METHOD("wais", 210) ||
    +			ACC_METHOD("nntp", 119) ||
    +			ACC_METHOD("news", 119) ||
    +			ACC_METHOD("newspost", 119) ||
    +			ACC_METHOD("newsreply", 119) ||
    +			ACC_METHOD("snews", 563) ||
    +			ACC_METHOD("snewspost", 563) ||
    +			ACC_METHOD("snewsreply", 563) ||
    +			ACC_METHOD("finger", 79) ||
    +			ACC_METHOD("telnet", 23) ||
    +			ACC_METHOD("tn3270", 23) ||
    +			ACC_METHOD("rlogin", 513) ||
    +			ACC_METHOD("cso", 105))
    +			*p2 = '\0';	/* It is the default: ignore it */
    +		}
    +		if (p2 == NULL) {
    +		    int len3 = (int) strlen(tail);
    +
    +		    if (len3 > 0) {
    +			h = tail + len3 - 1;	/* last char of hostname */
    +			if (*h == '.')
    +			    *h = '\0';	/* chop final . */
    +		    }
    +		} else if (p2 != result) {
    +		    h = p2;
    +		    h--;	/* End of hostname */
    +		    if (*h == '.') {
    +			/*
    +			 * Slide p2 over h.
    +			 */
    +			while (*p2 != '\0')
    +			    *h++ = *p2++;
    +			*h = '\0';	/* terminate */
    +		    }
    +		}
    +	    }
    +#if defined(USE_IDNA) || defined(USE_IDN2)
    +	    /*
    +	     * Depending on locale-support, we could have a literal UTF-8
    +	     * string as a host name, or a URL-encoded form of that.
    +	     */
    +	    convert_to_idna(tail);
    +#endif
    +	}
    +    }
    +
    +    /*
    +     * Trim any blanks from the result so far - there's no excuse for blanks
    +     * in a hostname.  Also update the tail here.
    +     */
    +    tail = LYRemoveBlanks(result);
    +
    +    /*
    +     * If host in given or related was ended directly with a '?' (no slash),
    +     * fake the search part into absolute.  This is the only case search is
    +     * returned from scan.  A host must have been present.  this restores the
    +     * '?' at which the host part had been truncated in scan, we have to do
    +     * this after host part handling is done.  - kw
    +     */
    +    if (given.search && *(given.search - 1) == '\0') {
    +	given.absolute = given.search - 1;
    +	given.absolute[0] = '?';
    +    } else if (related.search && !related.absolute &&
    +	       *(related.search - 1) == '\0') {
    +	related.absolute = related.search - 1;
    +	related.absolute[0] = '?';
    +    }
    +
    +    /*
    +     * If different hosts, inherit no path.
    +     */
    +    if (given.host && related.host)
    +	if (strcmp(given.host, related.host) != 0) {
    +	    related.absolute = NULL;
    +	    related.relative = NULL;
    +	    related.anchor = NULL;
    +	}
    +
    +    /*
    +     * Handle the path.
    +     */
    +    if (wanted & (PARSE_PATH | PARSE_STRICTPATH | PARSE_QUERY)) {
    +	int want_detail = (wanted & (PARSE_STRICTPATH | PARSE_QUERY));
    +
    +	if (acc_method && !given.absolute && given.relative) {
    +	    /*
    +	     * Treat all given nntp or snews paths, or given paths for news
    +	     * URLs with a host, as absolute.
    +	     */
    +	    switch (*acc_method) {
    +	    case 'N':
    +	    case 'n':
    +		if (!strcasecomp(acc_method, "nntp") ||
    +		    (!strcasecomp(acc_method, "news") &&
    +		     !strncasecomp(result, "news://", 7))) {
    +		    given.absolute = given.relative;
    +		    given.relative = NULL;
    +		}
    +		break;
    +	    case 'S':
    +	    case 's':
    +		if (!strcasecomp(acc_method, "snews")) {
    +		    given.absolute = given.relative;
    +		    given.relative = NULL;
    +		}
    +		break;
    +	    }
    +	}
    +
    +	if (given.absolute) {	/* All is given */
    +	    char *base = tail;
    +
    +	    if (wanted & PARSE_PUNCTUATION)
    +		*tail++ = '/';
    +	    strcpy(tail, given.absolute);
    +	    HTSimplify(base, TRUE);
    +	    CTRACE((tfp, "HTParse: (ABS)\n"));
    +	} else if (related.absolute) {	/* Adopt path not name */
    +	    char *base = tail;
    +
    +	    *tail++ = '/';
    +	    strcpy(tail, related.absolute);
    +	    if (given.relative) {
    +		/* RFC 1808 part 4 step 5 (if URL path is empty) */
    +		/* a) if given has params, add/replace that */
    +		if (given.relative[0] == ';') {
    +		    strcpy(strchr_or_end(tail, ';'), given.relative);
    +		}
    +		/* b) if given has query, add/replace that */
    +		else if (given.relative[0] == '?') {
    +		    strcpy(strchr_or_end(tail, '?'), given.relative);
    +		}
    +		/* otherwise fall through to RFC 1808 part 4 step 6 */
    +		else {
    +		    p = StrChr(tail, '?');	/* Search part? */
    +		    if (p == NULL)
    +			p = (tail + strlen(tail) - 1);
    +		    for (; *p != '/'; p--) ;	/* last / */
    +		    p[1] = '\0';	/* Remove filename */
    +		    strcat(p, given.relative);	/* Add given one */
    +		}
    +		HTSimplify(base, FALSE);
    +		if (*base == '\0')
    +		    strcpy(base, "/");
    +	    } else {
    +		HTSimplify(base, TRUE);
    +	    }
    +	    if (base[0] == '/' && base[1] == '/') {
    +		char *pz;
    +
    +		for (pz = base; (pz[0] = pz[1]) != '\0'; ++pz) ;
    +	    }
    +	    CTRACE((tfp, "HTParse: (Related-ABS)\n"));
    +	} else if (given.relative) {
    +	    strcpy(tail, given.relative);	/* what we've got */
    +	    HTSimplify(tail, FALSE);
    +	    CTRACE((tfp, "HTParse: (REL)\n"));
    +	} else if (related.relative) {
    +	    strcpy(tail, related.relative);
    +	    HTSimplify(tail, FALSE);
    +	    CTRACE((tfp, "HTParse: (Related-REL)\n"));
    +	} else {		/* No inheritance */
    +	    if (!isLYNXCGI(aName) &&
    +		!isLYNXEXEC(aName) &&
    +		!isLYNXPROG(aName)) {
    +		*tail++ = '/';
    +		*tail = '\0';
    +	    } else {
    +		HTSimplify(tail, FALSE);
    +	    }
    +	    if (!strcmp(result, "news:/"))
    +		result[5] = '*';
    +	    CTRACE((tfp, "HTParse: (No inheritance)\n"));
    +	}
    +	if (want_detail) {
    +	    p = StrChr(tail, '?');	/* Search part? */
    +	    if (p) {
    +		if (PARSE_STRICTPATH) {
    +		    *p = '\0';
    +		} else {
    +		    if (!(wanted & PARSE_PUNCTUATION))
    +			p++;
    +		    do {
    +			*tail++ = *p;
    +		    } while (*p++);
    +		}
    +	    } else {
    +		if (wanted & PARSE_QUERY)
    +		    *tail = '\0';
    +	    }
    +	}
    +    }
    +
    +    /*
    +     * Handle the fragment (anchor).  Never inherit.
    +     */
    +    if (wanted & PARSE_ANCHOR) {
    +	if (given.anchor && *given.anchor) {
    +	    tail += strlen(tail);
    +	    if (wanted & PARSE_PUNCTUATION)
    +		*tail++ = '#';
    +	    strcpy(tail, given.anchor);
    +	}
    +    }
    +
    +    /*
    +     * If there are any blanks remaining in the string, escape them as needed.
    +     * See the discussion in LYLegitimizeHREF() for example.
    +     */
    +    if ((p = StrChr(result, ' ')) != 0) {
    +	switch (is_url(result)) {
    +	case UNKNOWN_URL_TYPE:
    +	    CTRACE((tfp, "HTParse:      ignore:`%s'\n", result));
    +	    break;
    +	case LYNXEXEC_URL_TYPE:
    +	case LYNXPROG_URL_TYPE:
    +	case LYNXCGI_URL_TYPE:
    +	case LYNXPRINT_URL_TYPE:
    +	case LYNXHIST_URL_TYPE:
    +	case LYNXDOWNLOAD_URL_TYPE:
    +	case LYNXKEYMAP_URL_TYPE:
    +	case LYNXIMGMAP_URL_TYPE:
    +	case LYNXCOOKIE_URL_TYPE:
    +	case LYNXCACHE_URL_TYPE:
    +	case LYNXDIRED_URL_TYPE:
    +	case LYNXOPTIONS_URL_TYPE:
    +	case LYNXCFG_URL_TYPE:
    +	case LYNXCOMPILE_OPTS_URL_TYPE:
    +	case LYNXMESSAGES_URL_TYPE:
    +	    CTRACE((tfp, "HTParse:      spaces:`%s'\n", result));
    +	    break;
    +	case NOT_A_URL_TYPE:
    +	default:
    +	    CTRACE((tfp, "HTParse:      encode:`%s'\n", result));
    +	    do {
    +		q = p + strlen(p) + 2;
    +
    +		while (q != p + 1) {
    +		    q[0] = q[-2];
    +		    --q;
    +		}
    +		p[0] = HEX_ESCAPE;
    +		p[1] = '2';
    +		p[2] = '0';
    +	    } while ((p = StrChr(result, ' ')) != 0);
    +	    break;
    +	}
    +    }
    +    CTRACE((tfp, "HTParse:      result:`%s'\n", result));
    +
    +    StrAllocCopy(return_value, result);
    +    LYalloca_free(result);
    +
    +    /* FIXME: could be optimized using HTParse() internals */
    +    if (*relatedName &&
    +	((wanted & PARSE_ALL_WITHOUT_ANCHOR) == PARSE_ALL_WITHOUT_ANCHOR)) {
    +	/*
    +	 * Check whether to fill in localhost.  - FM
    +	 */
    +	LYFillLocalFileURL(&return_value, relatedName);
    +	CTRACE((tfp, "pass LYFillLocalFile:`%s'\n", return_value));
    +    }
    +
    +    return return_value;	/* exactly the right length */
    +}
    +
    +/*	HTParseAnchor(), fast HTParse() specialization
    + *	----------------------------------------------
    + *
    + * On exit,
    + *	returns		A pointer within input string (probably to its end '\0')
    + */
    +const char *HTParseAnchor(const char *aName)
    +{
    +    const char *p = aName;
    +
    +    for (; *p && *p != '#'; p++) {
    +	;
    +    }
    +    if (*p == '#') {
    +	/* the safe way based on HTParse() -
    +	 * keeping in mind scan() peculiarities on schemes:
    +	 */
    +	struct struct_parts given;
    +	size_t need = ((unsigned) ((p - aName) + (int) strlen(p) + 1));
    +	char *name;
    +
    +	if (need > (size_t) max_uri_size) {
    +	    p += strlen(p);
    +	} else {
    +	    name = (char *) LYalloca(need);
    +
    +	    if (name == NULL) {
    +		outofmem(__FILE__, "HTParseAnchor");
    +	    }
    +	    strcpy(name, aName);
    +	    scan(name, &given);
    +	    LYalloca_free(name);
    +
    +	    p++;		/*next to '#' */
    +	    if (given.anchor == NULL) {
    +		for (; *p; p++)	/*scroll to end '\0' */
    +		    ;
    +	    }
    +	}
    +    }
    +    return p;
    +}
    +
    +/*	Simplify a filename.				HTSimplify()
    + *	--------------------
    + *
    + *  A unix-style file is allowed to contain the sequence xxx/../ which may
    + *  be replaced by "" , and the sequence "/./" which may be replaced by "/".
    + *  Simplification helps us recognize duplicate filenames.
    + *
    + *  RFC 3986 section 5.2.4 says to do this whether or not the path was relative.
    + */
    +void HTSimplify(char *filename, BOOL absolute)
    +{
    +#define MY_FMT "HTParse HTSimplify\t(%s)"
    +#ifdef NO_LYNX_TRACE
    +#define debug_at(at)		/* nothing */
    +#define atln		"?"
    +#else
    +    const char *atln;
    +
    +#define debug_at(at)	atln = at
    +#endif
    +    char *mark;
    +    char *p;
    +    size_t limit;
    +
    +    CTRACE2(TRACE_HTPARSE,
    +	    (tfp, MY_FMT " %s\n",
    +	     filename,
    +	     absolute ? "ABS" : "REL"));
    +
    +    if (LYIsPathSep(*filename) && !absolute)
    +	++filename;
    +    mark = filename;
    +    limit = strlen(filename);
    +
    +    for (p = filename; *p; ++p) {
    +	if (*p == '?' || *p == '#') {
    +	    limit = (size_t) (p - filename);
    +	    break;
    +	}
    +    }
    +    while ((limit != 0) && (*filename != '\0')) {
    +	size_t trim = 0;
    +	size_t skip = 0;
    +	size_t last = 0;
    +
    +	debug_at("?");
    +	p = filename;
    +	if (limit >= 2 && !memcmp(p, "./", 2)) {	/* 2A */
    +	    debug_at("2A");
    +	    trim = 2;
    +	} else if (limit >= 3 && !memcmp(p, "../", 3)) {
    +	    debug_at("2A2");
    +	    trim = 3;
    +	} else if (limit >= 3 && !memcmp(p, "/./", 3)) {	/* 2B */
    +	    debug_at("2B");
    +	    trim = 2;
    +	    skip = 1;
    +	} else if (limit == 2 && !memcmp(p, "/.", 2)) {
    +	    debug_at("2B2");
    +	    trim = 1;
    +	    skip = 1;
    +	} else if (limit >= 4 && !memcmp(p, "/../", 4)) {	/* 2C */
    +	    debug_at("2C");
    +	    trim = 3;
    +	    skip = 1;
    +	    last = 1;
    +	} else if (limit == 3 && !memcmp(p, "/..", 3)) {
    +	    debug_at("2C2");
    +	    trim = 2;
    +	    skip = 1;
    +	    last = 1;
    +	} else if (limit == 2 && !memcmp(p, "..", 2)) {		/* 2D */
    +	    debug_at("2D");
    +	    trim = 2;
    +	} else if (limit == 1 && !memcmp(p, ".", 1)) {
    +	    debug_at("2D2");
    +	    trim = 1;
    +	}
    +	if (trim) {
    +	    CTRACE2(TRACE_HTPARSE,
    +		    (tfp, MY_FMT " trim %lu/%lu (%.*s) '%.*s' @%s\n",
    +		     mark, (unsigned long) trim, (unsigned long) limit,
    +		     (int) trim, p + skip, (int) limit, p, atln));
    +	}
    +	if (last) {
    +	    char *prior = filename;
    +
    +	    if (prior != mark) {
    +		--prior;
    +		while (prior != mark && *prior != '/') {
    +		    --prior;
    +		}
    +	    }
    +	    if (prior != filename) {
    +		trim += (size_t) (filename - prior);
    +		limit += (size_t) (filename - prior);
    +		filename = prior;
    +		CTRACE2(TRACE_HTPARSE,
    +			(tfp, MY_FMT " TRIM %lu/%lu (%.*s)\n",
    +			 mark, (unsigned long) trim, (unsigned long) limit,
    +			 (int) trim, filename + skip));
    +	    }
    +	}
    +	if (trim) {
    +	    limit -= trim;
    +	    for (p = filename;; ++p) {
    +		if ((p[0] = p[trim]) == '\0') {
    +		    break;
    +		}
    +		if (skip) {
    +		    p[0] = '/';
    +		    skip = 0;
    +		}
    +	    }
    +	    CTRACE2(TRACE_HTPARSE,
    +		    (tfp, MY_FMT " loop %lu\n", mark, (unsigned long) limit));
    +	} else {
    +	    if (*filename == '/') {
    +		++filename;
    +		--limit;
    +	    }
    +	    while ((limit != 0) && (*filename != '/')) {
    +		++filename;
    +		--limit;
    +	    }
    +	}
    +    }
    +    CTRACE2(TRACE_HTPARSE, (tfp, MY_FMT " done\n", mark));
    +#undef MY_FMT
    +}
    +
    +/*	Make Relative Name.					HTRelative()
    + *	-------------------
    + *
    + * This function creates and returns a string which gives an expression of
    + * one address as related to another.  Where there is no relation, an absolute
    + * address is returned.
    + *
    + *  On entry,
    + *	Both names must be absolute, fully qualified names of nodes
    + *	(no anchor bits)
    + *
    + *  On exit,
    + *	The return result points to a newly allocated name which, if
    + *	parsed by HTParse relative to relatedName, will yield aName.
    + *	The caller is responsible for freeing the resulting name later.
    + *
    + */
    +char *HTRelative(const char *aName,
    +		 const char *relatedName)
    +{
    +    char *result = NULL;
    +    const char *p = aName;
    +    const char *q = relatedName;
    +    const char *after_access = NULL;
    +    const char *path = NULL;
    +    const char *last_slash = NULL;
    +    int slashes = 0;
    +
    +    for (; *p; p++, q++) {	/* Find extent of match */
    +	if (*p != *q)
    +	    break;
    +	if (*p == ':')
    +	    after_access = p + 1;
    +	if (*p == '/') {
    +	    last_slash = p;
    +	    slashes++;
    +	    if (slashes == 3)
    +		path = p;
    +	}
    +    }
    +
    +    /* q, p point to the first non-matching character or zero */
    +
    +    if (!after_access) {	/* Different access */
    +	StrAllocCopy(result, aName);
    +    } else if (slashes < 3) {	/* Different nodes */
    +	StrAllocCopy(result, after_access);
    +    } else if (slashes == 3) {	/* Same node, different path */
    +	StrAllocCopy(result, path);
    +    } else {			/* Some path in common */
    +	unsigned levels = 0;
    +
    +	for (; *q && (*q != '#'); q++)
    +	    if (*q == '/')
    +		levels++;
    +	result = typecallocn(char, 3 * levels + strlen(last_slash) + 1);
    +
    +	if (result == NULL)
    +	    outofmem(__FILE__, "HTRelative");
    +
    +	result[0] = '\0';
    +	for (; levels; levels--)
    +	    strcat(result, "../");
    +	strcat(result, last_slash + 1);
    +    }
    +    CTRACE((tfp,
    +	    "HTparse: `%s' expressed relative to\n   `%s' is\n   `%s'.\n",
    +	    aName, relatedName, result));
    +    return result;
    +}
    +
    +#define AlloCopy(next,base,extra) \
    +	typecallocn(char, ((next - base) + ((int) extra)))
    +
    +/*	Escape undesirable characters using %			HTEscape()
    + *	-------------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be unacceptable unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits.
    + *
    + *	Unlike HTUnEscape(), this routine returns a calloc'd string.
    + */
    +/* *INDENT-OFF* */
    +static const unsigned char isAcceptable[96] =
    +
    +/*	Bit 0		xalpha		-- see HTFile.h
    + *	Bit 1		xpalpha		-- as xalpha but with plus.
    + *	Bit 2 ...	path		-- as xpalphas but with /
    + */
    +    /*	 0 1 2 3 4 5 6 7 8 9 A B C D E F */
    +    {	 0,0,0,0,0,0,0,0,0,0,7,6,0,7,7,4,	/* 2x	!"#$%&'()*+,-./  */
    +	 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,	/* 3x  0123456789:;<=>?  */
    +	 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,	/* 4x  @ABCDEFGHIJKLMNO  */
    +	 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,	/* 5X  PQRSTUVWXYZ[\]^_  */
    +	 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,	/* 6x  `abcdefghijklmno  */
    +	 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0 };	/* 7X  pqrstuvwxyz{|}~	DEL */
    +/* *INDENT-ON* */
    +
    +static const char *hex = "0123456789ABCDEF";
    +
    +#define ACCEPTABLE(a)	( a>=32 && a<128 && ((isAcceptable[a-32]) & mask))
    +
    +char *HTEscape(const char *str,
    +	       unsigned mask)
    +{
    +    const char *p;
    +    char *q;
    +    char *result;
    +    size_t unacceptable = 0;
    +
    +    for (p = str; *p; p++)
    +	if (!ACCEPTABLE(UCH(TOASCII(*p))))
    +	    unacceptable++;
    +    result = AlloCopy(p, str, (unacceptable * 2) + 1);
    +
    +    if (result == NULL)
    +	outofmem(__FILE__, "HTEscape");
    +
    +    for (q = result, p = str; *p; p++) {
    +	unsigned char a = UCH(TOASCII(*p));
    +
    +	if (!ACCEPTABLE(a)) {
    +	    *q++ = HEX_ESCAPE;	/* Means hex coming */
    +	    *q++ = hex[a >> 4];
    +	    *q++ = hex[a & 15];
    +	} else
    +	    *q++ = *p;
    +    }
    +    *q = '\0';			/* Terminate */
    +    return result;
    +}
    +
    +/*	Escape unsafe characters using %			HTEscapeUnsafe()
    + *	--------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be that may be unsafe are unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits.
    + *
    + *	Unlike HTUnEscape(), this routine returns a malloc'd string.
    + */
    +#define UNSAFE(ch) (((ch) <= 32) || ((ch) >= 127))
    +
    +char *HTEscapeUnsafe(const char *str)
    +{
    +    const char *p;
    +    char *q;
    +    char *result;
    +    size_t unacceptable = 0;
    +
    +    for (p = str; *p; p++)
    +	if (UNSAFE(UCH(TOASCII(*p))))
    +	    unacceptable++;
    +    result = AlloCopy(p, str, (unacceptable * 2) + 1);
    +
    +    if (result == NULL)
    +	outofmem(__FILE__, "HTEscapeUnsafe");
    +
    +    for (q = result, p = str; *p; p++) {
    +	unsigned char a = UCH(TOASCII(*p));
    +
    +	if (UNSAFE(a)) {
    +	    *q++ = HEX_ESCAPE;	/* Means hex coming */
    +	    *q++ = hex[a >> 4];
    +	    *q++ = hex[a & 15];
    +	} else
    +	    *q++ = *p;
    +    }
    +    *q = '\0';			/* Terminate */
    +    return result;
    +}
    +
    +/*	Escape undesirable characters using % but space to +.	HTEscapeSP()
    + *	-----------------------------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be unacceptable unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits,
    + *	except that spaces are converted to '+' instead of %2B.
    + *
    + *	Unlike HTUnEscape(), this routine returns a calloced string.
    + */
    +char *HTEscapeSP(const char *str,
    +		 unsigned mask)
    +{
    +    const char *p;
    +    char *q;
    +    char *result;
    +    size_t unacceptable = 0;
    +
    +    for (p = str; *p; p++)
    +	if (!(*p == ' ' || ACCEPTABLE(UCH(TOASCII(*p)))))
    +	    unacceptable++;
    +    result = AlloCopy(p, str, (unacceptable * 2) + 1);
    +
    +    if (result == NULL)
    +	outofmem(__FILE__, "HTEscape");
    +
    +    for (q = result, p = str; *p; p++) {
    +	unsigned char a = UCH(TOASCII(*p));
    +
    +	if (a == 32) {
    +	    *q++ = '+';
    +	} else if (!ACCEPTABLE(a)) {
    +	    *q++ = HEX_ESCAPE;	/* Means hex coming */
    +	    *q++ = hex[a >> 4];
    +	    *q++ = hex[a & 15];
    +	} else {
    +	    *q++ = *p;
    +	}
    +    }
    +    *q = '\0';			/* Terminate */
    +    return result;
    +}
    +
    +/*	Decode %xx escaped characters.				HTUnEscape()
    + *	------------------------------
    + *
    + *	This function takes a pointer to a string in which some
    + *	characters may have been encoded in %xy form, where xy is
    + *	the ASCII hex code for character 16x+y.
    + *	The string is converted in place, as it will never grow.
    + */
    +static char from_hex(int c)
    +{
    +    return (char) (c >= '0' && c <= '9' ? c - '0'
    +		   : c >= 'A' && c <= 'F' ? c - 'A' + 10
    +		   : c - 'a' + 10);	/* accept small letters just in case */
    +}
    +
    +char *HTUnEscape(char *str)
    +{
    +    char *p = str;
    +    char *q = str;
    +
    +    if (!(p && *p))
    +	return str;
    +
    +    while (*p != '\0') {
    +	if (*p == HEX_ESCAPE &&
    +	/*
    +	 * Tests shouldn't be needed, but better safe than sorry.
    +	 */
    +	    p[1] && p[2] &&
    +	    isxdigit(UCH(p[1])) &&
    +	    isxdigit(UCH(p[2]))) {
    +	    p++;
    +	    if (*p)
    +		*q = (char) (from_hex(*p++) * 16);
    +	    if (*p) {
    +		/*
    +		 * Careful! FROMASCII() may evaluate its arg more than once!
    +		 */
    +		/* S/390 -- gil -- 0221 */
    +		*q = (char) (*q + from_hex(*p++));
    +	    }
    +	    *q = FROMASCII(*q);
    +	    q++;
    +	} else {
    +	    *q++ = *p++;
    +	}
    +    }
    +
    +    *q = '\0';
    +    return str;
    +
    +}				/* HTUnEscape */
    +
    +/*	Decode some %xx escaped characters.		      HTUnEscapeSome()
    + *	-----------------------------------			Klaus Weide
    + *							    (kweide@tezcat.com)
    + *	This function takes a pointer to a string in which some
    + *	characters may have been encoded in %xy form, where xy is
    + *	the ASCII hex code for character 16x+y, and a pointer to
    + *	a second string containing one or more characters which
    + *	should be unescaped if escaped in the first string.
    + *	The first string is converted in place, as it will never grow.
    + */
    +char *HTUnEscapeSome(char *str,
    +		     const char *do_trans)
    +{
    +    char *p = str;
    +    char *q = str;
    +    char testcode;
    +
    +    if (p == NULL || *p == '\0' || do_trans == NULL || *do_trans == '\0')
    +	return str;
    +
    +    while (*p != '\0') {
    +	if (*p == HEX_ESCAPE &&
    +	    p[1] && p[2] &&	/* tests shouldn't be needed, but.. */
    +	    isxdigit(UCH(p[1])) &&
    +	    isxdigit(UCH(p[2])) &&
    +	    (testcode = (char) FROMASCII(from_hex(p[1]) * 16 +
    +					 from_hex(p[2]))) &&	/* %00 no good */
    +	    StrChr(do_trans, testcode)) {	/* it's one of the ones we want */
    +	    *q++ = testcode;
    +	    p += 3;
    +	} else {
    +	    *q++ = *p++;
    +	}
    +    }
    +
    +    *q = '\0';
    +    return str;
    +
    +}				/* HTUnEscapeSome */
    +/* *INDENT-OFF* */
    +static const unsigned char crfc[96] =
    +
    +/*	Bit 0		xalpha		-- need "quoting"
    + *	Bit 1		xpalpha		-- need \escape if quoted
    + */
    +    /*	 0 1 2 3 4 5 6 7 8 9 A B C D E F */
    +    {	 1,0,3,0,0,0,0,0,1,1,0,0,1,0,1,0,	/* 2x	!"#$%&'()*+,-./  */
    +	 0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,	/* 3x  0123456789:;<=>?  */
    +	 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	/* 4x  @ABCDEFGHIJKLMNO  */
    +	 0,0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,	/* 5X  PQRSTUVWXYZ[\]^_  */
    +	 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	/* 6x  `abcdefghijklmno  */
    +	 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3 };	/* 7X  pqrstuvwxyz{|}~	DEL */
    +/* *INDENT-ON* */
    +
    +#define ASCII_TAB '\011'
    +#define ASCII_LF  '\012'
    +#define ASCII_CR  '\015'
    +#define ASCII_SPC '\040'
    +#define ASCII_BAK '\134'
    +
    +/*
    + *  Turn a string which is not a RFC 822 token into a quoted-string. - KW
    + *  The "quoted" parameter tells whether we need the beginning/ending quote
    + *  marks.  If not, the caller will provide them -TD
    + */
    +void HTMake822Word(char **str,
    +		   int quoted)
    +{
    +    const char *p;
    +    char *q;
    +    char *result;
    +    unsigned char a;
    +    unsigned added = 0;
    +
    +    if (isEmpty(*str)) {
    +	StrAllocCopy(*str, quoted ? "\"\"" : "");
    +	return;
    +    }
    +    for (p = *str; *p; p++) {
    +	a = UCH(TOASCII(*p));	/* S/390 -- gil -- 0240 */
    +	if (a < 32 || a >= 128 ||
    +	    ((crfc[a - 32]) & 1)) {
    +	    if (!added)
    +		added = 2;
    +	    if (a >= 160 || a == '\t')
    +		continue;
    +	    if (a == '\r' || a == '\n')
    +		added += 2;
    +	    else if ((a & 127) < 32 || ((crfc[a - 32]) & 2))
    +		added++;
    +	}
    +    }
    +    if (!added)
    +	return;
    +    result = AlloCopy(p, *str, added + 1);
    +    if (result == NULL)
    +	outofmem(__FILE__, "HTMake822Word");
    +
    +    q = result;
    +    if (quoted)
    +	*q++ = '"';
    +    /*
    +     * Having converted the character to ASCII, we can't use symbolic
    +     * escape codes, since they're in the host character set, which
    +     * is not necessarily ASCII.  Thus we use octal escape codes instead.
    +     * -- gil (Paul Gilmartin) 
    +     */
    +    /* S/390 -- gil -- 0268 */
    +    for (p = *str; *p; p++) {
    +	a = UCH(TOASCII(*p));
    +	if ((a != ASCII_TAB) &&
    +	    ((a & 127) < ASCII_SPC ||
    +	     (a < 128 && ((crfc[a - 32]) & 2))))
    +	    *q++ = ASCII_BAK;
    +	*q++ = *p;
    +	if (a == ASCII_LF ||
    +	    (a == ASCII_CR && (TOASCII(*(p + 1)) != ASCII_LF)))
    +	    *q++ = ' ';
    +    }
    +    if (quoted)
    +	*q++ = '"';
    +    *q = '\0';			/* Terminate */
    +    FREE(*str);
    +    *str = result;
    +}
    diff --git a/WWW/Library/Implementation/HTParse.h b/WWW/Library/Implementation/HTParse.h
    new file mode 100644
    index 0000000..49b40b3
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTParse.h
    @@ -0,0 +1,212 @@
    +/*
    + * $LynxId: HTParse.h,v 1.26 2021/07/05 20:56:50 tom Exp $
    + *				HTParse:  URL parsing in the WWW Library
    + *				HTPARSE
    + *
    + *  This module of the WWW library contains code to parse URLs and various
    + *  related things.
    + *  Implemented by HTParse.c .
    + */
    +#ifndef HTPARSE_H
    +#define HTPARSE_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +#define RFC_3986_UNRESERVED(c) (isalnum(UCH(c)) || strchr("-._~", UCH(c)) != 0)
    +#define RFC_3986_GEN_DELIMS(c) ((c) != 0 && strchr(":/?#[]@", UCH(c)) != 0)
    +#define RFC_3986_SUB_DELIMS(c) ((c) != 0 && strchr("!$&'()*+,;=", UCH(c)) != 0)
    +
    +/*
    + *  The following are flag bits which may be ORed together to form
    + *  a number to give the 'wanted' argument to HTParse.
    + */
    +#define PARSE_ACCESS            16
    +#define PARSE_HOST               8
    +#define PARSE_PATH               4
    +#define PARSE_ANCHOR             2
    +#define PARSE_PUNCTUATION        1
    +#define PARSE_ALL               31
    +#define PARSE_ALL_WITHOUT_ANCHOR  (PARSE_ALL ^ PARSE_ANCHOR)
    +/*
    + *  Additional flag bits for more details on components already
    + *  covered by the above.  The PARSE_PATH above doesn't really
    + *  strictly refer to the path component in the sense of the URI
    + *  specs only, but rather to that combined with a possible query
    + *  component. - kw
    + */
    +#define PARSE_STRICTPATH        32
    +#define PARSE_QUERY             64
    +/*
    + *  The following are valid mask values.  The terms are the BNF names
    + *  in the URL document.
    + */
    +#define URL_XALPHAS     UCH(1)
    +#define URL_XPALPHAS    UCH(2)
    +#define URL_PATH        UCH(4)
    +
    +#ifdef USE_IDN2
    +    typedef enum {
    +	LYidna2003 = 1,
    +	LYidna2008,
    +	LYidnaTR46,
    +	LYidnaCompat
    +    } HTIdnaModes;
    +
    +    extern int LYidnaMode;
    +#endif
    +
    +/*	Strip white space off a string.				HTStrip()
    + *	-------------------------------
    + *
    + * On exit,
    + *	Return value points to first non-white character, or to 0 if none.
    + *	All trailing white space is OVERWRITTEN with zero.
    + */
    +    extern char *HTStrip(char *s);
    +
    +/*
    + *	Parse a port number
    + *	-------------------
    + *
    + * On entry,
    + *	host            A pointer to hostname possibly followed by port
    + *
    + * On exit,
    + *	returns         A pointer to the ":" before the port
    + *	sets            the port number via the pointer portp.
    + */
    +    extern char *HTParsePort(char *host, int *portp);
    +
    +/*	Parse a Name relative to another name.			HTParse()
    + *	--------------------------------------
    + *
    + *	This returns those parts of a name which are given (and requested)
    + *	substituting bits from the related name where necessary.
    + *
    + * On entry,
    + *	aName		A filename given
    + *      relatedName     A name relative to which aName is to be parsed
    + *      wanted          A mask for the bits which are wanted.
    + *
    + * On exit,
    + *	returns		A pointer to a malloc'd string which MUST BE FREED
    + */
    +    extern char *HTParse(const char *aName,
    +			 const char *relatedName,
    +			 int wanted);
    +
    +/*	HTParseAnchor(), fast HTParse() specialization
    + *	----------------------------------------------
    + *
    + * On exit,
    + *	returns		A pointer within input string (probably to its end '\0')
    + */
    +    extern const char *HTParseAnchor(const char *aName);
    +
    +/*	Simplify a filename.				HTSimplify()
    + *	--------------------
    + *
    + *  A unix-style file is allowed to contain the sequence xxx/../ which may
    + *  be replaced by "" , and the sequence "/./" which may be replaced by "/".
    + *  Simplification helps us recognize duplicate filenames.
    + */
    +    extern void HTSimplify(char *filename, BOOL absolute);
    +
    +/*	Make Relative Name.					HTRelative()
    + *	-------------------
    + *
    + * This function creates and returns a string which gives an expression of
    + * one address as related to another.  Where there is no relation, an absolute
    + * address is returned.
    + *
    + *  On entry,
    + *	Both names must be absolute, fully qualified names of nodes
    + *	(no anchor bits)
    + *
    + *  On exit,
    + *	The return result points to a newly allocated name which, if
    + *	parsed by HTParse relative to relatedName, will yield aName.
    + *	The caller is responsible for freeing the resulting name later.
    + *
    + */
    +    extern char *HTRelative(const char *aName,
    +			    const char *relatedName);
    +
    +/*		Escape undesirable characters using %		HTEscape()
    + *		-------------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be unacceptable are unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits.
    + *
    + *	Unlike HTUnEscape(), this routine returns a malloc'd string.
    + */
    +    extern char *HTEscape(const char *str,
    +			  unsigned mask);
    +
    +/*		Escape unsafe characters using %		HTEscapeUnsafe()
    + *		--------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be that may be unsafe are unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits.
    + *
    + *	Unlike HTUnEscape(), this routine returns a malloc'd string.
    + */
    +    extern char *HTEscapeUnsafe(const char *str);
    +
    +/*	Escape undesirable characters using % but space to +.	HTEscapeSP()
    + *	-----------------------------------------------------
    + *
    + *	This function takes a pointer to a string in which
    + *	some characters may be unacceptable are unescaped.
    + *	It returns a string which has these characters
    + *	represented by a '%' character followed by two hex digits,
    + *	except that spaces are converted to '+' instead of %2B.
    + *
    + *	Unlike HTUnEscape(), this routine returns a malloc'd string.
    + */
    +    extern char *HTEscapeSP(const char *str,
    +			    unsigned mask);
    +
    +/*	Decode %xx escaped characters.				HTUnEscape()
    + *	------------------------------
    + *
    + *	This function takes a pointer to a string in which some
    + *	characters may have been encoded in %xy form, where xy is
    + *	the acsii hex code for character 16x+y.
    + *	The string is converted in place, as it will never grow.
    + */
    +    extern char *HTUnEscape(char *str);
    +
    +/*	Decode some %xx escaped characters.		      HTUnEscapeSome()
    + *	-----------------------------------			Klaus Weide
    + *							    (kweide@tezcat.com)
    + *	This function takes a pointer to a string in which some
    + *	characters may have been encoded in %xy form, where xy is
    + *	the acsii hex code for character 16x+y, and a pointer to
    + *	a second string containing one or more characters which
    + *	should be unescaped if escaped in the first string.
    + *	The first string is converted in place, as it will never grow.
    + */
    +    extern char *HTUnEscapeSome(char *str,
    +				const char *do_trans);
    +
    +/*
    + *  Turn a string which is not a RFC 822 token into a quoted-string. - KW
    + */
    +    extern void HTMake822Word(char **str,
    +			      int quoted);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTPARSE_H */
    diff --git a/WWW/Library/Implementation/HTPlain.c b/WWW/Library/Implementation/HTPlain.c
    new file mode 100644
    index 0000000..ff52731
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTPlain.c
    @@ -0,0 +1,601 @@
    +/*
    + * $LynxId: HTPlain.c,v 1.61 2020/01/21 22:05:46 tom Exp $
    + *
    + *		Plain text object		HTWrite.c
    + *		=================
    + *
    + *	This version of the stream object just writes to a socket.
    + *	The socket is assumed open and left open.
    + *
    + *	Bugs:
    + *		strings written must be less than buffer size.
    + */
    +
    +#define HTSTREAM_INTERNAL 1
    +
    +#include 
    +#include 		/* S/390 -- gil -- 0288 */
    +
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#define Lynx_HTML_Handler
    +#include 		/* styles[] */
    +
    +#define BUFFER_SIZE 4096;	/* Tradeoff */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +
    +static int HTPlain_lastraw = -1;
    +static int HTPlain_bs_pending = 0;	/* 1:bs 2:underline 3:underline+bs - kw */
    +
    +/*		HTML Object
    + *		-----------
    + */
    +struct _HTStream {
    +    const HTStreamClass *isa;
    +    HText *text;
    +    /*
    +     * The node_anchor UCInfo and handle for the input (PARSER) stage.  - FM
    +     */
    +    LYUCcharset *inUCI;
    +    int inUCLYhndl;
    +    /*
    +     * The node_anchor UCInfo and handle for the output (HTEXT) stage.  - FM
    +     */
    +    LYUCcharset *outUCI;
    +    int outUCLYhndl;
    +
    +    UTFDecodeState U;
    +    UCTransParams T;
    +};
    +
    +static char replace_buf[64];	/* buffer for replacement strings */
    +
    +static void HTPlain_getChartransInfo(HTStream *me, HTParentAnchor *anchor)
    +{
    +    if (me->inUCLYhndl < 0) {
    +	HTAnchor_copyUCInfoStage(anchor, UCT_STAGE_PARSER, UCT_STAGE_MIME,
    +				 UCT_SETBY_PARSER);
    +	me->inUCLYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER);
    +    }
    +    if (me->outUCLYhndl < 0) {
    +	int chndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_HTEXT);
    +
    +	if (chndl < 0) {
    +	    chndl = current_char_set;
    +	    HTAnchor_setUCInfoStage(anchor, chndl,
    +				    UCT_STAGE_HTEXT, UCT_SETBY_DEFAULT);
    +	}
    +	HTAnchor_setUCInfoStage(anchor, chndl,
    +				UCT_STAGE_HTEXT, UCT_SETBY_DEFAULT);
    +	me->outUCLYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_HTEXT);
    +    }
    +    me->inUCI = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_PARSER);
    +    me->outUCI = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT);
    +}
    +
    +/*	Write the buffer out to the socket
    + *	----------------------------------
    + */
    +
    +/*_________________________________________________________________________
    + *
    + *			A C T I O N	R O U T I N E S
    + */
    +
    +static void HTPlain_write(HTStream *me, const char *s,
    +			  int l);
    +
    +/*	Character handling
    + *	------------------
    + */
    +static void HTPlain_put_character(HTStream *me, int c)
    +{
    +#ifdef REMOVE_CR_ONLY
    +    /*
    +     * Throw away \r's.
    +     */
    +    if (c != '\r') {
    +	HText_appendCharacter(me->text, c);
    +    }
    +#else
    +    /*
    +     * See HTPlain_write() for explanations of the following code (we've been
    +     * called via HTPlain_put_string() to do for each character of a terminated
    +     * string what HTPlain_write() does via a while loop for each character in
    +     * a stream of given length).  - FM
    +     */
    +    if ((HTPlain_lastraw == '\r') && c == '\n') {
    +	HTPlain_lastraw = -1;
    +	return;
    +    }
    +    if (c == '\b' || c == '_' || HTPlain_bs_pending) {
    +	char temp[1];
    +
    +	temp[0] = (char) c;
    +	HTPlain_write(me, temp, 1);
    +	return;
    +    }
    +    HTPlain_lastraw = UCH(c);
    +    if (c == '\r') {
    +	HText_appendCharacter(me->text, '\n');
    +    } else if (TOASCII(UCH(c)) >= 127) {	/* S/390 -- gil -- 0305 */
    +	char temp[1];
    +
    +	temp[0] = (char) c;
    +	/*
    +	 * For now, don't repeat everything here that has been done below - KW
    +	 */
    +	HTPlain_write(me, temp, 1);
    +    } else if (IS_CJK_TTY) {
    +	HText_appendCharacter(me->text, c);
    +    } else if (TOASCII(UCH(c)) >= 127 && TOASCII(UCH(c)) < 161 &&
    +	       HTPassHighCtrlRaw) {
    +	HText_appendCharacter(me->text, c);
    +#if CH_NBSP < 127
    +    } else if (UCH(c) == CH_NBSP) {	/* S/390 -- gil -- 0341 */
    +	HText_appendCharacter(me->text, ' ');
    +#endif
    +#if CH_SHY < 127
    +    } else if (UCH(c) == CH_SHY) {
    +	return;
    +#endif
    +    } else if ((UCH(c) >= ' ' && TOASCII(UCH(c)) < 127) ||
    +	       c == '\n' || c == '\t') {
    +	HText_appendCharacter(me->text, c);
    +    }
    +#endif /* REMOVE_CR_ONLY */
    +}
    +
    +/*	String handling
    + *	---------------
    + *
    + */
    +static void HTPlain_put_string(HTStream *me, const char *s)
    +{
    +#ifdef REMOVE_CR_ONLY
    +    HText_appendText(me->text, s);
    +#else
    +    const char *p;
    +
    +    if (s == NULL)
    +	return;
    +    for (p = s; *p; p++) {
    +	HTPlain_put_character(me, *p);
    +    }
    +#endif /* REMOVE_CR_ONLY */
    +}
    +
    +/*
    + *	Entry function for displayed text/plain and WWW_SOURCE strings. - FM
    + *	---------------------------------------------------------------
    + */
    +static void HTPlain_write(HTStream *me, const char *s, int l)
    +{
    +    const char *p;
    +    const char *e = s + l;
    +    int c;
    +    unsigned c_unsign;
    +    BOOL chk;
    +    UCode_t code, uck = -1;
    +    int saved_char_in = '\0';
    +
    +    for (p = s; p < e; p++) {
    +#ifdef REMOVE_CR_ONLY
    +	/*
    +	 * Append the whole string, but remove any \r's.  - FM
    +	 */
    +	if (*p != '\r') {
    +	    HText_appendCharacter(me->text, *p);
    +	}
    +#else
    +	if (*p == '\b') {
    +	    if (HTPlain_lastraw >= UCH(' ') &&
    +		HTPlain_lastraw != '\r' && HTPlain_lastraw != '\n') {
    +		if (!HTPlain_bs_pending) {
    +		    HTPlain_bs_pending = 1;
    +		    continue;
    +		} else if (HTPlain_bs_pending == 2) {
    +		    HTPlain_bs_pending = 3;
    +		    continue;
    +		}
    +	    }
    +	    if (HTPlain_bs_pending >= 2)
    +		HText_appendCharacter(me->text, '_');
    +	    HTPlain_bs_pending = 0;
    +	} else if (*p == '_') {
    +	    if (!HTPlain_bs_pending) {
    +		HTPlain_bs_pending = 2;
    +		HTPlain_lastraw = UCH(*p);
    +		continue;
    +	    }
    +	}
    +
    +	/*
    +	 * Try to handle lone LFs, CRLFs and lone CRs as newline, and to deal
    +	 * with control, ASCII, and 8-bit characters based on best guesses of
    +	 * what's appropriate.  - FM
    +	 */
    +	if ((HTPlain_lastraw == '\r') && *p == '\n') {
    +	    HTPlain_lastraw = -1;
    +	    continue;
    +	}
    +
    +	if (HTPlain_bs_pending &&
    +	    !(UCH(*p) >= ' ' && *p != '\r' && *p != '\n' &&
    +	      (HTPlain_lastraw == UCH(*p) ||
    +	       HTPlain_lastraw == UCH('_') ||
    +	       *p == '_'))) {
    +	    if (HTPlain_bs_pending >= 2)
    +		HText_appendCharacter(me->text, '_');
    +	    HTPlain_bs_pending = 0;
    +	} else if (HTPlain_bs_pending == 1) {
    +	    HTPlain_bs_pending = 0;
    +	    continue;		/* ignore last two of "X\bX" or "X\b_" - kw */
    +	} else if (HTPlain_bs_pending == 3) {
    +	    if (*p == '_') {
    +		HTPlain_bs_pending = 2;
    +		continue;	/* ignore last two of "_\b_" - kw */
    +	    } else {
    +		HTPlain_bs_pending = 0;
    +		/* ignore first two of "_\bX" - kw */
    +	    }
    +	} else if (HTPlain_bs_pending == 2) {
    +	    HText_appendCharacter(me->text, '_');
    +	    if (*p == '_')
    +		continue;	/* keep second of "__" pending - kw */
    +	    HTPlain_bs_pending = 0;
    +	} else {
    +	    HTPlain_bs_pending = 0;
    +	}
    +	HTPlain_lastraw = UCH(*p);
    +	if (*p == '\r') {
    +	    HText_appendCharacter(me->text, '\n');
    +	    continue;
    +	}
    +	/*
    +	 * Make sure the character is handled as Unicode whenever that's
    +	 * appropriate.  - FM
    +	 */
    +	c = *p;
    +	c_unsign = UCH(c);
    +	code = (UCode_t) c_unsign;
    +	saved_char_in = '\0';
    +	/*
    +	 * Combine any UTF-8 multibytes into Unicode to check for special
    +	 * characters. - FM, TD
    +	 */
    +	if (me->T.decode_utf8) {
    +	    switch (HTDecodeUTF8(&(me->U), &c, &code)) {
    +	    case dUTF8_ok:
    +		if (code < 256) {
    +		    c = FROMASCII((char) code);
    +		    c_unsign = UCH(c);
    +		}
    +		break;
    +	    case dUTF8_err:
    +		code = UCS_REPL;
    +		strcpy(me->U.utf_buf, "\357\277\275");
    +		me->U.utf_buf_p = (me->U.utf_buf + 3);
    +		break;
    +	    case dUTF8_more:
    +		continue;
    +	    }
    +	}
    +	/*
    +	 * Convert characters from non-UTF-8 charsets to Unicode (if
    +	 * appropriate).  - FM
    +	 */
    +	if (!(me->T.decode_utf8 &&
    +	      UCH(*p) > 127)) {
    +	    if (me->T.trans_to_uni &&
    +		(TOASCII(code) >= LYlowest_eightbit[me->inUCLYhndl] ||	/* S/390 -- gil -- 0389 */
    +		 (code < ' ' && code != 0 &&
    +		  me->T.trans_C0_to_uni))) {
    +		/*
    +		 * Convert the octet to Unicode.  - FM
    +		 */
    +		code = (UCode_t) UCTransToUni(c, me->inUCLYhndl);
    +		if (code > 0) {
    +		    saved_char_in = c;
    +		    if (code < 256) {
    +			c = FROMASCII((char) code);
    +			c_unsign = UCH(c);
    +		    }
    +		}
    +	    } else if (code < 32 && code != 0 &&
    +		       me->T.trans_C0_to_uni) {
    +		/*
    +		 * Quote from SGML.c:
    +		 * "This else if may be too ugly to keep.  - KW"
    +		 */
    +		if (me->T.trans_from_uni &&
    +		    (((code = UCTransToUni(c, me->inUCLYhndl)) >= 32) ||
    +		     (me->T.transp &&
    +		      (code = UCTransToUni(c, me->inUCLYhndl)) > 0))) {
    +		    saved_char_in = c;
    +		    if (code < 256) {
    +			c = FROMASCII((char) code);
    +			c_unsign = UCH(c);
    +		    }
    +		} else {
    +		    uck = -1;
    +		    if (me->T.transp) {
    +			uck = UCTransCharStr(replace_buf, 60, c,
    +					     me->inUCLYhndl,
    +					     me->inUCLYhndl, NO);
    +		    }
    +		    if (!me->T.transp || uck < 0) {
    +			uck = UCTransCharStr(replace_buf, 60, c,
    +					     me->inUCLYhndl,
    +					     me->outUCLYhndl, YES);
    +		    }
    +		    if (uck == 0) {
    +			continue;
    +		    } else if (uck < 0) {
    +			me->U.utf_buf[0] = '\0';
    +		    } else {
    +			c = replace_buf[0];
    +			if (c && replace_buf[1]) {
    +			    HText_appendText(me->text, replace_buf);
    +			    continue;
    +			}
    +		    }
    +		    me->U.utf_buf[0] = '\0';
    +		    code = UCH(c);
    +		}		/*  Next line end of ugly stuff for C0. - KW */
    +	    } else {
    +		me->U.utf_buf[0] = '\0';
    +		code = UCH(c);
    +	    }
    +	}
    +	/*
    +	 * At this point we have either code in Unicode (and c in latin1 if
    +	 * code is in the latin1 range), or code and c will have to be passed
    +	 * raw.
    +	 */
    +
    +	/*
    +	 * If CJK mode is on, we'll assume the document matches the user's
    +	 * display character set, and if not, the user should toggle off
    +	 * raw/CJK mode to reload.  - FM
    +	 */
    +	if (IS_CJK_TTY) {
    +	    HText_appendCharacter(me->text, c);
    +
    +#define PASSHICTRL (me->T.transp || \
    +		    code >= LYlowest_eightbit[me->inUCLYhndl])
    +#define PASS8859SPECL me->T.pass_160_173_raw
    +#define PASSHI8BIT (HTPassEightBitRaw || \
    +		    (me->T.do_8bitraw && !me->T.trans_from_uni))
    +	    /*
    +	     * If HTPassHighCtrlRaw is set (e.g., for KOI8-R) assume the
    +	     * document matches and pass 127-160 8-bit characters.  If it
    +	     * doesn't match, the user should toggle raw/CJK mode off.  - FM
    +	     */
    +	} else if (TOASCII(code) >= 127 && TOASCII(code) < 161 &&	/* S/390 -- gil -- 0427 */
    +		   PASSHICTRL && PASS8859SPECL) {
    +	    HText_appendCharacter(me->text, c);
    +	} else if (code == CH_SHY && PASS8859SPECL) {
    +	    HText_appendCharacter(me->text, c);
    +	    /*
    +	     * If neither HTPassHighCtrlRaw nor CJK is set, play it safe and
    +	     * treat 160 (nbsp) as an ASCII space (32).  - FM
    +	     */
    +	} else if (code == CH_NBSP) {
    +	    HText_appendCharacter(me->text, ' ');
    +	    /*
    +	     * If neither HTPassHighCtrlRaw nor CJK is set, play it safe and
    +	     * ignore 173 (shy).  - FM
    +	     * Now only ignore it for color style, which doesn't handle it
    +	     * anyway.  Otherwise pass it on as LY_SOFT_HYPHEN and let HText
    +	     * deal with it.  It should be either ignored, or displayed as a
    +	     * hyphen if it was indeed at the end of a line.  Well it should. 
    +	     * - kw
    +	     */
    +	} else if (code == CH_SHY) {
    +#ifndef USE_COLOR_STYLE
    +	    HText_appendCharacter(me->text, LY_SOFT_HYPHEN);
    +#endif
    +	    continue;
    +	    /*
    +	     * If we get to here, pass the displayable ASCII characters.  - FM
    +	     */
    +	} else if ((code >= ' ' && code != UCS_REPL && TOASCII(code) < 127) ||
    +		   (PASSHI8BIT &&
    +		    c >= LYlowest_eightbit[me->outUCLYhndl]) ||
    +		   *p == '\n' || *p == '\t') {
    +	    HText_appendCharacter(me->text, c);
    +	    /*
    +	     * Use an ASCII space (32) for ensp, emsp or thinsp.  - FM
    +	     */
    +	} else if (code == 8194 || code == 8195 || code == 8201) {
    +	    HText_appendCharacter(me->text, ' ');
    +	    /*
    +	     * If we want the raw character, pass it now.  - FM
    +	     */
    +	} else if (me->T.use_raw_char_in && saved_char_in) {
    +	    HText_appendCharacter(me->text, saved_char_in);
    +/******************************************************************
    + * I.  LATIN-1 OR UCS2 TO DISPLAY CHARSET
    + ******************************************************************/
    +	} else if ((chk = (BOOL) (me->T.trans_from_uni && code >= 160)) &&
    +		   (uck = UCTransUniChar(code,
    +					 me->outUCLYhndl)) >= ' ' &&	/* S/390 -- gil -- 0464 */
    +		   uck < 256) {
    +	    CTRACE((tfp, "UCTransUniChar returned 0x%.2" PRI_UCode_t
    +		    ":'%c'.\n",
    +		    uck, FROMASCII(UCH(uck))));
    +	    HText_appendCharacter(me->text, ((char) (uck & 0xff)));
    +	} else if (chk &&
    +		   (uck == -4 ||
    +		    (me->T.repl_translated_C0 && uck > 0 && uck < ' ')) &&	/* S/390 -- gil -- 0481 */
    +	    /*
    +	     * Not found; look for replacement string.
    +	     */
    +		   (uck = UCTransUniCharStr(replace_buf, 60, code,
    +					    me->outUCLYhndl, 0) >= 0)) {
    +	    /*
    +	     * No further tests for valididy - assume that whoever defined
    +	     * replacement strings knew what she was doing.
    +	     */
    +	    HText_appendText(me->text, replace_buf);
    +	    /*
    +	     * If we get to here, and should have translated, translation has
    +	     * failed so far.
    +	     */
    +	} else if (chk && TOASCII(code) > 127 && me->T.output_utf8) {	/* S/390 -- gil -- 0498 */
    +	    /*
    +	     * We want UTF-8 output, so do it now.  - FM
    +	     */
    +	    if (*me->U.utf_buf) {
    +		HText_appendText(me->text, me->U.utf_buf);
    +		me->U.utf_buf[0] = '\0';
    +		me->U.utf_buf_p = me->U.utf_buf;
    +	    } else if (UCConvertUniToUtf8(code, replace_buf)) {
    +		HText_appendText(me->text, replace_buf);
    +	    } else {
    +		/*
    +		 * Out of luck, so use the UHHH notation (ugh).  - gil
    +		 */
    +		/* S/390 -- gil -- 0517 */
    +		sprintf(replace_buf, "U%.2lX", (unsigned long) TOASCII(code));
    +		HText_appendText(me->text, replace_buf);
    +	    }
    +	    /*
    +	     * If we don't actually want the character, make it safe and output
    +	     * that now.  - FM
    +	     */
    +	} else if ((c_unsign > 0 &&
    +		    (int) c_unsign < LYlowest_eightbit[me->outUCLYhndl]) ||
    +		   (me->T.trans_from_uni && !HTPassEightBitRaw)) {
    +	    /*
    +	     * If we do not have the "7-bit approximations" as our output
    +	     * character set (in which case we did it already) seek a
    +	     * translation for that.  Otherwise, or if the translation fails,
    +	     * use UHHH notation.  - FM
    +	     */
    +	    if ((chk = (BOOL) (me->outUCLYhndl !=
    +			       UCGetLYhndl_byMIME("us-ascii"))) &&
    +		(uck = UCTransUniChar(code,
    +				      UCGetLYhndl_byMIME("us-ascii")))
    +		>= ' ' && TOASCII(uck) < 127) {		/* S/390 -- gil -- 0535 */
    +		/*
    +		 * Got an ASCII character (yippey).  - FM
    +		 */
    +		c = FROMASCII((char) uck);
    +		HText_appendCharacter(me->text, c);
    +	    } else if ((chk && uck == -4) &&
    +		       (uck = UCTransUniCharStr(replace_buf,
    +						60, code,
    +						UCGetLYhndl_byMIME("us-ascii"),
    +						0) >= 0)) {
    +		/*
    +		 * Got a replacement string (yippey).  - FM
    +		 */
    +		HText_appendText(me->text, replace_buf);
    +	    } else if (code == 8204 || code == 8205) {
    +		/*
    +		 * Ignore 8204 (zwnj) or 8205 (zwj), if we get to here.  - FM
    +		 */
    +		CTRACE((tfp, "HTPlain_write: Ignoring '%" PRI_UCode_t "'.\n", code));
    +	    } else if (code == 8206 || code == 8207) {
    +		/*
    +		 * Ignore 8206 (lrm) or 8207 (rlm), if we get to here.  - FM
    +		 */
    +		CTRACE((tfp, "HTPlain_write: Ignoring '%" PRI_UCode_t "'.\n", code));
    +	    } else {
    +		/*
    +		 * Out of luck, so use the UHHH notation (ugh).  - FM
    +		 */
    +		/* do not print UHHH for now
    +		   sprintf(replace_buf, "U%.2lX", code);
    +		   HText_appendText(me->text, replace_buf);
    +		 */
    +	    }
    +	    /*
    +	     * If we get to here and have a monobyte character, pass it.  - FM
    +	     */
    +	} else if (c_unsign != 0 && c_unsign < 256) {
    +	    HText_appendCharacter(me->text, c);
    +	}
    +#endif /* REMOVE_CR_ONLY */
    +    }
    +}
    +
    +/*	Free an HTML object
    + *	-------------------
    + *
    + *	Note that the SGML parsing context is freed, but the created object is
    + *	not, as it takes on an existence of its own unless explicitly freed.
    + */
    +static void HTPlain_free(HTStream *me)
    +{
    +    if (HTPlain_bs_pending >= 2)
    +	HText_appendCharacter(me->text, '_');
    +    FREE(me);
    +}
    +
    +/*	End writing
    +*/
    +static void HTPlain_abort(HTStream *me, HTError e GCC_UNUSED)
    +{
    +    HTPlain_free(me);
    +}
    +
    +/*		Structured Object Class
    + *		-----------------------
    + */
    +static const HTStreamClass HTPlain =
    +{
    +    "PlainPresenter",
    +    HTPlain_free,
    +    HTPlain_abort,
    +    HTPlain_put_character, HTPlain_put_string, HTPlain_write,
    +};
    +
    +/*		New object
    + *		----------
    + */
    +HTStream *HTPlainPresent(HTPresentation *pres GCC_UNUSED, HTParentAnchor *anchor,
    +			 HTStream *sink GCC_UNUSED)
    +{
    +
    +    HTStream *me = (HTStream *) malloc(sizeof(*me));
    +
    +    if (me == NULL)
    +	outofmem(__FILE__, "HTPlain_new");
    +
    +    me->isa = &HTPlain;
    +
    +    HTPlain_lastraw = -1;
    +
    +    me->U.utf_count = 0;
    +    me->U.utf_char = 0;
    +    me->U.utf_buf[0] = me->U.utf_buf[6] = me->U.utf_buf[7] = '\0';
    +    me->U.utf_buf_p = me->U.utf_buf;
    +    me->outUCLYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_HTEXT);
    +    me->inUCLYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER);
    +    HTPlain_getChartransInfo(me, anchor);
    +    UCSetTransParams(&me->T,
    +		     me->inUCLYhndl, me->inUCI,
    +		     me->outUCLYhndl,
    +		     HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT));
    +
    +    me->text = HText_new(anchor);
    +    HText_setStyle(me->text, LYstyles(HTML_XMP));
    +    HText_beginAppend(me->text);
    +
    +    return (HTStream *) me;
    +}
    diff --git a/WWW/Library/Implementation/HTPlain.h b/WWW/Library/Implementation/HTPlain.h
    new file mode 100644
    index 0000000..24fd669
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTPlain.h
    @@ -0,0 +1,21 @@
    +/*                  /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTPlain.html
    +                                    PLAIN TEXT OBJECT
    +                                             
    + */
    +#ifndef HTPLAIN_H
    +#define HTPLAIN_H
    +
    +#include 
    +#include 
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern HTStream *HTPlainPresent(HTPresentation *pres,
    +				    HTParentAnchor *anchor,
    +				    HTStream *sink);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTPLAIN_H */
    diff --git a/WWW/Library/Implementation/HTRules.c b/WWW/Library/Implementation/HTRules.c
    new file mode 100644
    index 0000000..91ac93d
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTRules.c
    @@ -0,0 +1,704 @@
    +/*
    + * $LynxId: HTRules.c,v 1.47 2017/07/02 19:45:22 tom Exp $
    + *
    + *	Configuration manager for Hypertext Daemon		HTRules.c
    + *	==========================================
    + *
    + *
    + * History:
    + *	 3 Jun 91	Written TBL
    + *	10 Aug 91	Authorisation added after Daniel Martin (pass, fail)
    + *			Rule order in file changed
    + *			Comments allowed with # on 1st char of rule line
    + *	17 Jun 92	Bug fix: pass and fail failed if didn't contain '*' TBL
    + *	 1 Sep 93	Bug fix: no memory check - Nathan Torkington
    + *			BYTE_ADDRESSING removed - Arthur Secret
    + *	11 Sep 93  MD	Changed %i into %d in debug printf.
    + *			VMS does not recognize %i.
    + *			Bug Fix: in case of PASS, only one parameter to printf.
    + *	19 Sep 93  AL	Added Access Authorization stuff.
    + *	 1 Nov 93  AL	Added htbin.
    + *	25 May 99  KW	Added redirect for lynx.
    + *
    + */
    +
    +#include 
    +
    +/* (c) CERN WorldWideWeb project 1990,91. See Copyright.html for details */
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +
    +#define LINE_LENGTH 256
    +
    +typedef struct _rule {
    +    struct _rule *next;
    +    HTRuleOp op;
    +    char *pattern;
    +    char *equiv;
    +    char *condition_op;		/* as strings - may be inefficient, */
    +    char *condition;		/* but this is not for a server - kw */
    +} rule;
    +
    +#ifndef NO_RULES
    +
    +#include 		/* for redirecting_url, indirectly HTPermitRedir - kw */
    +#include 	/* for LYUserSpecifiedURL - kw */
    +#include 		/* for LYscanFloat */
    +#include 		/* for LYFixCursesOn - kw */
    +#include 
    +
    +/*	Global variables
    + *	----------------
    + */
    +char *HTBinDir = NULL;		/* Physical /htbin directory path.      */
    +
    +				/* In future this should not be global. */
    +char *HTSearchScript = NULL;	/* Search script name.          */
    +
    +/*	Module-wide variables
    + *	---------------------
    + */
    +
    +static rule *rules = 0;		/* Pointer to first on list */
    +
    +#ifndef PUT_ON_HEAD
    +static rule *rule_tail = 0;	/* Pointer to last on list */
    +#endif
    +
    +/*	Add rule to the list					HTAddRule()
    + *	--------------------
    + *
    + *  On entry,
    + *	pattern		points to 0-terminated string containing a single "*"
    + *	equiv		points to the equivalent string with * for the
    + *			place where the text matched by * goes.
    + *  On exit,
    + *	returns		0 if success, -1 if error.
    + */
    +
    +int HTAddRule(HTRuleOp op, const char *pattern,
    +	      const char *equiv,
    +	      const char *cond_op,
    +	      const char *cond)
    +{				/* BYTE_ADDRESSING removed and memory check - AS - 1 Sep 93 */
    +    rule *temp;
    +    char *pPattern = NULL;
    +
    +    temp = typecalloc(rule);
    +    if (temp == NULL)
    +	outofmem(__FILE__, "HTAddRule");
    +
    +    if (equiv) {		/* Two operands */
    +	char *pEquiv = NULL;
    +
    +	StrAllocCopy(pEquiv, equiv);
    +	temp->equiv = pEquiv;
    +    } else {
    +	temp->equiv = 0;
    +    }
    +    if (cond_op) {
    +	StrAllocCopy(temp->condition_op, cond_op);
    +	StrAllocCopy(temp->condition, cond);
    +    }
    +    StrAllocCopy(pPattern, pattern);
    +    temp->pattern = pPattern;
    +    temp->op = op;
    +
    +    if (equiv) {
    +	CTRACE((tfp, "Rule: For `%s' op %d `%s'", pattern, (int) op, equiv));
    +    } else {
    +	CTRACE((tfp, "Rule: For `%s' op %d", pattern, (int) op));
    +    }
    +    if (cond_op) {
    +	CTRACE((tfp, "\t%s %s\n", cond_op, NONNULL(cond)));
    +    } else {
    +	CTRACE((tfp, "\n"));
    +    }
    +
    +    if (!rules) {
    +#ifdef LY_FIND_LEAKS
    +	atexit(HTClearRules);
    +#endif
    +    }
    +#ifdef PUT_ON_HEAD
    +    temp->next = rules;
    +    rules = temp;
    +#else
    +    temp->next = 0;
    +    if (rule_tail)
    +	rule_tail->next = temp;
    +    else
    +	rules = temp;
    +    rule_tail = temp;
    +#endif
    +
    +    return 0;
    +}
    +
    +/*	Clear all rules						HTClearRules()
    + *	---------------
    + *
    + * On exit,
    + *	There are no rules
    + *
    + * See also
    + *	HTAddRule()
    + */
    +void HTClearRules(void)
    +{
    +    while (rules) {
    +	rule *temp = rules;
    +
    +	rules = temp->next;
    +	FREE(temp->pattern);
    +	FREE(temp->equiv);
    +	FREE(temp->condition_op);
    +	FREE(temp->condition);
    +	FREE(temp);
    +    }
    +#ifndef PUT_ON_HEAD
    +    rule_tail = 0;
    +#endif
    +}
    +
    +static BOOL rule_cond_ok(rule * r)
    +{
    +    BOOL result;
    +
    +    if (!r->condition_op)
    +	return YES;
    +    if (strcmp(r->condition_op, "if") && strcmp(r->condition_op, "unless")) {
    +	CTRACE((tfp, "....... rule ignored, unrecognized `%s'!\n",
    +		r->condition_op));
    +	return NO;
    +    }
    +    if (!strcmp(r->condition, "redirected"))
    +	result = (BOOL) (redirection_attempts > 0);
    +    else if (!strcmp(r->condition, "userspec"))
    +	result = LYUserSpecifiedURL;
    +    else {
    +	CTRACE((tfp, "....... rule ignored, unrecognized `%s %s'!\n",
    +		r->condition_op, NONNULL(r->condition)));
    +	return NO;
    +    }
    +    if (!strcmp(r->condition_op, "if"))
    +	return result;
    +    else
    +	return (BOOL) (!result);
    +
    +}
    +
    +/*	Translate by rules					HTTranslate()
    + *	------------------
    + *
    + *	The most recently defined rules are applied first.
    + *
    + * On entry,
    + *	required	points to a string whose equivalent value is needed
    + * On exit,
    + *	returns		the address of the equivalent string allocated from
    + *			the heap which the CALLER MUST FREE. If no translation
    + *			occurred, then it is a copy of the original.
    + * NEW FEATURES:
    + *			When a "protect" or "defprot" rule is matched,
    + *			a call to HTAA_setCurrentProtection() or
    + *			HTAA_setDefaultProtection() is made to notify
    + *			the Access Authorization module that the file is
    + *			protected, and so it knows how to handle it.
    + *								-- AL
    + */
    +char *HTTranslate(const char *required)
    +{
    +    rule *r;
    +    char *current = NULL;
    +    char *msgtmp = NULL;
    +    const char *pMsg;
    +    int proxy_none_flag = 0;
    +    int permitredir_flag = 0;
    +
    +    StrAllocCopy(current, required);
    +
    +    HTAA_clearProtections();	/* Reset from previous call -- AL */
    +
    +    for (r = rules; r; r = r->next) {
    +	char *p = r->pattern;
    +	int m = 0;		/* Number of characters matched against wildcard */
    +	const char *q = current;
    +
    +	for (; *p && *q; p++, q++) {	/* Find first mismatch */
    +	    if (*p != *q)
    +		break;
    +	}
    +
    +	if (*p == '*') {	/* Match up to wildcard */
    +	    m = (int) strlen(q) - (int) strlen(p + 1);	/* Amount to match to wildcard */
    +	    if (m < 0)
    +		continue;	/* tail is too short to match */
    +	    if (0 != strcmp(q + m, p + 1))
    +		continue;	/* Tail mismatch */
    +	} else
    +	    /* Not wildcard */ if (*p != *q)
    +	    continue;		/* plain mismatch: go to next rule */
    +
    +	if (!rule_cond_ok(r))	/* check condition, next rule if false - kw */
    +	    continue;
    +
    +	switch (r->op) {	/* Perform operation */
    +
    +	case HT_DefProt:
    +	case HT_Protect:
    +#ifdef ACCESS_AUTH
    +	    {
    +		char *local_copy = NULL;
    +		char *p2;
    +		char *eff_ids = NULL;
    +		char *prot_file = NULL;
    +
    +		CTRACE((tfp, "HTRule: `%s' matched %s %s: `%s'\n",
    +			current,
    +			(r->op == HT_Protect ? "Protect" : "DefProt"),
    +			"rule, setup",
    +			(r->equiv ? r->equiv :
    +			 (r->op == HT_Protect ? "DEFAULT" : "NULL!!"))));
    +
    +		if (r->equiv) {
    +		    StrAllocCopy(local_copy, r->equiv);
    +		    p2 = local_copy;
    +		    prot_file = HTNextField(&p2);
    +		    eff_ids = HTNextField(&p2);
    +		}
    +
    +		if (r->op == HT_Protect)
    +		    HTAA_setCurrentProtection(current, prot_file, eff_ids);
    +		else
    +		    HTAA_setDefaultProtection(current, prot_file, eff_ids);
    +
    +		FREE(local_copy);
    +
    +		/* continue translating rules */
    +	    }
    +#endif /* ACCESS_AUTH */
    +	    break;
    +
    +	case HT_UserMsg:	/* Produce message immediately */
    +	    LYFixCursesOn("show rule message:");
    +	    HTUserMsg2((r->equiv ? r->equiv : "Rule: %s"), current);
    +	    break;
    +	case HT_InfoMsg:	/* Produce messages immediately */
    +	case HT_Progress:
    +	case HT_Alert:
    +	    LYFixCursesOn("show rule message:");	/* and fall through */
    +	    /* FALLTHRU */
    +	case HT_AlwaysAlert:
    +	    pMsg = r->equiv ? r->equiv :
    +		(r->op == HT_AlwaysAlert) ? "%s" : "Rule: %s";
    +	    if (StrChr(pMsg, '%')) {
    +		HTSprintf0(&msgtmp, pMsg, current);
    +		pMsg = msgtmp;
    +	    }
    +	    switch (r->op) {	/* Actually produce message */
    +	    case HT_InfoMsg:
    +		HTInfoMsg(pMsg);
    +		break;
    +	    case HT_Progress:
    +		HTProgress(pMsg);
    +		break;
    +	    case HT_Alert:
    +		HTAlert(pMsg);
    +		break;
    +	    case HT_AlwaysAlert:
    +		HTAlwaysAlert("Rule alert:", pMsg);
    +		break;
    +	    default:
    +		break;
    +	    }
    +	    FREE(msgtmp);
    +	    break;
    +
    +	case HT_PermitRedir:	/* Set special flag */
    +	    permitredir_flag = 1;
    +	    CTRACE((tfp, "HTRule: Mark for redirection permitted\n"));
    +	    break;
    +
    +	case HT_Pass:		/* Authorised */
    +	    if (!r->equiv) {
    +		if (proxy_none_flag) {
    +		    char *temp = NULL;
    +
    +		    StrAllocCopy(temp, "NoProxy=");
    +		    StrAllocCat(temp, current);
    +		    FREE(current);
    +		    current = temp;
    +		}
    +		CTRACE((tfp, "HTRule: Pass `%s'\n", current));
    +		return current;
    +	    }
    +	    /* FALLTHRU */
    +
    +	case HT_Map:
    +	case HT_Redirect:
    +	case HT_RedirectPerm:
    +	    if (*p == *q) {	/* End of both strings, no wildcard */
    +		CTRACE((tfp, "For `%s' using `%s'\n", current, r->equiv));
    +		StrAllocCopy(current, r->equiv);	/* use entire translation */
    +	    } else {
    +		char *ins = StrChr(r->equiv, '*');	/* Insertion point */
    +
    +		if (ins) {	/* Consistent rule!!! */
    +		    char *temp = NULL;
    +
    +		    HTSprintf0(&temp, "%.*s%.*s%s",
    +			       (int) (ins - r->equiv),
    +			       r->equiv,
    +			       m,
    +			       q,
    +			       ins + 1);
    +		    CTRACE((tfp, "For `%s' using `%s'\n",
    +			    current, temp));
    +		    FREE(current);
    +		    current = temp;	/* Use this */
    +
    +		} else {	/* No insertion point */
    +		    char *temp = NULL;
    +
    +		    StrAllocCopy(temp, r->equiv);
    +		    CTRACE((tfp, "For `%s' using `%s'\n",
    +			    current, temp));
    +		    FREE(current);
    +		    current = temp;	/* Use this */
    +		}		/* If no insertion point exists */
    +	    }
    +	    if (r->op == HT_Pass) {
    +		if (proxy_none_flag) {
    +		    char *temp = NULL;
    +
    +		    StrAllocCopy(temp, "NoProxy=");
    +		    StrAllocCat(temp, current);
    +		    FREE(current);
    +		    current = temp;
    +		}
    +		CTRACE((tfp, "HTRule: ...and pass `%s'\n",
    +			current));
    +		return current;
    +	    } else if (r->op == HT_Redirect) {
    +		CTRACE((tfp, "HTRule: ...and redirect to `%s'\n",
    +			current));
    +		redirecting_url = current;
    +		HTPermitRedir = (BOOL) (permitredir_flag == 1);
    +		return (char *) 0;
    +	    } else if (r->op == HT_RedirectPerm) {
    +		CTRACE((tfp, "HTRule: ...and redirect like 301 to `%s'\n",
    +			current));
    +		redirecting_url = current;
    +		permanent_redirection = TRUE;
    +		HTPermitRedir = (BOOL) (permitredir_flag == 1);
    +		return (char *) 0;
    +	    }
    +	    break;
    +
    +	case HT_UseProxy:
    +	    if (r->equiv && 0 == strcasecomp(r->equiv, "none")) {
    +		CTRACE((tfp, "For `%s' will not use proxy\n", current));
    +		proxy_none_flag = 1;
    +	    } else if (proxy_none_flag) {
    +		CTRACE((tfp, "For `%s' proxy server ignored: %s\n",
    +			current,
    +			NONNULL(r->equiv)));
    +	    } else {
    +		char *temp = NULL;
    +
    +		StrAllocCopy(temp, "Proxied=");
    +		StrAllocCat(temp, r->equiv);
    +		StrAllocCat(temp, current);
    +		CTRACE((tfp, "HTRule: proxy server found: %s\n",
    +			NONNULL(r->equiv)));
    +		FREE(current);
    +		return temp;
    +	    }
    +	    break;
    +
    +	case HT_Invalid:
    +	case HT_Fail:		/* Unauthorised */
    +	    CTRACE((tfp, "HTRule: *** FAIL `%s'\n", current));
    +	    FREE(current);
    +	    return (char *) 0;
    +	}			/* if tail matches ... switch operation */
    +
    +    }				/* loop over rules */
    +
    +    if (proxy_none_flag) {
    +	char *temp = NULL;
    +
    +	StrAllocCopy(temp, "NoProxy=");
    +	StrAllocCat(temp, current);
    +	FREE(current);
    +	return temp;
    +    }
    +
    +    return current;
    +}
    +
    +/*	Load one line of configuration
    + *	------------------------------
    + *
    + *	Call this, for example, to load a X resource with config info.
    + *
    + * returns	0 OK, < 0 syntax error.
    + */
    +int HTSetConfiguration(char *config)
    +{
    +    HTRuleOp op;
    +    char *line = NULL;
    +    char *pointer = NULL;
    +    char *word1;
    +    const char *word2;
    +    const char *word3;
    +    const char *cond_op = NULL;
    +    const char *cond = NULL;
    +    float quality, secs, secs_per_byte;
    +    long maxbytes;
    +    int status;
    +
    +    StrAllocCopy(line, config);
    +    if (line != NULL) {
    +	char *p = line;
    +
    +	/* Chop off comments */
    +	while ((p = StrChr(p, '#'))) {
    +	    if (p == line || isspace(UCH(*(p - 1)))) {
    +		*p = 0;
    +		break;
    +	    } else {
    +		p++;
    +	    }
    +	}
    +    }
    +    pointer = line;
    +    word1 = HTNextField(&pointer);
    +    if (!word1) {
    +	FREE(line);
    +	return 0;
    +    };				/* Comment only or blank */
    +
    +    word2 = HTNextField(&pointer);
    +
    +    if (0 == strcasecomp(word1, "defprot") ||
    +	0 == strcasecomp(word1, "protect"))
    +	word3 = pointer;	/* The rest of the line to be parsed by AA module */
    +    else
    +	word3 = HTNextField(&pointer);	/* Just the next word */
    +
    +    if (!word2) {
    +	fprintf(stderr, "HTRule: %s %s\n", RULE_NEEDS_DATA, line);
    +	FREE(line);
    +	return -2;		/*syntax error */
    +    }
    +
    +    if (0 == strcasecomp(word1, "suffix")) {
    +	char *encoding = HTNextField(&pointer);
    +
    +	status = 0;
    +	if (pointer)
    +	    status = LYscanFloat(pointer, &quality);
    +
    +	HTSetSuffix(word2, word3,
    +		    encoding ? encoding : "binary",
    +		    status >= 1 ? quality : (float) 1.0);
    +
    +    } else if (0 == strcasecomp(word1, "presentation")) {
    +	status = 0;
    +	if (pointer) {
    +	    const char *temp = pointer;
    +
    +	    if (LYscanFloat2(&temp, &quality)) {
    +		status = 1;
    +		if (LYscanFloat2(&temp, &secs)) {
    +		    status = 2;
    +		    if (LYscanFloat2(&temp, &secs_per_byte)) {
    +			status = 3;
    +			if (sscanf(temp, "%ld", &maxbytes)) {
    +			    status = 4;
    +			}
    +		    }
    +		}
    +	    }
    +	}
    +
    +	HTSetPresentation(word2, word3, NULL,
    +			  status >= 1 ? quality : 1.0,
    +			  status >= 2 ? secs : 0.0,
    +			  status >= 3 ? secs_per_byte : 0.0,
    +			  status >= 4 ? maxbytes : 0,
    +			  mediaCFG);
    +
    +    } else if (0 == strncasecomp(word1, "htbin", 5) ||
    +	       0 == strncasecomp(word1, "bindir", 6)) {
    +	StrAllocCopy(HTBinDir, word2);	/* Physical /htbin location */
    +
    +    } else if (0 == strncasecomp(word1, "search", 6)) {
    +	StrAllocCopy(HTSearchScript, word2);	/* Search script name */
    +
    +    } else {
    +	op = 0 == strcasecomp(word1, "map") ? HT_Map
    +	    : 0 == strcasecomp(word1, "pass") ? HT_Pass
    +	    : 0 == strcasecomp(word1, "fail") ? HT_Fail
    +	    : 0 == strcasecomp(word1, "redirect") ? HT_Redirect
    +	    : 0 == strncasecomp(word1, "redirectperm", 12) ? HT_RedirectPerm
    +	    : 0 == strcasecomp(word1, "redirecttemp") ? HT_Redirect
    +	    : 0 == strcasecomp(word1, "permitredirection") ? HT_PermitRedir
    +	    : 0 == strcasecomp(word1, "useproxy") ? HT_UseProxy
    +	    : 0 == strcasecomp(word1, "alert") ? HT_Alert
    +	    : 0 == strcasecomp(word1, "alwaysalert") ? HT_AlwaysAlert
    +	    : 0 == strcasecomp(word1, "progress") ? HT_Progress
    +	    : 0 == strcasecomp(word1, "usermsg") ? HT_UserMsg
    +	    : 0 == strcasecomp(word1, "infomsg") ? HT_InfoMsg
    +	    : 0 == strcasecomp(word1, "defprot") ? HT_DefProt
    +	    : 0 == strcasecomp(word1, "protect") ? HT_Protect
    +	    : HT_Invalid;
    +	if (op == HT_Invalid) {
    +	    fprintf(stderr, "HTRule: %s '%s'\n", RULE_INCORRECT, config);
    +	} else {
    +	    switch (op) {
    +	    case HT_Fail:	/* never a or other 2nd parameter */
    +	    case HT_PermitRedir:
    +		cond_op = word3;
    +		if (cond_op && *cond_op) {
    +		    word3 = NULL;
    +		    cond = HTNextField(&pointer);
    +		}
    +		break;
    +
    +	    case HT_Pass:	/* possibly a URL2 */
    +		if (word3 && (!strcasecomp(word3, "if") ||
    +			      !strcasecomp(word3, "unless"))) {
    +		    cond_op = word3;
    +		    word3 = NULL;
    +		    cond = HTNextField(&pointer);
    +		    break;
    +		}
    +		/* else fall through */
    +	    case HT_Map:	/* always a URL2 (or other 2nd parameter) */
    +	    case HT_Redirect:
    +	    case HT_RedirectPerm:
    +	    case HT_UseProxy:
    +		cond_op = HTNextField(&pointer);
    +		/* check for extra status word in "Redirect" */
    +		if (op == HT_Redirect && 0 == strcasecomp(word1, "redirect") &&
    +		    cond_op &&
    +		    strcasecomp(cond_op, "if") &&
    +		    strcasecomp(cond_op, "unless")) {
    +		    if (0 == strcmp(word2, "301") ||
    +			0 == strcasecomp(word2, "permanent")) {
    +			op = HT_RedirectPerm;
    +		    } else if (!(0 == strcmp(word2, "302") ||
    +				 0 == strcmp(word2, "303") ||
    +				 0 == strcasecomp(word2, "temp") ||
    +				 0 == strcasecomp(word2, "seeother"))) {
    +			CTRACE((tfp, "Rule: Ignoring `%s' in Redirect\n", word2));
    +		    }
    +		    word2 = word3;
    +		    word3 = cond_op;	/* cond_op isn't condition op after all */
    +		    cond_op = HTNextField(&pointer);
    +		}
    +		if (cond_op && *cond_op)
    +		    cond = HTNextField(&pointer);
    +		break;
    +
    +	    case HT_Progress:
    +	    case HT_InfoMsg:
    +	    case HT_UserMsg:
    +	    case HT_Alert:
    +	    case HT_AlwaysAlert:
    +		cond_op = HTNextField(&pointer);
    +		if (cond_op && *cond_op)
    +		    cond = HTNextField(&pointer);
    +		if (word3) {	/* Fix string with too may %s - kw */
    +		    const char *cp = word3;
    +		    char *cp1, *cp2;
    +
    +		    while ((cp1 = StrChr(cp, '%'))) {
    +			if (cp1[1] == '\0') {
    +			    *cp1 = '\0';
    +			    break;
    +			} else if (cp1[1] == '%') {
    +			    cp = cp1 + 2;
    +			    continue;
    +			} else
    +			    while ((cp2 = StrChr(cp1 + 2, '%'))) {
    +				if (cp2[1] == '\0') {
    +				    *cp2 = '\0';
    +				    break;
    +				} else if (cp2[1] == '%') {
    +				    cp1 = cp2;
    +				} else {
    +				    *cp2 = '?';		/* replace bad % */
    +				    cp1 = cp2;
    +				}
    +			    }
    +			break;
    +		    }
    +		}
    +		break;
    +
    +	    default:
    +		break;
    +	    }
    +	    if (cond_op && cond && *cond && !strcasecomp(cond_op, "unless")) {
    +		cond_op = "unless";
    +	    } else if (cond_op && cond && *cond &&
    +		       !strcasecomp(cond_op, "if")) {
    +		cond_op = "if";
    +	    } else if (cond_op || cond) {
    +		fprintf(stderr, "HTRule: %s '%s'\n", RULE_INCORRECT, config);
    +		FREE(line);	/* syntax error, condition is a mess - kw */
    +		return -2;	/* NB unrecognized cond passes here - kw */
    +	    }
    +	    if (cond && !strncasecomp(cond, "redirected", (int) strlen(cond))) {
    +		cond = "redirected";	/* recognized, canonical case - kw */
    +	    } else if (cond && strlen(cond) >= 8 &&
    +		       !strncasecomp(cond, "userspecified", (int) strlen(cond))) {
    +		cond = "userspec";	/* also allow abbreviation - kw */
    +	    }
    +	    HTAddRule(op, word2, word3, cond_op, cond);
    +	}
    +    }
    +    FREE(line);
    +    return 0;
    +}
    +
    +/*	Load the rules from a file				HTLoadRules()
    + *	--------------------------
    + *
    + * On entry,
    + *	Rules can be in any state
    + * On exit,
    + *	Any existing rules will have been kept.
    + *	Any new rules will have been loaded.
    + *	Returns		0 if no error, 0 if error!
    + *
    + * Bugs:
    + *	The strings may not contain spaces.
    + */
    +
    +int HTLoadRules(const char *filename)
    +{
    +    FILE *fp = fopen(filename, TXT_R);
    +    char line[LINE_LENGTH + 1];
    +
    +    if (!fp) {
    +	CTRACE((tfp, "HTRules: Can't open rules file %s\n", filename));
    +	return -1;		/* File open error */
    +    }
    +    for (;;) {
    +	if (!fgets(line, LINE_LENGTH + 1, fp))
    +	    break;		/* EOF or error */
    +	(void) HTSetConfiguration(line);
    +    }
    +    fclose(fp);
    +    return 0;			/* No error or syntax errors ignored */
    +}
    +
    +#endif /* NO_RULES */
    diff --git a/WWW/Library/Implementation/HTRules.h b/WWW/Library/Implementation/HTRules.h
    new file mode 100644
    index 0000000..9bb8593
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTRules.h
    @@ -0,0 +1,169 @@
    +/*                                             Configuration Manager for libwww
    + *                            CONFIGURATION MANAGER
    + *
    + * Author Tim Berners-Lee/CERN.  Public domain.  Please mail changes to
    + * timbl@info.cern.ch.
    + *
    + * The configuration information loaded includes tables (file suffixes,
    + * presentation methods) in other modules.  The most likely routines needed by
    + * developers will be:
    + *
    + * HTSetConfiguration	to load configuration information.
    + *
    + * HTLoadRules		to load a whole file of configuration information
    + *
    + * HTTranslate		to translate a URL using the rule table.
    + *
    + */
    +#ifndef HTRULE_H
    +#define HTRULE_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    typedef enum {
    +	HT_Invalid,
    +	HT_Map,
    +	HT_Pass,
    +	HT_Fail,
    +	HT_DefProt,
    +	HT_Protect,
    +	HT_Progress,
    +	HT_InfoMsg,
    +	HT_UserMsg,
    +	HT_Alert,
    +	HT_AlwaysAlert,
    +	HT_Redirect,
    +	HT_RedirectPerm,
    +	HT_PermitRedir,
    +	HT_UseProxy
    +    } HTRuleOp;
    +
    +#ifndef NO_RULES
    +
    +/*
    +
    +Server Side Script Execution
    +
    +   If a URL starts with /htbin/ it is understood to mean a script execution request on
    +   server.  This feature needs to be turned on by setting HTBinDir by the htbin rule.
    +   Index searching is enabled by setting HTSearchScript into the name of script in BinDir
    +   doing the actual search by search rule (BinDir must also be set in this case, of
    +   course).
    +
    + */
    +
    +    extern char *HTBinDir;	/* Physical /htbin location */
    +    extern char *HTSearchScript;	/* Search script name */
    +
    +/*
    +
    +HTAddRule:  Add rule to the list
    +
    +  ON ENTRY,
    +
    +  pattern                points to 0-terminated string containing a single "*"
    +
    +  equiv                  points to the equivalent string with * for the place where the
    +                         text matched by * goes; or to other 2nd parameter
    +                         meaning depends on op).			 
    +
    +  cond_op,               additional condition for applying rule; cond_op should
    +  cond                   be either NULL (no additional condition), or one of
    +                         the strings "if" or "unless"; if cond_op is not NULL,
    +                         cond should point to a recognized condition keyword
    +                         (as a string) such as "userspec", "redirected".
    +
    +  ON EXIT,
    +
    +  returns                0 if success, -1 if error.
    +
    +   Note that if BYTE_ADDRESSING is set, the three blocks required are allocated and
    +   deallocated as one.  This will save time and storage, when malloc's allocation units are
    +   large.
    +
    + */
    +    extern int HTAddRule(HTRuleOp op, const char *pattern,
    +			 const char *equiv,
    +			 const char *cond_op,
    +			 const char *cond);
    +
    +/*
    +
    +HTClearRules: Clear all rules
    +
    +  ON EXIT,
    +
    +  Rule file               There are no rules
    +
    + */
    +
    +    extern void HTClearRules(void);
    +
    +/*
    +
    +HTTranslate: Translate by rules
    +
    + */
    +
    +/*
    +
    +  ON ENTRY,
    +
    +  required                points to a string whose equivalent value is needed
    +
    +  ON EXIT,
    +
    +  returns                 the address of the equivalent string allocated from the heap
    +                         which the CALLER MUST FREE. If no translation occurred, then it is
    +                         a copy of the original.
    +
    + */
    +    extern char *HTTranslate(const char *required);
    +
    +/*
    +
    +HTSetConfiguration:  Load one line of configuration information
    +
    +  ON ENTRY,
    +
    +  config                  is a string in the syntax of a rule file line.
    +
    +   This routine may be used for loading configuration information from sources other than
    +   the  rule file, for example INI files for X resources.
    +
    + */
    +    extern int HTSetConfiguration(char *config);
    +
    +/*
    +
    +HtLoadRules:  Load the rules from a file
    +
    +  ON ENTRY,
    +
    +  Rule table              Rules can be in any state
    +
    +  ON EXIT,
    +
    +  Rule table              Any existing rules will have been kept.  Any new rules will have
    +                         been loaded on top, so as to be tried first.
    +
    +  Returns                 0 if no error.
    +
    + */
    +
    +    extern int HTLoadRules(const char *filename);
    +
    +/*
    +
    + */
    +
    +#endif				/* NO_RULES */
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTRULE_H */
    diff --git a/WWW/Library/Implementation/HTStream.h b/WWW/Library/Implementation/HTStream.h
    new file mode 100644
    index 0000000..a753266
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTStream.h
    @@ -0,0 +1,69 @@
    +/*
    + * $LynxId: HTStream.h,v 1.16 2011/06/11 12:08:40 tom Exp $
    + *
    + *                                                      The Stream class definition -- libwww
    +                                 STREAM OBJECT DEFINITION
    +
    +   A Stream object is something which accepts a stream of text.
    +
    +   The creation methods will vary on the type of Stream Object.  All creation
    +   methods return a pointer to the stream type below.
    +
    +   As you can see, but the methods used to write to the stream and close it are
    +   pointed to be the object itself.
    +
    + */
    +#ifndef HTSTREAM_H
    +#define HTSTREAM_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    typedef struct _HTStream HTStream;
    +
    +/*
    +
    +   These are the common methods of all streams.  They should be
    +   self-explanatory.
    +
    + */
    +    typedef struct _HTStreamClass {
    +
    +	const char *name;	/* Just for diagnostics */
    +
    +	void (*_free) (HTStream *me);
    +
    +	void (*_abort) (HTStream *me, HTError e);
    +
    +	void (*put_character) (HTStream *me, int ch);
    +
    +	void (*put_string) (HTStream *me, const char *str);
    +
    +	void (*put_block) (HTStream *me, const char *str, int len);
    +
    +    } HTStreamClass;
    +
    +#ifndef HTSTREAM_INTERNAL
    +    struct _HTStream {
    +	HTStreamClass *isa;
    +    };
    +#endif
    +/*
    +
    +  Generic Error Stream
    +
    +   The Error stream simply signals an error on all output methods.
    +   This can be used to stop a stream as soon as data arrives, for
    +   example from the network.
    +
    + */
    +    extern HTStream *HTErrorStream(void);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTSTREAM_H */
    diff --git a/WWW/Library/Implementation/HTString.c b/WWW/Library/Implementation/HTString.c
    new file mode 100644
    index 0000000..3cb8358
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTString.c
    @@ -0,0 +1,1464 @@
    +/*
    + * $LynxId: HTString.c,v 1.82 2022/03/12 12:19:10 Gisle.Vanem Exp $
    + *
    + *	Case-independent string comparison		HTString.c
    + *
    + *	Original version came with listserv implementation.
    + *	Version TBL Oct 91 replaces one which modified the strings.
    + *	02-Dec-91 (JFG) Added stralloccopy and stralloccat
    + *	23 Jan 92 (TBL) Changed strallocc* to 8 char HTSAC* for VM and suchlike
    + *	 6 Oct 92 (TBL) Moved WWW_TraceFlag in here to be in library
    + *	15 Nov 98 (TD)  Added HTSprintf.
    + */
    +
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef USE_IGNORE_RC
    +int ignore_unused;
    +#endif
    +
    +#ifndef NO_LYNX_TRACE
    +BOOLEAN WWW_TraceFlag = 0;	/* Global trace flag for ALL W3 code */
    +int WWW_TraceMask = 0;		/* Global trace flag for ALL W3 code */
    +#endif
    +
    +#ifdef _WINDOWS
    +#undef VC
    +#define VC "2.14FM"
    +#endif
    +
    +#ifndef VC
    +#define VC "2.14"
    +#endif /* !VC */
    +
    +const char *HTLibraryVersion = VC;	/* String for help screen etc */
    +
    +/*
    + *     strcasecomp8 is a variant of strcasecomp (below)
    + *     ------------		    -----------
    + *     but uses 8bit upper/lower case information
    + *     from the current display charset.
    + *     It returns 0 if exact match.
    + */
    +int strcasecomp8(const char *a,
    +		 const char *b)
    +{
    +    const char *p = a;
    +    const char *q = b;
    +
    +    for (; *p && *q; p++, q++) {
    +	int diff = UPPER8(*p, *q);
    +
    +	if (diff)
    +	    return diff;
    +    }
    +    if (*p)
    +	return 1;		/* p was longer than q */
    +    if (*q)
    +	return -1;		/* p was shorter than q */
    +    return 0;			/* Exact match */
    +}
    +
    +/*
    + *     strncasecomp8 is a variant of strncasecomp (below)
    + *     -------------		     ------------
    + *     but uses 8bit upper/lower case information
    + *     from the current display charset.
    + *     It returns 0 if exact match.
    + */
    +int strncasecomp8(const char *a,
    +		  const char *b,
    +		  int n)
    +{
    +    const char *p = a;
    +    const char *q = b;
    +
    +    for (;; p++, q++) {
    +	int diff;
    +
    +	if (p == (a + n))
    +	    return 0;		/*   Match up to n characters */
    +	if (!(*p && *q))
    +	    return (*p - *q);
    +	diff = UPPER8(*p, *q);
    +	if (diff)
    +	    return diff;
    +    }
    +    /*NOTREACHED */
    +}
    +
    +#ifndef VM			/* VM has these already it seems */
    +/*	Strings of any length
    + *	---------------------
    + */
    +int strcasecomp(const char *a,
    +		const char *b)
    +{
    +    const char *p = a;
    +    const char *q = b;
    +
    +    for (; *p && *q; p++, q++) {
    +	int diff = TOLOWER(*p) - TOLOWER(*q);
    +
    +	if (diff)
    +	    return diff;
    +    }
    +    if (*p)
    +	return 1;		/* p was longer than q */
    +    if (*q)
    +	return -1;		/* p was shorter than q */
    +    return 0;			/* Exact match */
    +}
    +
    +/*	With count limit
    + *	----------------
    + */
    +int strncasecomp(const char *a,
    +		 const char *b,
    +		 int n)
    +{
    +    const char *p = a;
    +    const char *q = b;
    +
    +    for (;; p++, q++) {
    +	int diff;
    +
    +	if (p == (a + n))
    +	    return 0;		/*   Match up to n characters */
    +	if (!(*p && *q))
    +	    return (*p - *q);
    +	diff = TOLOWER(*p) - TOLOWER(*q);
    +	if (diff)
    +	    return diff;
    +    }
    +    /*NOTREACHED */
    +}
    +#endif /* VM */
    +
    +#define end_component(p) (*(p) == '.' || *(p) == '\0')
    +
    +#ifdef DEBUG_ASTERISK
    +#define SHOW_ASTERISK CTRACE
    +#else
    +#define SHOW_ASTERISK(p)	/* nothing */
    +#endif
    +
    +#define SHOW_ASTERISK_NUM(a,b,c)  \
    +	SHOW_ASTERISK((tfp, "test @%d, '%s' vs '%s' (%d)\n", __LINE__, a,b,c))
    +
    +#define SHOW_ASTERISK_TXT(a,b,c)  \
    +	SHOW_ASTERISK((tfp, "test @%d, '%s' vs '%s' %s\n", __LINE__, a,b,c))
    +
    +/*
    + * Compare names as described in RFC 2818: ignore case, allow wildcards. 
    + * Return zero on a match, nonzero on mismatch -TD
    + *
    + * From RFC 2818:
    + * Names may contain the wildcard character * which is considered to match any
    + * single domain name component or component fragment.  E.g., *.a.com matches
    + * foo.a.com but not bar.foo.a.com.  f*.com matches foo.com but not bar.com.
    + */
    +int strcasecomp_asterisk(const char *a, const char *b)
    +{
    +    const char *p;
    +    int result = 0;
    +    int done = FALSE;
    +
    +    while (!result && !done) {
    +	SHOW_ASTERISK_TXT(a, b, "main");
    +	if (*a == '*') {
    +	    p = b;
    +	    for (;;) {
    +		SHOW_ASTERISK_TXT(a, p, "loop");
    +		if (end_component(p)) {
    +		    if (end_component(a + 1)) {
    +			b = p - 1;
    +			result = 0;
    +		    } else {
    +			result = 1;
    +		    }
    +		    break;
    +		} else if (strcasecomp_asterisk(a + 1, p)) {
    +		    ++p;
    +		} else {
    +		    b = p - 1;
    +		    result = 0;	/* found a match starting at 'p' */
    +		    done = TRUE;
    +		    break;
    +		}
    +	    }
    +	    SHOW_ASTERISK_NUM(a, b, result);
    +	} else if (*b == '*') {
    +	    result = strcasecomp_asterisk(b, a);
    +	    SHOW_ASTERISK_NUM(a, b, result);
    +	    done = (result == 0);
    +	} else if (*a == '\0' || *b == '\0') {
    +	    result = (*a != *b);
    +	    SHOW_ASTERISK_NUM(a, b, result);
    +	    break;
    +	} else if (TOLOWER(UCH(*a)) != TOLOWER(UCH(*b))) {
    +	    result = 1;
    +	    SHOW_ASTERISK_NUM(a, b, result);
    +	    break;
    +	}
    +	++a;
    +	++b;
    +    }
    +    return result;
    +}
    +
    +#ifdef DEBUG_ASTERISK
    +void mismatch_asterisk(void)
    +{
    +    /* *INDENT-OFF* */
    +    static struct {
    +	const char *a;
    +	const char *b;
    +	int	    code;
    +    } table[] = {
    +	{ "foo.bar",	 "*.*",	      0 },
    +	{ "foo.bar",	 "*.b*",      0 },
    +	{ "foo.bar",	 "*.ba*",     0 },
    +	{ "foo.bar",	 "*.bar*",    0 },
    +	{ "foo.bar",	 "*.*bar*",   0 },
    +	{ "foo.bar",	 "*.*.",      1 },
    +	{ "foo.bar",	 "fo*.b*",    0 },
    +	{ "*oo.bar",	 "fo*.b*",    0 },
    +	{ "*oo.bar.com", "fo*.b*",    1 },
    +	{ "*oo.bar.com", "fo*.b*m",   1 },
    +	{ "*oo.bar.com", "fo*.b*.c*", 0 },
    +    };
    +    /* *INDENT-ON* */
    +
    +    unsigned n;
    +    int code;
    +
    +    CTRACE((tfp, "mismatch_asterisk testing\n"));
    +    for (n = 0; n < TABLESIZE(table); ++n) {
    +	CTRACE((tfp, "-------%d\n", n));
    +	code = strcasecomp_asterisk(table[n].a, table[n].b);
    +	if (code != table[n].code) {
    +	    CTRACE((tfp, "mismatch_asterisk '%s' '%s' got %d, want %d\n",
    +		    table[n].a, table[n].b, code, table[n].code));
    +	}
    +    }
    +}
    +#endif
    +
    +#ifdef NOT_ASCII
    +
    +/*	Case-insensitive with ASCII collating sequence
    + *	----------------
    + */
    +int AS_casecomp(const char *p,
    +		const char *q)
    +{
    +    int diff;
    +
    +    for (;; p++, q++) {
    +	if (!(*p && *q))
    +	    return (UCH(*p) - UCH(*q));
    +	diff = TOASCII(TOLOWER(*p))
    +	    - TOASCII(TOLOWER(*q));
    +	if (diff)
    +	    return diff;
    +    }
    +    /*NOTREACHED */
    +}
    +
    +/*	With count limit and ASCII collating sequence
    + *	----------------
    + *	AS_cmp uses n == -1 to compare indefinite length.
    + */
    +int AS_ncmp(const char *p,
    +	    const char *q,
    +	    unsigned int n)
    +{
    +    const char *a = p;
    +    int diff;
    +
    +    for (; (unsigned) (p - a) < n; p++, q++) {
    +	if (!(*p && *q))
    +	    return (UCH(*p) - UCH(*q));
    +	diff = TOASCII(*p)
    +	    - TOASCII(*q);
    +	if (diff)
    +	    return diff;
    +    }
    +    return 0;			/*   Match up to n characters */
    +}
    +#endif /* NOT_ASCII */
    +
    +/*	Allocate a new copy of a string, and returns it
    +*/
    +char *HTSACopy(char **dest,
    +	       const char *src)
    +{
    +    if (src != 0) {
    +	if (src != *dest) {
    +	    size_t size = strlen(src) + 1;
    +
    +	    FREE(*dest);
    +	    *dest = (char *) malloc(size);
    +	    if (*dest == NULL)
    +		outofmem(__FILE__, "HTSACopy");
    +	    MemCpy(*dest, src, size);
    +	}
    +    } else {
    +	FREE(*dest);
    +    }
    +    return *dest;
    +}
    +
    +/*	String Allocate and Concatenate
    +*/
    +char *HTSACat(char **dest,
    +	      const char *src)
    +{
    +    if (src && *src && (src != *dest)) {
    +	if (*dest) {
    +	    size_t length = strlen(*dest);
    +
    +	    *dest = (char *) realloc(*dest, length + strlen(src) + 1);
    +	    if (*dest == NULL)
    +		outofmem(__FILE__, "HTSACat");
    +	    strcpy(*dest + length, src);
    +	} else {
    +	    *dest = (char *) malloc(strlen(src) + 1);
    +	    if (*dest == NULL)
    +		outofmem(__FILE__, "HTSACat");
    +	    strcpy(*dest, src);
    +	}
    +    }
    +    return *dest;
    +}
    +
    +/* optimized for heavily realloc'd strings, store length inside */
    +
    +#define EXTRA_TYPE size_t	/* type we use for length */
    +#define EXTRA_SIZE sizeof(void *)	/* alignment >= sizeof(EXTRA_TYPE) */
    +
    +void HTSAFree_extra(char *s)
    +{
    +    free(s - EXTRA_SIZE);
    +}
    +
    +/* never shrink */
    +char *HTSACopy_extra(char **dest,
    +		     const char *src)
    +{
    +    if (src != 0) {
    +	size_t srcsize = strlen(src) + 1;
    +	EXTRA_TYPE size = 0;
    +
    +	if (*dest != 0) {
    +	    size = *(EXTRA_TYPE *) (void *) ((*dest) - EXTRA_SIZE);
    +	}
    +	if ((*dest == 0) || (size < srcsize)) {
    +	    FREE_extra(*dest);
    +	    size = srcsize * 2;	/* x2 step */
    +	    *dest = (char *) malloc(size + EXTRA_SIZE);
    +	    if (*dest == NULL)
    +		outofmem(__FILE__, "HTSACopy_extra");
    +	    *(EXTRA_TYPE *) (void *) (*dest) = size;
    +	    *dest += EXTRA_SIZE;
    +	}
    +	MemCpy(*dest, src, srcsize);
    +    } else {
    +	Clear_extra(*dest);
    +    }
    +    return *dest;
    +}
    +
    +/*	Find next Field
    + *	---------------
    + *
    + * On entry,
    + *	*pstr	points to a string containing white space separated
    + *		field, optionlly quoted.
    + *
    + * On exit,
    + *	*pstr	has been moved to the first delimiter past the
    + *		field
    + *		THE STRING HAS BEEN MUTILATED by a 0 terminator
    + *
    + *	returns a pointer to the first field
    + */
    +char *HTNextField(char **pstr)
    +{
    +    char *p = *pstr;
    +    char *start = NULL;		/* start of field */
    +
    +    if (p != NULL) {
    +	while (*p && WHITE(*p))
    +	    p++;		/* Strip white space */
    +	if (!*p) {
    +	    *pstr = p;
    +	} else {
    +	    if (*p == '"') {	/* quoted field */
    +		p++;
    +		start = p;
    +		for (; *p && *p != '"'; p++) {
    +		    if (*p == '\\' && p[1])
    +			p++;	/* Skip escaped chars */
    +		}
    +	    } else {
    +		start = p;
    +		while (*p && !WHITE(*p))
    +		    p++;	/* Skip first field */
    +	    }
    +	    if (*p)
    +		*p++ = '\0';
    +	    *pstr = p;
    +	}
    +    }
    +    return start;
    +}
    +
    +/*	Find next Token
    + *	---------------
    + *	Finds the next token in a string
    + *	On entry,
    + *	*pstr	points to a string to be parsed.
    + *	delims	lists characters to be recognized as delimiters.
    + *		If NULL, default is white space "," ";" or "=".
    + *		The word can optionally be quoted or enclosed with
    + *		chars from bracks.
    + *		Comments surrounded by '(' ')' are filtered out
    + *		unless they are specifically requested by including
    + *		' ' or '(' in delims or bracks.
    + *	bracks	lists bracketing chars.  Some are recognized as
    + *		special, for those give the opening char.
    + *		If NULL, defaults to <"> and "<" ">".
    + *	found	points to location to fill with the ending delimiter
    + *		found, or is NULL.
    + *
    + *	On exit,
    + *	*pstr	has been moved to the first delimiter past the
    + *		field
    + *		THE STRING HAS BEEN MUTILATED by a 0 terminator
    + *	found	points to the delimiter found unless it was NULL.
    + *	Returns a pointer to the first word or NULL on error
    + */
    +char *HTNextTok(char **pstr,
    +		const char *delims,
    +		const char *bracks,
    +		char *found)
    +{
    +    char *p = *pstr;
    +    char *start = NULL;
    +    BOOL get_blanks, skip_comments;
    +    BOOL get_comments;
    +    BOOL get_closing_char_too = FALSE;
    +    char closer;
    +
    +    if (isEmpty(pstr))
    +	return NULL;
    +    if (!delims)
    +	delims = " ;,=";
    +    if (!bracks)
    +	bracks = "<\"";
    +
    +    get_blanks = (BOOL) (!StrChr(delims, ' ') && !StrChr(bracks, ' '));
    +    get_comments = (BOOL) (StrChr(bracks, '(') != NULL);
    +    skip_comments = (BOOL) (!get_comments && !StrChr(delims, '(') && !get_blanks);
    +#define skipWHITE(c) (!get_blanks && WHITE(c))
    +
    +    while (*p && skipWHITE(*p))
    +	p++;			/* Strip white space */
    +    if (!*p) {
    +	*pstr = p;
    +	if (found)
    +	    *found = '\0';
    +	return NULL;		/* No first field */
    +    }
    +    while (1) {
    +	/* Strip white space and other delimiters */
    +	while (*p && (skipWHITE(*p) || StrChr(delims, *p)))
    +	    p++;
    +	if (!*p) {
    +	    *pstr = p;
    +	    if (found)
    +		*found = *(p - 1);
    +	    return NULL;	/* No field */
    +	}
    +
    +	if (*p == '(' && (skip_comments || get_comments)) {	/* Comment */
    +	    int comment_level = 0;
    +
    +	    if (get_comments && !start)
    +		start = p + 1;
    +	    for (; *p && (*p != ')' || --comment_level > 0); p++) {
    +		if (*p == '(')
    +		    comment_level++;
    +		else if (*p == '"') {	/* quoted field within Comment */
    +		    for (p++; *p && *p != '"'; p++)
    +			if (*p == '\\' && *(p + 1))
    +			    p++;	/* Skip escaped chars */
    +		    if (!*p)
    +			break;	/* (invalid) end of string found, leave */
    +		}
    +		if (*p == '\\' && *(p + 1))
    +		    p++;	/* Skip escaped chars */
    +	    }
    +	    if (get_comments)
    +		break;
    +	    if (*p)
    +		p++;
    +	    if (get_closing_char_too) {
    +		if (!*p || (!StrChr(bracks, *p) && StrChr(delims, *p))) {
    +		    break;
    +		} else
    +		    get_closing_char_too = (BOOL) (StrChr(bracks, *p) != NULL);
    +	    }
    +	} else if (StrChr(bracks, *p)) {	/* quoted or bracketed field */
    +	    switch (*p) {
    +	    case '<':
    +		closer = '>';
    +		break;
    +	    case '[':
    +		closer = ']';
    +		break;
    +	    case '{':
    +		closer = '}';
    +		break;
    +	    case ':':
    +		closer = ';';
    +		break;
    +	    default:
    +		closer = *p;
    +	    }
    +	    if (!start)
    +		start = ++p;
    +	    for (; *p && *p != closer; p++)
    +		if (*p == '\\' && *(p + 1))
    +		    p++;	/* Skip escaped chars */
    +	    if (get_closing_char_too) {
    +		p++;
    +		if (!*p || (!StrChr(bracks, *p) && StrChr(delims, *p))) {
    +		    break;
    +		} else
    +		    get_closing_char_too = (BOOL) (StrChr(bracks, *p) != NULL);
    +	    } else
    +		break;		/* kr95-10-9: needs to stop here */
    +	} else {		/* Spool field */
    +	    if (!start)
    +		start = p;
    +	    while (*p && !skipWHITE(*p) && !StrChr(bracks, *p) &&
    +		   !StrChr(delims, *p))
    +		p++;
    +	    if (*p && StrChr(bracks, *p)) {
    +		get_closing_char_too = TRUE;
    +	    } else {
    +		if (*p == '(' && skip_comments) {
    +		    *pstr = p;
    +		    HTNextTok(pstr, NULL, "(", found);	/*      Advance pstr */
    +		    *p = '\0';
    +		    if (*pstr && **pstr)
    +			(*pstr)++;
    +		    return start;
    +		}
    +		break;		/* Got it */
    +	    }
    +	}
    +    }
    +    if (found)
    +	*found = *p;
    +
    +    if (*p)
    +	*p++ = '\0';
    +    *pstr = p;
    +    return start;
    +}
    +
    +static char *HTAlloc(char *ptr, size_t length)
    +{
    +    if (ptr != 0)
    +	ptr = (char *) realloc(ptr, length);
    +    else
    +	ptr = (char *) malloc(length);
    +    if (ptr == 0)
    +	outofmem(__FILE__, "HTAlloc");
    +    return ptr;
    +}
    +
    +/*
    + * If SAVE_TIME_NOT_SPACE is defined, StrAllocVsprintf will hang on to
    + * its temporary string buffers instead of allocating and freeing them
    + * in each invocation.  They only grow and never shrink, and won't be
    + * cleaned up on exit. - kw
    + */
    +#if defined(_REENTRANT) || defined(_THREAD_SAFE) || defined(LY_FIND_LEAKS)
    +#undef SAVE_TIME_NOT_SPACE
    +#endif
    +
    +/*
    + * Replacement for sprintf, allocates buffer on the fly according to what's
    + * needed for its arguments.  Unlike sprintf, this always concatenates to the
    + * destination buffer, so we do not have to provide both flavors.
    + */
    +typedef enum {
    +    Flags,
    +    Width,
    +    Prec,
    +    Type,
    +    Format
    +} PRINTF;
    +
    +#define VA_INTGR(type) ival = (int)    va_arg((*ap), type)
    +#define VA_FLOAT(type) fval = (double) va_arg((*ap), type)
    +#define VA_POINT(type) pval = (char *) va_arg((*ap), type)
    +
    +#define NUM_WIDTH 10		/* allow for width substituted for "*" in "%*s" */
    +		/* also number of chars assumed to be needed in addition
    +		   to a given precision in floating point formats */
    +
    +#define GROW_EXPR(n) (((n) * 3) / 2)
    +#define GROW_SIZE 256
    +
    +PUBLIC_IF_FIND_LEAKS char *StrAllocVsprintf(char **pstr,
    +					    size_t dst_len,
    +					    const char *fmt,
    +					    va_list *ap)
    +{
    +#ifdef HAVE_VASPRINTF
    +    /*
    +     * Use vasprintf() if we have it, since it is simplest.
    +     */
    +    char *result = 0;
    +    char *temp = 0;
    +
    +    /* discard old destination if no length was given */
    +    if (pstr && !dst_len) {
    +	if (*pstr)
    +	    FREE(*pstr);
    +    }
    +
    +    if (vasprintf(&temp, fmt, *ap) >= 0) {
    +	if (dst_len != 0) {
    +	    size_t src_len = strlen(temp);
    +	    size_t new_len = dst_len + src_len + 1;
    +
    +	    result = HTAlloc(pstr ? *pstr : 0, new_len);
    +	    if (result != 0) {
    +		strcpy(result + dst_len, temp);
    +	    }
    +	    (free) (temp);
    +	} else {
    +	    result = temp;
    +	    mark_malloced(temp, strlen(temp));
    +	}
    +    }
    +
    +    if (pstr != 0)
    +	*pstr = result;
    +
    +    return result;
    +#else /* !HAVE_VASPRINTF */
    +    /*
    +     * If vasprintf() is not available, this works - but does not implement
    +     * the POSIX '$' formatting character which may be used in some of the
    +     * ".po" files.
    +     */
    +#ifdef SAVE_TIME_NOT_SPACE
    +    static size_t tmp_len = 0;
    +    static size_t fmt_len = 0;
    +    static char *tmp_ptr = NULL;
    +    static char *fmt_ptr = NULL;
    +
    +#else
    +    size_t tmp_len = GROW_SIZE;
    +    char *tmp_ptr = 0;
    +    char *fmt_ptr;
    +#endif /* SAVE_TIME_NOT_SPACE */
    +    size_t have, need;
    +    char *dst_ptr = pstr ? *pstr : NULL;
    +    const char *format = fmt;
    +
    +    if (isEmpty(fmt))
    +	return 0;
    +
    +    need = strlen(fmt) + 1;
    +#ifdef SAVE_TIME_NOT_SPACE
    +    if (!fmt_ptr || fmt_len < need * NUM_WIDTH) {
    +	fmt_ptr = HTAlloc(fmt_ptr, fmt_len = need * NUM_WIDTH);
    +    }
    +    if (!tmp_ptr || tmp_len < GROW_SIZE) {
    +	tmp_ptr = HTAlloc(tmp_ptr, tmp_len = GROW_SIZE);
    +    }
    +#else
    +    if ((fmt_ptr = malloc(need * NUM_WIDTH)) == 0
    +	|| (tmp_ptr = malloc(tmp_len)) == 0) {
    +	outofmem(__FILE__, "StrAllocVsprintf");
    +    }
    +#endif /* SAVE_TIME_NOT_SPACE */
    +
    +    if (dst_ptr == 0) {
    +	dst_ptr = HTAlloc(dst_ptr, have = GROW_SIZE + need);
    +    } else {
    +	have = strlen(dst_ptr) + 1;
    +	need += dst_len;
    +	if (have < need)
    +	    dst_ptr = HTAlloc(dst_ptr, have = GROW_SIZE + need);
    +    }
    +
    +    while (*fmt != '\0') {
    +	if (*fmt == '%') {
    +	    static char dummy[] = "";
    +	    PRINTF state = Flags;
    +	    char *pval = dummy;	/* avoid const-cast */
    +	    double fval = 0.0;
    +	    int done = FALSE;
    +	    int ival = 0;
    +	    int prec = -1;
    +	    int type = 0;
    +	    int used = 0;
    +	    int width = -1;
    +	    size_t f = 0;
    +
    +	    fmt_ptr[f++] = *fmt;
    +	    while (*++fmt != '\0' && !done) {
    +		fmt_ptr[f++] = *fmt;
    +
    +		if (isdigit(UCH(*fmt))) {
    +		    int num = *fmt - '0';
    +
    +		    if (state == Flags && num != 0)
    +			state = Width;
    +		    if (state == Width) {
    +			if (width < 0)
    +			    width = 0;
    +			width = (width * 10) + num;
    +		    } else if (state == Prec) {
    +			if (prec < 0)
    +			    prec = 0;
    +			prec = (prec * 10) + num;
    +		    }
    +		} else if (*fmt == '*') {
    +		    VA_INTGR(int);
    +
    +		    if (state == Flags)
    +			state = Width;
    +		    if (state == Width) {
    +			width = ival;
    +		    } else if (state == Prec) {
    +			prec = ival;
    +		    }
    +		    sprintf(&fmt_ptr[--f], "%d", ival);
    +		    f = strlen(fmt_ptr);
    +		} else if (isalpha(UCH(*fmt))) {
    +		    done = TRUE;
    +		    switch (*fmt) {
    +		    case 'Z':	/* FALLTHRU */
    +		    case 'h':	/* FALLTHRU */
    +		    case 'l':	/* FALLTHRU */
    +		    case 'L':	/* FALLTHRU */
    +			done = FALSE;
    +			type = *fmt;
    +			break;
    +		    case 'o':	/* FALLTHRU */
    +		    case 'i':	/* FALLTHRU */
    +		    case 'd':	/* FALLTHRU */
    +		    case 'u':	/* FALLTHRU */
    +		    case 'x':	/* FALLTHRU */
    +		    case 'X':	/* FALLTHRU */
    +			if (type == 'l')
    +			    VA_INTGR(long);
    +
    +			else if (type == 'Z')
    +			    VA_INTGR(size_t);
    +
    +			else
    +			    VA_INTGR(int);
    +
    +			used = 'i';
    +			break;
    +		    case 'f':	/* FALLTHRU */
    +		    case 'e':	/* FALLTHRU */
    +		    case 'E':	/* FALLTHRU */
    +		    case 'g':	/* FALLTHRU */
    +		    case 'G':	/* FALLTHRU */
    +			VA_FLOAT(double);
    +
    +			used = 'f';
    +			break;
    +		    case 'c':
    +			VA_INTGR(int);
    +
    +			used = 'c';
    +			break;
    +		    case 's':
    +			VA_POINT(char *);
    +
    +			if (prec < 0)
    +			    prec = (int) strlen(pval);
    +			used = 's';
    +			break;
    +		    case 'p':
    +			VA_POINT(void *);
    +
    +			used = 'p';
    +			break;
    +		    case 'n':
    +			VA_POINT(int *);
    +
    +			used = 0;
    +			break;
    +		    default:
    +			CTRACE((tfp, "unknown format character '%c' in %s\n",
    +				*fmt, format));
    +			break;
    +		    }
    +		} else if (*fmt == '.') {
    +		    state = Prec;
    +		} else if (*fmt == '%') {
    +		    done = TRUE;
    +		    used = '%';
    +		}
    +	    }
    +	    fmt_ptr[f] = '\0';
    +
    +	    if (prec > 0) {
    +		switch (used) {
    +		case 'f':
    +		    if (width < prec + NUM_WIDTH)
    +			width = prec + NUM_WIDTH;
    +		    /* FALLTHRU */
    +		case 'i':
    +		    /* FALLTHRU */
    +		case 'p':
    +		    if (width < prec + 2)
    +			width = prec + 2;	/* leading sign/space/zero, "0x" */
    +		    break;
    +		case 'c':
    +		    break;
    +		case '%':
    +		    break;
    +		default:
    +		    if (width < prec)
    +			width = prec;
    +		    break;
    +		}
    +	    }
    +	    if (width >= (int) tmp_len) {
    +		tmp_len = GROW_EXPR(tmp_len + width);
    +		tmp_ptr = HTAlloc(tmp_ptr, tmp_len);
    +	    }
    +
    +	    switch (used) {
    +	    case 'i':
    +	    case 'c':
    +		sprintf(tmp_ptr, fmt_ptr, ival);
    +		break;
    +	    case 'f':
    +		sprintf(tmp_ptr, fmt_ptr, fval);
    +		break;
    +	    default:
    +		sprintf(tmp_ptr, fmt_ptr, pval);
    +		break;
    +	    }
    +	    need = dst_len + strlen(tmp_ptr) + 1;
    +	    if (need >= have) {
    +		dst_ptr = HTAlloc(dst_ptr, have = GROW_EXPR(need));
    +	    }
    +	    strcpy(dst_ptr + dst_len, tmp_ptr);
    +	    dst_len += strlen(tmp_ptr);
    +	} else {
    +	    if ((dst_len + 2) >= have) {
    +		dst_ptr = HTAlloc(dst_ptr, (have += GROW_SIZE));
    +	    }
    +	    dst_ptr[dst_len++] = *fmt++;
    +	}
    +    }
    +
    +#ifndef SAVE_TIME_NOT_SPACE
    +    FREE(tmp_ptr);
    +    FREE(fmt_ptr);
    +#endif
    +    dst_ptr[dst_len] = '\0';
    +    if (pstr)
    +	*pstr = dst_ptr;
    +    return (dst_ptr);
    +#endif /* HAVE_VASPRINTF */
    +}
    +#undef SAVE_TIME_NOT_SPACE
    +
    +/*
    + * Replacement for sprintf, allocates buffer on the fly according to what's
    + * needed for its arguments.  Unlike sprintf, this always concatenates to the
    + * destination buffer.
    + */
    +/* Note: if making changes, also check the memory tracking version
    + * LYLeakHTSprintf in LYLeaks.c. - kw */
    +#ifdef HTSprintf		/* if hidden by LYLeaks stuff */
    +#undef HTSprintf
    +#endif
    +char *HTSprintf(char **pstr, const char *fmt, ...)
    +{
    +    char *result = 0;
    +    size_t inuse = 0;
    +    va_list ap;
    +
    +    LYva_start(ap, fmt);
    +    {
    +	if (pstr != 0 && *pstr != 0)
    +	    inuse = strlen(*pstr);
    +	result = StrAllocVsprintf(pstr, inuse, fmt, &ap);
    +    }
    +    va_end(ap);
    +
    +    return (result);
    +}
    +
    +/*
    + * Replacement for sprintf, allocates buffer on the fly according to what's
    + * needed for its arguments.  Like sprintf, this always resets the destination
    + * buffer.
    + */
    +/* Note: if making changes, also check the memory tracking version
    + * LYLeakHTSprintf0 in LYLeaks.c. - kw */
    +#ifdef HTSprintf0		/* if hidden by LYLeaks stuff */
    +#undef HTSprintf0
    +#endif
    +char *HTSprintf0(char **pstr, const char *fmt, ...)
    +{
    +    char *result = 0;
    +    va_list ap;
    +
    +    LYva_start(ap, fmt);
    +    {
    +	result = StrAllocVsprintf(pstr, (size_t) 0, fmt, &ap);
    +    }
    +    va_end(ap);
    +
    +    return (result);
    +}
    +
    +/*
    + * Returns a quoted or escaped form of the given parameter, suitable for use in
    + * a command string.
    + */
    +#if USE_QUOTED_PARAMETER
    +#define S_QUOTE '\''
    +#define D_QUOTE '"'
    +char *HTQuoteParameter(const char *parameter)
    +{
    +    size_t i;
    +    size_t last;
    +    size_t n = 0;
    +    size_t quoted = 0;
    +    char *result;
    +
    +    if (parameter == 0)
    +	parameter = "";
    +
    +    last = strlen(parameter);
    +    for (i = 0; i < last; ++i)
    +	if (StrChr("\\&#$^*?(){}<>\"';`|", parameter[i]) != 0
    +	    || isspace(UCH(parameter[i])))
    +	    ++quoted;
    +
    +    result = (char *) malloc(last + 5 * quoted + 3);
    +    if (result == NULL)
    +	outofmem(__FILE__, "HTQuoteParameter");
    +
    +    n = 0;
    +#if (USE_QUOTED_PARAMETER == 1)
    +    /*
    +     * Only double-quotes are used in Win32/DOS -TD
    +     */
    +    if (quoted)
    +	result[n++] = D_QUOTE;
    +    for (i = 0; i < last; i++) {
    +	result[n++] = parameter[i];
    +    }
    +    if (quoted)
    +	result[n++] = D_QUOTE;
    +#else
    +    if (quoted)
    +	result[n++] = S_QUOTE;
    +    for (i = 0; i < last; i++) {
    +	if (parameter[i] == S_QUOTE) {
    +	    result[n++] = S_QUOTE;
    +	    result[n++] = D_QUOTE;
    +	    result[n++] = parameter[i];
    +	    result[n++] = D_QUOTE;
    +	    result[n++] = S_QUOTE;
    +	} else {
    +	    /* Note:  No special handling of other characters, including
    +	       backslash, since we are constructing a single-quoted string!
    +	       Backslash has no special escape meaning within those for sh
    +	       and compatible shells, so trying to escape a backslash by
    +	       doubling it is unnecessary and would be interpreted by the
    +	       shell as an additional data character. - kw 2000-05-02
    +	     */
    +	    result[n++] = parameter[i];
    +	}
    +    }
    +    if (quoted)
    +	result[n++] = S_QUOTE;
    +#endif
    +    result[n] = '\0';
    +    return result;
    +}
    +#endif
    +
    +#define HTIsParam(string) ((string[0] == '%' && string[1] == 's'))
    +
    +/*
    + * Returns the number of "%s" tokens in a system command-template.
    + */
    +int HTCountCommandArgs(const char *command)
    +{
    +    int number = 0;
    +
    +    while (command[0] != 0) {
    +	if (HTIsParam(command))
    +	    number++;
    +	command++;
    +    }
    +    return number;
    +}
    +
    +/*
    + * Returns a pointer into the given string after the given parameter number
    + */
    +static const char *HTAfterCommandArg(const char *command,
    +				     int number)
    +{
    +    while (number > 0) {
    +	if (command[0] != 0) {
    +	    if (HTIsParam(command)) {
    +		number--;
    +		command++;
    +	    }
    +	    command++;
    +	} else {
    +	    break;
    +	}
    +    }
    +    return command;
    +}
    +
    +#if USE_QUOTED_PARAMETER
    +/*
    + * Recursively trim possible parameters of the source until an existing file
    + * is found.  If no file is found, return -1.  If a file is found, return
    + * the offset to a blank just after the filename.
    + *
    + * TODO: this could be smarter about trimming, e.g., matching quotes.
    + */
    +static int skipPathname(const char *target, const char *source)
    +{
    +    int result = -1;
    +    const char *last;
    +    struct stat stat_info;
    +
    +    if (HTStat(target, &stat_info) == 0
    +	&& S_ISREG(stat_info.st_mode)) {
    +	result = 0;
    +    } else if (*target != ' ' && (last = strrchr(target, ' ')) != NULL) {
    +	char *temp = NULL;
    +	int inner;
    +
    +	while (last != target && last[-1] == ' ')
    +	    --last;
    +
    +	StrAllocCopy(temp, target);
    +	result = (int) (last - target);
    +	temp[result] = '\0';
    +
    +	if ((inner = skipPathname(temp, source)) < 0) {
    +	    result = -1;
    +	} else if (inner > 0) {
    +	    result = inner;
    +	}
    +
    +	FREE(temp);
    +    }
    +    CTRACE((tfp, "skip/recur %d '%s'\n", result, target));
    +    return result;
    +}
    +#endif
    +
    +/*
    + * Like HTAddParam, but the parameter may be an environment variable, which we
    + * will expand and append.  Do this only for things like the command-verb,
    + * where we obtain the parameter from the user's configuration.  Any quoting
    + * required for the environment variable has to be done within its value, e.g.,
    + *
    + *	setenv EDITOR 'xvile -name "No such class"'
    + *
    + * This is useful only when we quote parameters, of course.
    + */
    +#if USE_QUOTED_PARAMETER
    +void HTAddXpand(char **result,
    +		const char *command,
    +		int number,
    +		const char *parameter)
    +{
    +    if (parameter == NULL)
    +	parameter = "";
    +    if (number > 0) {
    +	const char *last = HTAfterCommandArg(command, number - 1);
    +	const char *next = last;
    +
    +	if (number <= 1) {
    +	    FREE(*result);
    +	}
    +
    +	while (next[0] != 0) {
    +	    if (HTIsParam(next)) {
    +		if (next != last) {
    +		    size_t len = ((size_t) (next - last)
    +				  + ((*result != 0)
    +				     ? strlen(*result)
    +				     : 0));
    +
    +		    HTSACat(result, last);
    +		    (*result)[len] = 0;
    +		}
    +		if (LYisAbsPath(parameter)) {
    +		    int skip = skipPathname(parameter, parameter);
    +		    char *quoted;
    +
    +		    if (skip > 0) {
    +			char *temp = NULL;
    +
    +			StrAllocCopy(temp, parameter);
    +			temp[skip] = 0;
    +
    +			quoted = HTQuoteParameter(temp);
    +			HTSACat(result, quoted);
    +			FREE(quoted);
    +
    +			temp[skip] = ' ';
    +			HTSACat(result, temp + skip);
    +			FREE(temp);
    +		    } else {
    +			quoted = HTQuoteParameter(parameter);
    +			HTSACat(result, quoted);
    +			FREE(quoted);
    +		    }
    +		} else {
    +		    /* leave it unquoted, e.g., environment variable expanded */
    +		    HTSACat(result, parameter);
    +		}
    +		CTRACE((tfp, "PARAM-EXP:%s\n", *result));
    +		return;
    +	    }
    +	    next++;
    +	}
    +    }
    +}
    +#endif /* USE_QUOTED_PARAMETER */
    +
    +/*
    + * Append string to a system command that we are constructing, without quoting. 
    + * We're given the index of the newest parameter we're processing.  Zero
    + * indicates none, so a value of '1' indicates that we copy from the beginning
    + * of the command string up to the first parameter, substitute the quoted
    + * parameter and return the result.
    + *
    + * Parameters are substituted at "%s" tokens, like printf.  Other printf-style
    + * tokens are not substituted; they are passed through without change.
    + */
    +void HTAddToCmd(char **result,
    +		const char *command,
    +		int number,
    +		const char *string)
    +{
    +    if (number > 0) {
    +	const char *last = HTAfterCommandArg(command, number - 1);
    +	const char *next = last;
    +
    +	if (number <= 1) {
    +	    FREE(*result);
    +	}
    +	if (string == 0)
    +	    string = "";
    +	while (next[0] != 0) {
    +	    if (HTIsParam(next)) {
    +		if (next != last) {
    +		    size_t len = ((size_t) (next - last)
    +				  + ((*result != 0)
    +				     ? strlen(*result)
    +				     : 0));
    +
    +		    HTSACat(result, last);
    +		    (*result)[len] = 0;
    +		}
    +		HTSACat(result, string);
    +		CTRACE((tfp, "PARAM-ADD:%s\n", *result));
    +		return;
    +	    }
    +	    next++;
    +	}
    +    }
    +}
    +
    +/*
    + * Append string-parameter to a system command that we are constructing.  The
    + * string is a complete parameter (which is a necessary assumption so we can
    + * quote it properly).
    + */
    +void HTAddParam(char **result,
    +		const char *command,
    +		int number,
    +		const char *parameter)
    +{
    +    if (number > 0) {
    +#if USE_QUOTED_PARAMETER
    +	char *quoted = HTQuoteParameter(parameter);
    +
    +	HTAddToCmd(result, command, number, quoted);
    +	FREE(quoted);
    +#else
    +	HTAddToCmd(result, command, number, parameter);
    +#endif
    +    }
    +}
    +
    +/*
    + * Append the remaining command-string to a system command (compare with
    + * HTAddParam).  Any remaining "%s" tokens are copied as empty strings.
    + */
    +void HTEndParam(char **result,
    +		const char *command,
    +		int number)
    +{
    +    const char *last;
    +    int count;
    +
    +    count = HTCountCommandArgs(command);
    +    if (count < number)
    +	number = count;
    +    last = HTAfterCommandArg(command, number);
    +    if (last[0] != 0) {
    +	HTSACat(result, last);
    +    }
    +    CTRACE((tfp, "PARAM-END:%s\n", *result));
    +}
    +
    +/* Binary-strings (may have embedded nulls).  Some modules (HTGopher) assume
    + * there is a null on the end, anyway.
    + */
    +
    +/* (Re)allocate a bstring, e.g., to increase its buffer size for ad hoc
    + * operations.
    + */
    +void HTSABAlloc(bstring **dest, int len)
    +{
    +    if (*dest == 0) {
    +	*dest = typecalloc(bstring);
    +
    +	if (*dest == 0)
    +	    outofmem(__FILE__, "HTSABAlloc");
    +    }
    +
    +    if ((*dest)->len != len) {
    +	(*dest)->str = typeRealloc(char, (*dest)->str, len);
    +
    +	if ((*dest)->str == 0)
    +	    outofmem(__FILE__, "HTSABAlloc");
    +
    +	(*dest)->len = len;
    +    }
    +}
    +
    +/* Allocate a new bstring, and return it.
    +*/
    +void HTSABCopy(bstring **dest, const char *src,
    +	       int len)
    +{
    +    bstring *t;
    +    unsigned need = (unsigned) (len + 1);
    +
    +    CTRACE2(TRACE_BSTRING,
    +	    (tfp, "HTSABCopy(%p, %p, %d)\n",
    +	     (void *) dest, (const void *) src, len));
    +    HTSABFree(dest);
    +    if (src) {
    +	if (TRACE_BSTRING) {
    +	    CTRACE((tfp, "===    %4d:", len));
    +	    trace_bstring2(src, len);
    +	    CTRACE((tfp, "\n"));
    +	}
    +	if ((t = (bstring *) malloc(sizeof(bstring))) == NULL)
    +	      outofmem(__FILE__, "HTSABCopy");
    +
    +	if ((t->str = typeMallocn(char, need)) == NULL)
    +	      outofmem(__FILE__, "HTSABCopy");
    +
    +	MemCpy(t->str, src, len);
    +	t->len = len;
    +	t->str[t->len] = '\0';
    +	*dest = t;
    +    }
    +    if (TRACE_BSTRING) {
    +	CTRACE((tfp, "=>     %4d:", BStrLen(*dest)));
    +	trace_bstring(*dest);
    +	CTRACE((tfp, "\n"));
    +    }
    +}
    +
    +/*
    + * Initialize with a null-terminated string (discards the null).
    + */
    +void HTSABCopy0(bstring **dest, const char *src)
    +{
    +    HTSABCopy(dest, src, (int) strlen(src));
    +}
    +
    +/*
    + * Append a block of memory to a bstring.
    + */
    +void HTSABCat(bstring **dest, const char *src,
    +	      int len)
    +{
    +    bstring *t = *dest;
    +
    +    CTRACE2(TRACE_BSTRING,
    +	    (tfp, "HTSABCat(%p, %p, %d)\n",
    +	     (void *) dest, (const void *) src, len));
    +    if (src) {
    +	unsigned need = (unsigned) (len + 1);
    +
    +	if (TRACE_BSTRING) {
    +	    CTRACE((tfp, "===    %4d:", len));
    +	    trace_bstring2(src, len);
    +	    CTRACE((tfp, "\n"));
    +	}
    +	if (t) {
    +	    unsigned length = (unsigned) t->len + need;
    +
    +	    t->str = typeRealloc(char, t->str, length);
    +	} else {
    +	    if ((t = typecalloc(bstring)) == NULL)
    +		  outofmem(__FILE__, "HTSACat");
    +
    +	    t->str = typeMallocn(char, need);
    +	}
    +	if (t->str == NULL)
    +	    outofmem(__FILE__, "HTSACat");
    +
    +	MemCpy(t->str + t->len, src, len);
    +	t->len += len;
    +	t->str[t->len] = '\0';
    +	*dest = t;
    +    }
    +    if (TRACE_BSTRING) {
    +	CTRACE((tfp, "=>     %4d:", BStrLen(*dest)));
    +	trace_bstring(*dest);
    +	CTRACE((tfp, "\n"));
    +    }
    +}
    +
    +/*
    + * Append a null-terminated string (discards the null).
    + */
    +void HTSABCat0(bstring **dest, const char *src)
    +{
    +    HTSABCat(dest, src, (int) strlen(src));
    +}
    +
    +/*
    + * Compare two bstring's for equality
    + */
    +BOOL HTSABEql(bstring *a, bstring *b)
    +{
    +    unsigned len_a = (unsigned) ((a != 0) ? a->len : 0);
    +    unsigned len_b = (unsigned) ((b != 0) ? b->len : 0);
    +
    +    if (len_a == len_b) {
    +	if (len_a == 0
    +	    || MemCmp(a->str, b->str, a->len) == 0)
    +	    return TRUE;
    +    }
    +    return FALSE;
    +}
    +
    +/*
    + * Deallocate a bstring.
    + */
    +void HTSABFree(bstring **ptr)
    +{
    +    if (*ptr != NULL) {
    +	FREE((*ptr)->str);
    +	FREE(*ptr);
    +	*ptr = NULL;
    +    }
    +}
    +
    +/*
    + * Use this function to perform formatted sprintf's onto the end of a bstring.
    + * The bstring may contain embedded nulls; the formatted portions must not.
    + */
    +bstring *HTBprintf(bstring **pstr, const char *fmt, ...)
    +{
    +    bstring *result = 0;
    +    char *temp = 0;
    +    va_list ap;
    +
    +    LYva_start(ap, fmt);
    +    {
    +	temp = StrAllocVsprintf(&temp, (size_t) 0, fmt, &ap);
    +	if (non_empty(temp)) {
    +	    HTSABCat(pstr, temp, (int) strlen(temp));
    +	}
    +	FREE(temp);
    +	result = *pstr;
    +    }
    +    va_end(ap);
    +
    +    return (result);
    +}
    +
    +/*
    + * Write binary-data to the logfile, making it safe for most editors to view.
    + * That is most, since we do not restrict line-length.  Nulls and other
    + * non-printing characters are addressed.
    + */
    +void trace_bstring2(const char *text,
    +		    int size)
    +{
    +    int n;
    +
    +    if (text != 0) {
    +	for (n = 0; n < size; ++n) {
    +	    int ch = UCH(text[n]);
    +
    +	    switch (ch) {
    +	    case '\\':
    +		fputs("\\\\", tfp);
    +		break;
    +	    case '\r':
    +		fputs("\\r", tfp);
    +		break;
    +	    case '\t':
    +		fputs("\\t", tfp);
    +		break;
    +	    case '\f':
    +		fputs("\\f", tfp);
    +		break;
    +	    default:
    +		if (isprint(ch) || isspace(ch)) {
    +		    fputc(ch, tfp);
    +		} else {
    +		    fprintf(tfp, "\\%03o", ch);
    +		}
    +		break;
    +	    }
    +	}
    +    }
    +}
    +
    +void trace_bstring(bstring *data)
    +{
    +    trace_bstring2(BStrData(data), BStrLen(data));
    +}
    diff --git a/WWW/Library/Implementation/HTString.h b/WWW/Library/Implementation/HTString.h
    new file mode 100644
    index 0000000..82520ad
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTString.h
    @@ -0,0 +1,167 @@
    +/*
    + * $LynxId: HTString.h,v 1.41 2021/06/09 19:30:55 tom Exp $
    + *						String handling for libwww
    + *                                         STRINGS
    + *                                            
    + * Case-independent string comparison and allocations with copies etc
    + */
    +#ifndef HTSTRING_H
    +#define HTSTRING_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif /* HTUTILS_H */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern const char *HTLibraryVersion;	/* String for help screen etc */
    +
    +/*
    + * EBCDIC string comparison using ASCII collating sequence
    + */
    +#ifdef    NOT_ASCII
    +    extern int AS_casecomp(const char *a, const char *b);
    +    extern int AS_ncmp(const char *a, const char *b, unsigned int n);
    +
    +#define    AS_cmp( a, b )  ( AS_ncmp( ( a ), ( b ), -1 ) )
    +
    +#else
    +#define AS_casecomp( a, b ) ( strcasecomp( ( a ), ( b ) ) )
    +#define AS_ncmp( a, b, c )  ( StrNCmp( ( a ), ( b ), ( c ) ) )
    +#define AS_cmp strcmp
    +
    +#endif				/* NOT_ASCII */
    +
    +#define StrNCat(a,b,c) strncat((a),(b),(size_t)(c))
    +#define StrNCpy(a,b,c) strncpy((a),(b),(size_t)(c))
    +#define StrNCmp(a,b,c) strncmp((a),(b),(size_t)(c))
    +
    +#define MemCpy(a,b,c)  memcpy((a),(b),(size_t)(c))
    +#define MemCmp(a,b,c)  memcmp((a),(b),(size_t)(c))
    +
    +    /*
    +     * Workaround for glibc header defect combined with -Wlogical-op warnings
    +     */
    +#define StrChr (strchr)
    +
    +    /*
    +     * Case-insensitive string comparison
    +     *
    +     * The usual routines (comp instead of cmp) had some problem.
    +     */
    +    extern int strcasecomp(const char *a, const char *b);
    +    extern int strncasecomp(const char *a, const char *b, int n);
    +
    +    extern int strcasecomp8(const char *a, const char *b);
    +    extern int strncasecomp8(const char *a, const char *b, int n);
    +
    +    extern int strcasecomp_asterisk(const char *a, const char *b);
    +
    +    /*
    +     * strcasecomp8 and strncasecomp8 are variants of strcasecomp and
    +     * strncasecomp, but use 8bit upper/lower case information from the
    +     * current display charset
    +     */
    +
    +    /*
    +     * Malloced string manipulation
    +     */
    +#define StrAllocCopy(dest, src) HTSACopy (&(dest), src)
    +#define StrAllocCat(dest, src)  HTSACat  (&(dest), src)
    +    extern char *HTSACopy(char **dest, const char *src);
    +    extern char *HTSACat(char **dest, const char *src);
    +
    +    /*
    +     * optimized for heavily realloc'd strings in temp objects
    +     */
    +#define StrAllocCopy_extra(dest, src) HTSACopy_extra (&(dest), src)
    +#define FREE_extra(x)   {if (x != NULL) {HTSAFree_extra(x); x = NULL;}}
    +#define Clear_extra(x)  {if (x != NULL) {*x = '\0';}}
    +    extern char *HTSACopy_extra(char **dest, const char *src);
    +    extern void HTSAFree_extra(char *s);
    +
    +    /*
    +     * Next word or quoted string
    +     */
    +    extern char *HTNextField(char **pstr);
    +
    +    /* A more general parser - kw */
    +    extern char *HTNextTok(char **pstr,
    +			   const char *delims, const char *bracks, char *found);
    +
    +    extern char *HTSprintf(char **pstr, const char *fmt, ...) GCC_PRINTFLIKE(2,3);
    +    extern char *HTSprintf0(char **pstr, const char *fmt, ...) GCC_PRINTFLIKE(2,3);
    +
    +#if defined(LY_FIND_LEAKS)	/* private otherwise */
    +    extern char *StrAllocVsprintf(char **pstr,
    +				  size_t len,
    +				  const char *fmt,
    +				  va_list *ap);
    +#endif
    +
    +#if defined(__CYGWIN__)
    +#define USE_QUOTED_PARAMETER 2	/* single and double-quoting */
    +#elif defined(DOSPATH)
    +#define USE_QUOTED_PARAMETER 1	/* double-quoting only */
    +#elif (defined(VMS) || defined(__EMX__))
    +#define USE_QUOTED_PARAMETER 0	/* no quoting */
    +#else
    +#define USE_QUOTED_PARAMETER 2	/* single and double-quoting */
    +#endif
    +
    +#if USE_QUOTED_PARAMETER
    +    extern char *HTQuoteParameter(const char *parameter);
    +    extern void HTAddXpand(char **result, const char *command, int number, const char *parameter);
    +
    +#else
    +#define HTQuoteParameter(parameter) parameter	/* simplify ifdef'ing */
    +#define HTAddXpand(result,command,number,parameter)  HTAddParam(result,command,number,parameter)
    +#endif
    +
    +    extern int HTCountCommandArgs(const char *command);
    +    extern void HTAddToCmd(char **result, const char *command, int number, const char *string);
    +    extern void HTAddParam(char **result, const char *command, int number, const char *parameter);
    +    extern void HTEndParam(char **result, const char *command, int number);
    +
    +/* Force an option, with leading blanks, to be appended without quoting them */
    +#define HTOptParam(result, command, number, parameter) HTSACat(result, parameter)
    +
    +/* Binary copy and concat */
    +    typedef struct {
    +	char *str;
    +	int len;
    +    } bstring;
    +
    +    extern void HTSABAlloc(bstring **dest, int len);
    +    extern void HTSABCopy(bstring **dest, const char *src, int len);
    +    extern void HTSABCopy0(bstring **dest, const char *src);
    +    extern void HTSABCat(bstring **dest, const char *src, int len);
    +    extern void HTSABCat0(bstring **dest, const char *src);
    +    extern BOOL HTSABEql(bstring *a, bstring *b);
    +    extern void HTSABFree(bstring **ptr);
    +
    +#define BStrLen(s)    (((s) != 0) ? (s)->len : 0)
    +#define BStrData(s)   (((s) != 0) ? (s)->str : 0)
    +
    +#define BINEQ(a,b)    (HTSABEql(a,b))	/* like STREQ() */
    +
    +#define isBEmpty(p)   ((p) == 0 || BStrData(p) == 0 || BStrLen(p) == 0)
    +
    +#define BStrAlloc(d,n)   HTSABAlloc( &(d), n)
    +#define BStrCopy(d,s)    HTSABCopy( &(d), BStrData(s), BStrLen(s))
    +#define BStrCopy0(d,s)   HTSABCopy0( &(d), s)
    +#define BStrCopy1(d,s,n) HTSABCopy(  &(d), s, n)
    +#define BStrCat(d,s)     HTSABCat( &(d), BStrData(s), BStrLen(s))
    +#define BStrCat0(d,s)    HTSABCat0( &(d), s)
    +#define BStrFree(d)      HTSABFree( &(d))
    +
    +    extern bstring *HTBprintf(bstring **pstr, const char *fmt, ...) GCC_PRINTFLIKE(2,3);
    +
    +    extern void trace_bstring(bstring *data);
    +    extern void trace_bstring2(const char *text, int size);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTSTRING_H */
    diff --git a/WWW/Library/Implementation/HTStyle.c b/WWW/Library/Implementation/HTStyle.c
    new file mode 100644
    index 0000000..54c0bc5
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTStyle.c
    @@ -0,0 +1,378 @@
    +/*
    + * $LynxId: HTStyle.c,v 1.16 2009/11/27 13:01:48 tom Exp $
    + *
    + *	Style Implementation for Hypertext			HTStyle.c
    + *	==================================
    + *
    + *	Styles allow the translation between a logical property
    + *	of a piece of text and its physical representation.
    + *
    + *	A StyleSheet is a collection of styles, defining the
    + *	translation necessary to
    + *	represent a document.  It is a linked list of styles.
    + */
    +
    +#include 
    +#include 
    +
    +#include 
    +
    +/*	Create a new style
    +*/
    +HTStyle *HTStyleNew(void)
    +{
    +    HTStyle *self = typecalloc(HTStyle);
    +
    +    if (self == NULL)
    +	outofmem(__FILE__, "HTStyleNew");
    +    return self;
    +}
    +
    +/*	Create a new style with a name
    +*/
    +HTStyle *HTStyleNewNamed(const char *name)
    +{
    +    HTStyle *self = HTStyleNew();
    +
    +    StrAllocCopy(self->w_name, name);
    +    self->id = -1;		/* <0 */
    +    return self;
    +}
    +
    +/*	Free a style
    +*/
    +HTStyle *HTStyleFree(HTStyle *self)
    +{
    +    FREE(self->w_name);
    +    FREE(self->w_SGMLTag);
    +    FREE(self);
    +    return NULL;
    +}
    +
    +#ifdef SUPPRESS			/* Only on the NeXT */
    +/*	Read a style from a stream	(without its name)
    + *	--------------------------
    + *
    + *	Reads a style with paragraph information from a stream.
    + *	The style name is not read or written by these routines.
    + */
    +#define NONE_STRING "(None)"
    +#define HTStream NXStream
    +
    +HTStyle *HTStyleRead(HTStyle *style, HTStream *stream)
    +{
    +    char myTag[STYLE_NAME_LENGTH];
    +    char fontName[STYLE_NAME_LENGTH];
    +    NXTextStyle *p;
    +    int tab;
    +    int gotpara;		/* flag: have we got a paragraph definition? */
    +
    +    NXScanf(stream, "%s%s%f%d",
    +	    myTag,
    +	    fontName,
    +	    &style->fontSize,
    +	    &gotpara);
    +    if (gotpara) {
    +	if (!style->paragraph) {
    +	    style->paragraph = malloc(sizeof(*(style->paragraph)));
    +	    if (!style->paragraph)
    +		outofmem(__FILE__, "HTStyleRead");
    +	    style->paragraph->tabs = 0;
    +	}
    +	p = style->paragraph;
    +	NXScanf(stream, "%f%f%f%f%hd%f%f%hd",
    +		&p->indent1st,
    +		&p->indent2nd,
    +		&p->lineHt,
    +		&p->descentLine,
    +		&p->alignment,
    +		&style->spaceBefore,
    +		&style->spaceAfter,
    +		&p->numTabs);
    +	FREE(p->tabs);
    +	p->tabs = malloc(p->numTabs * sizeof(p->tabs[0]));
    +	if (!p->tabs)
    +	    outofmem(__FILE__, "HTStyleRead");
    +	for (tab = 0; tab < p->numTabs; tab++) {
    +	    NXScanf(stream, "%hd%f",
    +		    &p->tabs[tab].kind,
    +		    &p->tabs[tab].x);
    +	}
    +    } else {			/* No paragraph */
    +	FREE(style->paragraph);
    +    }				/* if no paragraph */
    +    StrAllocCopy(style->SGMLTag, myTag);
    +    if (strcmp(fontName, NONE_STRING) == 0)
    +	style->font = 0;
    +    else
    +      style->font =[Font newFont: fontName size:style->fontSize];
    +    return NULL;
    +}
    +
    +/*	Write a style to a stream in a compatible way
    +*/
    +HTStyle *HTStyleWrite(HTStyle *style, NXStream * stream)
    +{
    +    int tab;
    +    NXTextStyle *p = style->paragraph;
    +
    +    NXPrintf(stream, "%s %s %f %d\n",
    +	     style->SGMLTag,
    +	     style->font ?[style->font name] : NONE_STRING,
    +	     style->fontSize,
    +	     p != 0);
    +
    +    if (p) {
    +	NXPrintf(stream, "\t%f %f %f %f %d %f %f\t%d\n",
    +		 p->indent1st,
    +		 p->indent2nd,
    +		 p->lineHt,
    +		 p->descentLine,
    +		 p->alignment,
    +		 style->spaceBefore,
    +		 style->spaceAfter,
    +		 p->numTabs);
    +
    +	for (tab = 0; tab < p->numTabs; tab++)
    +	    NXPrintf(stream, "\t%d %f\n",
    +		     p->tabs[tab].kind,
    +		     p->tabs[tab].x);
    +    }
    +    return style;
    +}
    +
    +/*	Write a style to stdout for diagnostics
    +*/
    +HTStyle *HTStyleDump(HTStyle *style)
    +{
    +    int tab;
    +    NXTextStyle *p = style->paragraph;
    +
    +    printf(STYLE_DUMP_FONT,
    +	   style,
    +	   style->name,
    +	   style->SGMLTag,
    +	   [style->font name],
    +	   style->fontSize);
    +    if (p) {
    +	printf(STYLE_DUMP_IDENT,
    +	       p->indent1st,
    +	       p->indent2nd,
    +	       p->lineHt,
    +	       p->descentLine);
    +	printf(STYLE_DUMP_ALIGN,
    +	       p->alignment,
    +	       p->numTabs,
    +	       style->spaceBefore,
    +	       style->spaceAfter);
    +
    +	for (tab = 0; tab < p->numTabs; tab++) {
    +	    printf(STYLE_DUMP_TAB,
    +		   p->tabs[tab].kind,
    +		   p->tabs[tab].x);
    +	}
    +	printf("\n");
    +    }				/* if paragraph */
    +    return style;
    +}
    +#endif /* SUPPRESS */
    +
    +/*			StyleSheet Functions
    + *			====================
    + */
    +
    +/*	Searching for styles:
    +*/
    +HTStyle *HTStyleNamed(HTStyleSheet *self, const char *name)
    +{
    +    HTStyle *scan;
    +
    +    for (scan = self->styles; scan; scan = scan->next)
    +	if (0 == strcmp(GetHTStyleName(scan), name))
    +	    return scan;
    +    CTRACE((tfp, "StyleSheet: No style named `%s'\n", name));
    +    return NULL;
    +}
    +
    +#ifdef NEXT_SUPRESS		/* Not in general common code */
    +
    +HTStyle *HTStyleMatching(HTStyleSheet *self, HTStyle *style)
    +{
    +    HTStyle *scan;
    +
    +    for (scan = self->styles; scan; scan = scan->next)
    +	if (scan->paragraph == para)
    +	    return scan;
    +    return NULL;
    +}
    +
    +/*	Find the style which best fits a given run
    + *	------------------------------------------
    + *
    + *	This heuristic is used for guessing the style for a run of
    + *	text which has been pasted in.  In order, we try:
    + *
    + *	A style whose paragraph structure is actually used by the run.
    + *	A style matching in font
    + *	A style matching in paragraph style exactly
    + *	A style matching in paragraph to a degree
    + */
    +
    +HTStyle *HTStyleForRun(HTStyleSheet *self, NXRun * run)
    +{
    +    HTStyle *scan;
    +    HTStyle *best = 0;
    +    int bestMatch = 0;
    +    NXTextStyle *rp = run->paraStyle;
    +
    +    for (scan = self->styles; scan; scan = scan->next)
    +	if (scan->paragraph == run->paraStyle)
    +	    return scan;	/* Exact */
    +
    +    for (scan = self->styles; scan; scan = scan->next) {
    +	NXTextStyle *sp = scan->paragraph;
    +
    +	if (sp) {
    +	    int match = 0;
    +
    +	    if (sp->indent1st == rp->indent1st)
    +		match = match + 1;
    +	    if (sp->indent2nd == rp->indent2nd)
    +		match = match + 2;
    +	    if (sp->lineHt == rp->lineHt)
    +		match = match + 1;
    +	    if (sp->numTabs == rp->numTabs)
    +		match = match + 1;
    +	    if (sp->alignment == rp->alignment)
    +		match = match + 3;
    +	    if (scan->font == run->font)
    +		match = match + 10;
    +	    if (match > bestMatch) {
    +		best = scan;
    +		bestMatch = match;
    +	    }
    +	}
    +    }
    +    CTRACE((tfp, "HTStyleForRun: Best match for style is %d out of 18\n",
    +	    bestMatch));
    +    return best;
    +}
    +#endif /* NEXT_SUPRESS */
    +
    +/*	Add a style to a sheet
    + *	----------------------
    + */
    +HTStyleSheet *HTStyleSheetAddStyle(HTStyleSheet *self, HTStyle *style)
    +{
    +    style->next = 0;		/* The style will go on the end */
    +    if (!self->styles) {
    +	self->styles = style;
    +    } else {
    +	HTStyle *scan;
    +
    +	for (scan = self->styles; scan->next; scan = scan->next) ;	/* Find end */
    +	scan->next = style;
    +    }
    +    return self;
    +}
    +
    +/*	Remove the given object from a style sheet if it exists
    +*/
    +HTStyleSheet *HTStyleSheetRemoveStyle(HTStyleSheet *self, HTStyle *style)
    +{
    +    if (self->styles == style) {
    +	self->styles = style->next;
    +	return self;
    +    } else {
    +	HTStyle *scan;
    +
    +	for (scan = self->styles; scan; scan = scan->next) {
    +	    if (scan->next == style) {
    +		scan->next = style->next;
    +		return self;
    +	    }
    +	}
    +    }
    +    return NULL;
    +}
    +
    +/*	Create new style sheet
    +*/
    +
    +HTStyleSheet *HTStyleSheetNew(void)
    +{
    +    HTStyleSheet *self = typecalloc(HTStyleSheet);
    +
    +    if (self == NULL)
    +	outofmem(__FILE__, "HTStyleSheetNew");
    +    return self;
    +}
    +
    +/*	Free off a style sheet pointer
    +*/
    +HTStyleSheet *HTStyleSheetFree(HTStyleSheet *self)
    +{
    +    HTStyle *style;
    +
    +    while ((style = self->styles) != 0) {
    +	self->styles = style->next;
    +	HTStyleFree(style);
    +    }
    +    FREE(self);
    +    return NULL;
    +}
    +
    +/*	Read a stylesheet from a typed stream
    + *	-------------------------------------
    + *
    + *	Reads a style sheet from a stream.  If new styles have the same names
    + *	as existing styles, they replace the old ones without changing the ids.
    + */
    +
    +#ifdef NEXT_SUPRESS		/* Only on the NeXT */
    +HTStyleSheet *HTStyleSheetRead(HTStyleSheet *self, NXStream * stream)
    +{
    +    int numStyles;
    +    int i;
    +    HTStyle *style;
    +    char styleName[80];
    +
    +    NXScanf(stream, " %d ", &numStyles);
    +    CTRACE((tfp, "Stylesheet: Reading %d styles\n", numStyles));
    +    for (i = 0; i < numStyles; i++) {
    +	NXScanf(stream, "%s", styleName);
    +	style = HTStyleNamed(self, styleName);
    +	if (!style) {
    +	    style = HTStyleNewNamed(styleName);
    +	    (void) HTStyleSheetAddStyle(self, style);
    +	}
    +	(void) HTStyleRead(style, stream);
    +	if (TRACE)
    +	    HTStyleDump(style);
    +    }
    +    return self;
    +}
    +
    +/*	Write a stylesheet to a typed stream
    + *	------------------------------------
    + *
    + *	Writes a style sheet to a stream.
    + */
    +
    +HTStyleSheet *HTStyleSheetWrite(HTStyleSheet *self, NXStream * stream)
    +{
    +    int numStyles = 0;
    +    HTStyle *style;
    +
    +    for (style = self->styles; style; style = style->next)
    +	numStyles++;
    +    NXPrintf(stream, "%d\n", numStyles);
    +
    +    CTRACE((tfp, "StyleSheet: Writing %d styles\n", numStyles));
    +    for (style = self->styles; style; style = style->next) {
    +	NXPrintf(stream, "%s ", style->name);
    +	(void) HTStyleWrite(style, stream);
    +    }
    +    return self;
    +}
    +#endif /* NEXT_SUPRESS */
    diff --git a/WWW/Library/Implementation/HTStyle.h b/WWW/Library/Implementation/HTStyle.h
    new file mode 100644
    index 0000000..79ba003
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTStyle.h
    @@ -0,0 +1,241 @@
    +/*
    + * $LynxId: HTStyle.h,v 1.18 2020/01/21 22:02:35 tom Exp $
    +					    HTStyle: Style management for libwww
    +                              STYLE DEFINITION FOR HYPERTEXT
    +
    +   Styles allow the translation between a logical property of a piece of text
    +   and its physical representation.
    +
    +   A StyleSheet is a collection of styles, defining the translation necessary
    +   to represent a document.  It is a linked list of styles.
    +
    +Overriding this module
    +
    +   Why is the style structure declared in the HTStyle.h module, instead of
    +   having the user browser define the structure, and the HTStyle routines just
    +   use sizeof() for copying?
    +
    +   It's not obvious whether HTStyle.c should be common code.  It's useful to
    +   have common code for loading style sheets, especially if the movement toward
    +   standard style sheets gets going.
    +
    +   If it IS common code, then both the hypertext object and HTStyle.c must know
    +   the structure of a style, so HTStyle.h is a suitable place to put that. 
    +   HTStyle.c has to be compiled with a knowledge of the
    +
    +   It we take it out of the library, then of course HTStyle could be declared
    +   as an undefined structure.  The only references to it are in the
    +   structure-flattening code HTML.c and HTPlain.c, which only use
    +   HTStypeNamed().
    +
    +   You can in any case override this function in your own code, which will
    +   prevent the HTStyle from being loaded.  You will be able to redefine your
    +   style structure in this case without problems, as no other moule needs to
    +   know it.
    +
    + */
    +#ifndef HTStyle_H
    +#define HTStyle_H
    +
    +#include 
    +
    +typedef long int HTFont;	/* Dummy definition instead */
    +
    +#ifdef NeXT_suppressed
    +#include 
    +typedef NXCoord HTCoord;
    +
    +#define HTParagraphStyle NXTextStyle
    +#define HTCoord NXCoord
    +typedef struct _color {
    +    float grey;
    +    int RGBColor;
    +} HTColor;
    +
    +#else
    +
    +typedef int HTCoord;		/* changed from float to int - kw */
    +
    +typedef struct _HTParagraphStyle {
    +    HTCoord left_indent;	/* @@@@ junk! etc etc */
    +} HTParagraphStyle;
    +
    +typedef int HTColor;		/* Sorry about the US spelling! */
    +
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#define STYLE_NAME_LENGTH       80	/* @@@@@@@@@@@ */
    +    typedef struct {
    +	short kind;		/* only NX_LEFTTAB implemented */
    +	HTCoord position;	/* x coordinate for stop */
    +    } HTTabStop;
    +
    +/*      The Style Structure
    + *      -------------------
    + */
    +
    +    typedef struct _HTStyle {
    +
    +/*      Style management information
    +*/
    +	struct _HTStyle *next;	/* Link for putting into stylesheet */
    +	char *w_name;		/* Style name */
    +	const char *c_name;	/* Style name */
    +	int id;			/* equivalent of name, for speed */
    +	char *w_SGMLTag;	/* Tag name to start */
    +	const char *c_SGMLTag;	/* Tag name to start */
    +
    +/*      Character attributes    (a la NXRun)
    +*/
    +	HTFont font;		/* Font id */
    +	HTCoord fontSize;	/* The size of font, not independent */
    +	HTColor color;		/* text gray of current run */
    +	int superscript;	/* superscript (-sub) in points */
    +
    +	HTAnchor *anchor;	/* Anchor id if any, else zero */
    +
    +/*      Paragraph Attributes    (a la NXTextStyle)
    +*/
    +	HTCoord indent1st;	/* how far first line in paragraph is
    +				   * indented */
    +	HTCoord leftIndent;	/* how far second line is indented */
    +	HTCoord rightIndent;	/* (Missing from NeXT version */
    +	short alignment;	/* quad justification */
    +	HTCoord lineHt;		/* line height */
    +	HTCoord descentLine;	/* descender bottom from baseline */
    +	const HTTabStop *tabs;	/* array of tab stops, 0 terminated */
    +
    +	BOOL wordWrap;		/* Yes means wrap at space not char */
    +	BOOL freeFormat;	/* Yes means \n is just white space */
    +	HTCoord spaceBefore;	/* Omissions from NXTextStyle */
    +	HTCoord spaceAfter;
    +	int paraFlags;		/* Paragraph flags, bits as follows: */
    +
    +#define PARA_KEEP       1	/* Do not break page within this paragraph */
    +#define PARA_WITH_NEXT  2	/* Do not break page after this paragraph */
    +
    +#define HT_JUSTIFY 0		/* For alignment */
    +#define HT_LEFT 1
    +#define HT_RIGHT 2
    +#define HT_CENTER 3
    +
    +    } HTStyle;
    +
    +#define GetHTStyleName(p) ((p)->w_name    ? (p)->w_name    : (p)->c_name)
    +#define GetHTStyleSGML(p) ((p)->w_SGMLTag ? (p)->w_SGMLTag : (p)->c_SGMLTag)
    +
    +#define HTStyleInit( \
    +	next, name, SGML_tag, \
    +	font, fontsize, color, superscript, \
    +	anchor, indent1st, leftIndent, rightIndent, \
    +	alignment, lineHt, descentLine, \
    +	tabs, wordWrap, freeFormat, spaceBefore, spaceAfter, paraFlags) \
    +    { \
    +	next, NULL, #name, ST_##name, NULL, SGML_tag, \
    +	font, fontsize, color, superscript, \
    +	anchor, indent1st, leftIndent, rightIndent, \
    +	alignment, lineHt, descentLine, \
    +	tabs, wordWrap, freeFormat, spaceBefore, spaceAfter, paraFlags }
    +
    +#define HT_ALIGN_NONE (-1)
    +
    +/*      Style functions:
    +*/
    +    extern HTStyle *HTStyleNew(void);
    +    extern HTStyle *HTStyleNewNamed(const char *name);
    +    extern HTStyle *HTStyleFree(HTStyle *self);
    +
    +#ifdef SUPRESS
    +    extern HTStyle *HTStyleRead(HTStyle *self, HTStream *stream);
    +    extern HTStyle *HTStyleWrite(HTStyle *self, HTStream *stream);
    +#endif
    +/*              Style Sheet
    + *              -----------
    + */
    +    typedef struct _HTStyleSheet {
    +	const char *name;
    +	HTStyle *styles;
    +    } HTStyleSheet;
    +
    +/*      Stylesheet functions:
    +*/
    +    extern HTStyleSheet *HTStyleSheetNew(void);
    +    extern HTStyleSheet *HTStyleSheetFree(HTStyleSheet *self);
    +    extern HTStyle *HTStyleNamed(HTStyleSheet *self, const char *name);
    +    extern HTStyle *HTStyleForParagraph(HTStyleSheet *self, HTParagraphStyle * paraStyle);
    +    extern HTStyle *HTStyleMatching(HTStyleSheet *self, HTStyle *style);
    +
    +/* extern HTStyle * HTStyleForRun (HTStyleSheet *self, NXRun * run); */
    +    extern HTStyleSheet *HTStyleSheetAddStyle(HTStyleSheet *self, HTStyle *style);
    +    extern HTStyleSheet *HTStyleSheetRemoveStyle(HTStyleSheet *self, HTStyle *style);
    +
    +#ifdef SUPPRESS
    +    extern HTStyleSheet *HTStyleSheetRead(HTStyleSheet *self, HTStream *stream);
    +    extern HTStyleSheet *HTStyleSheetWrite(HTStyleSheet *self, HTStream *stream);
    +#endif
    +#define CLEAR_POINTER ((void *)-1)	/* Pointer value means "clear me" */
    +
    +/* DefaultStyle.c */
    +    extern HTStyleSheet *DefaultStyle(HTStyle ***result_array);
    +
    +/* enum, use this instead of HTStyle name comparisons */
    +    enum HTStyle_Enum {
    +	ST_Normal = 0,
    +	ST_DivCenter,
    +	ST_DivLeft,
    +	ST_DivRight,
    +	ST_Banner,
    +	ST_Blockquote,
    +	ST_Bq,
    +	ST_Footnote,
    +	ST_List,
    +	ST_List1,
    +	ST_List2,
    +	ST_List3,
    +	ST_List4,
    +	ST_List5,
    +	ST_List6,
    +	ST_Menu,
    +	ST_Menu1,
    +	ST_Menu2,
    +	ST_Menu3,
    +	ST_Menu4,
    +	ST_Menu5,
    +	ST_Menu6,
    +	ST_Glossary,
    +	ST_Glossary1,
    +	ST_Glossary2,
    +	ST_Glossary3,
    +	ST_Glossary4,
    +	ST_Glossary5,
    +	ST_Glossary6,
    +	ST_GlossaryCompact,
    +	ST_GlossaryCompact1,
    +	ST_GlossaryCompact2,
    +	ST_GlossaryCompact3,
    +	ST_GlossaryCompact4,
    +	ST_GlossaryCompact5,
    +	ST_GlossaryCompact6,
    +	ST_Example,
    +	ST_Preformatted,
    +	ST_Listing,
    +	ST_Address,
    +	ST_Note,
    +	ST_Heading1,
    +	ST_Heading2,
    +	ST_Heading3,
    +	ST_Heading4,
    +	ST_Heading5,
    +	ST_Heading6,
    +	ST_HeadingCenter,
    +	ST_HeadingLeft,
    +	ST_HeadingRight
    +    };
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTStyle_H */
    diff --git a/WWW/Library/Implementation/HTTCP.c b/WWW/Library/Implementation/HTTCP.c
    new file mode 100644
    index 0000000..a3bfda6
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTCP.c
    @@ -0,0 +1,2623 @@
    +/*
    + * $LynxId: HTTCP.c,v 1.163 2022/04/01 23:18:35 Rajeev.V.Pillai Exp $
    + *
    + *			Generic Communication Code		HTTCP.c
    + *			==========================
    + *
    + *	This code is in common between client and server sides.
    + *
    + *	16 Jan 92  TBL	Fix strtol() undefined on CMU Mach.
    + *	25 Jun 92  JFG	Added DECNET option through TCP socket emulation.
    + *	13 Sep 93  MD	Added correct return of vmserrorno for HTInetStatus.
    + *			Added decoding of vms error message for MULTINET.
    + *	7-DEC-1993 Bjorn S. Nilsson, ALEPH, CERN, VMS UCX ioctl() changes
    + *			(done of Mosaic)
    + *	19 Feb 94  Danny Mayer	Added Bjorn Fixes to Lynx version
    + *	 7 Mar 94  Danny Mayer	Added Fix UCX version for full domain name
    + *	20 May 94  Andy Harper	Added support for CMU TCP/IP transport
    + *	17 Nov 94  Andy Harper	Added support for SOCKETSHR transport
    + *	16 Jul 95  S. Bjorndahl added kluge to deal with LIBCMU bug
    + */
    +
    +#define LYNX_ADDRINFO	struct addrinfo
    +#define LYNX_HOSTENT	struct hostent
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 	/* added for no_suspend */
    +#include 
    +
    +#ifdef NSL_FORK
    +#include 
    +#include 
    +#define FREE_NSL_FORK(p) { FREE(p); }
    +#elif defined(_WINDOWS_NSL)
    +#define FREE_NSL_FORK(p) if ((p) == gbl_phost) { FREE(p); }
    +#else
    +#define FREE_NSL_FORK(p)	/* nothing */
    +#endif /* NSL_FORK */
    +
    +#ifdef HAVE_RESOLV_H
    +#include 
    +#endif
    +
    +#ifdef __DJGPP__
    +#include 
    +#endif /* __DJGPP__ */
    +
    +#define OK_HOST(p) ((p) != 0 && ((p)->h_length) != 0)
    +
    +#ifdef SVR4_BSDSELECT
    +int BSDselect(int nfds,
    +	      fd_set * readfds,
    +	      fd_set * writefds,
    +	      fd_set * exceptfds,
    +	      struct timeval *select_timeout);
    +
    +#ifdef select
    +#undef select
    +#endif /* select */
    +#define select BSDselect
    +#ifdef SOCKS
    +#ifdef Rselect
    +#undef Rselect
    +#endif /* Rselect */
    +#define Rselect BSDselect
    +#endif /* SOCKS */
    +#endif /* SVR4_BSDSELECT */
    +
    +#include 
    +
    +/*
    + *  Module-Wide variables
    + */
    +static char *hostname = NULL;	/* The name of this host */
    +
    +/*
    + *  PUBLIC VARIABLES
    + */
    +#ifdef SOCKS
    +unsigned long socks_bind_remoteAddr;	/* for long Rbind */
    +#endif /* SOCKS */
    +
    +/*	Encode INET status (as in sys/errno.h)			  inet_status()
    + *	------------------
    + *
    + *  On entry,
    + *	where		gives a description of what caused the error
    + *	global errno	gives the error number in the Unix way.
    + *
    + *  On return,
    + *	returns		a negative status in the Unix way.
    + */
    +
    +#ifdef DECL_SYS_ERRLIST
    +extern char *sys_errlist[];	/* see man perror on cernvax */
    +extern int sys_nerr;
    +#endif /* DECL_SYS_ERRLIST */
    +
    +#ifdef __DJGPP__
    +static int ResolveYield(void)
    +{
    +    return HTCheckForInterrupt()? 0 : 1;
    +}
    +#endif
    +
    +#if defined(VMS) && defined(UCX)
    +/*
    + *  A routine to mimic the ioctl function for UCX.
    + *  Bjorn S. Nilsson, 25-Nov-1993. Based on an example in the UCX manual.
    + */
    +#include 
    +
    +int HTioctl(int d,
    +	    int request,
    +	    int *argp)
    +{
    +    int sdc, status;
    +    unsigned short fun, iosb[4];
    +    char *p5, *p6;
    +    struct comm {
    +	int command;
    +	char *addr;
    +    } ioctl_comm;
    +    struct it2 {
    +	unsigned short len;
    +	unsigned short opt;
    +	struct comm *addr;
    +    } ioctl_desc;
    +
    +    if ((sdc = vaxc$get_sdc(d)) == 0) {
    +	set_errno(EBADF);
    +	return -1;
    +    }
    +    ioctl_desc.opt = UCX$C_IOCTL;
    +    ioctl_desc.len = sizeof(struct comm);
    +
    +    ioctl_desc.addr = &ioctl_comm;
    +    if (request & IOC_OUT) {
    +	fun = IO$_SENSEMODE;
    +	p5 = 0;
    +	p6 = (char *) &ioctl_desc;
    +    } else {
    +	fun = IO$_SETMODE;
    +	p5 = (char *) &ioctl_desc;
    +	p6 = 0;
    +    }
    +    ioctl_comm.command = request;
    +    ioctl_comm.addr = (char *) argp;
    +    status = sys$qiow(0, sdc, fun, iosb, 0, 0, 0, 0, 0, 0, p5, p6);
    +    if (!(status & 01)) {
    +	set_errno(status);
    +	return -1;
    +    }
    +    if (!(iosb[0] & 01)) {
    +	set_errno(iosb[0]);
    +	return -1;
    +    }
    +    return 0;
    +}
    +#endif /* VMS && UCX */
    +
    +#define MY_FORMAT "TCP: Error %d in `SOCKET_ERRNO' after call to %s() failed.\n\t%s\n"
    +	   /* third arg is transport/platform specific */
    +
    +/*	Report Internet Error
    + *	---------------------
    + */
    +int HTInetStatus(const char *where)
    +{
    +    int status;
    +    int saved_errno = errno;
    +
    +#ifdef VMS
    +#ifdef MULTINET
    +    SOCKET_ERRNO = vmserrno;
    +#endif /* MULTINET */
    +#endif /* VMS */
    +
    +#ifdef VM
    +    CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where,
    +	    "(Error number not translated)"));	/* What Is the VM equiv? */
    +#define ER_NO_TRANS_DONE
    +#endif /* VM */
    +
    +#ifdef VMS
    +#ifdef MULTINET
    +    CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where,
    +	    vms_errno_string()));
    +#else
    +    CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where,
    +	    ((SOCKET_ERRNO > 0 && SOCKET_ERRNO <= 65) ?
    +	     strerror(SOCKET_ERRNO) : "(Error number not translated)")));
    +#endif /* MULTINET */
    +#define ER_NO_TRANS_DONE
    +#endif /* VMS */
    +
    +#ifdef HAVE_STRERROR
    +    CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where,
    +	    strerror(SOCKET_ERRNO)));
    +#define ER_NO_TRANS_DONE
    +#endif /* HAVE_STRERROR */
    +
    +#ifndef ER_NO_TRANS_DONE
    +    CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where,
    +	    (SOCKET_ERRNO < sys_nerr ?
    +	     sys_errlist[SOCKET_ERRNO] : "Unknown error")));
    +#endif /* !ER_NO_TRANS_DONE */
    +
    +#ifdef VMS
    +#ifndef MULTINET
    +    CTRACE((tfp,
    +	    "         Unix error number (SOCKET_ERRNO) = %ld dec\n",
    +	    SOCKET_ERRNO));
    +    CTRACE((tfp,
    +	    "         VMS error (vaxc$errno)    = %lx hex\n",
    +	    vaxc$errno));
    +#endif /* MULTINET */
    +#endif /* VMS */
    +
    +    set_errno(saved_errno);
    +
    +#ifdef VMS
    +    /*
    +     * uerrno and errno happen to be zero if vmserrno <> 0
    +     */
    +#ifdef MULTINET
    +    status = -vmserrno;
    +#else
    +    status = -vaxc$errno;
    +#endif /* MULTINET */
    +#else
    +    status = -SOCKET_ERRNO;
    +#endif /* VMS */
    +    return status;
    +}
    +
    +/*	Parse a cardinal value				       parse_cardinal()
    + *	----------------------
    + *
    + * On entry,
    + *	*pp	    points to first character to be interpreted, terminated by
    + *		    non 0:9 character.
    + *	*pstatus    points to status already valid
    + *	maxvalue    gives the largest allowable value.
    + *
    + * On exit,
    + *	*pp	    points to first unread character
    + *	*pstatus    points to status updated iff bad
    + */
    +unsigned int HTCardinal(int *pstatus,
    +			char **pp,
    +			unsigned int max_value)
    +{
    +    unsigned int n;
    +
    +    if ((**pp < '0') || (**pp > '9')) {		/* Null string is error */
    +	*pstatus = -3;		/* No number where one expected */
    +	return 0;
    +    }
    +
    +    n = 0;
    +    while ((**pp >= '0') && (**pp <= '9'))
    +	n = n * 10 + (unsigned) (*((*pp)++) - '0');
    +
    +    if (n > max_value) {
    +	*pstatus = -4;		/* Cardinal outside range */
    +	return 0;
    +    }
    +
    +    return n;
    +}
    +
    +#ifndef DECNET			/* Function only used below for a trace message */
    +/*	Produce a string for an Internet address
    + *	----------------------------------------
    + *
    + *  On exit,
    + *	returns a pointer to a static string which must be copied if
    + *		it is to be kept.
    + */
    +const char *HTInetString(LY_SOCKADDR * soc_A)
    +{
    +#ifdef INET6
    +    static char hostbuf[MAXHOSTNAMELEN];
    +    struct sockaddr *soc_addr = &(soc_A->soc_address);
    +
    +    getnameinfo(soc_addr,
    +		SA_LEN(soc_addr),
    +		hostbuf, (socklen_t) sizeof(hostbuf),
    +		NULL, 0,
    +		NI_NUMERICHOST);
    +    return hostbuf;
    +#else
    +    struct sockaddr_in *soc_in = &(soc_A->soc_in);
    +    static char string[20];
    +
    +    sprintf(string, "%d.%d.%d.%d",
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 0),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 1),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 2),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 3));
    +    return string;
    +#endif /* INET6 */
    +}
    +#endif /* !DECNET */
    +
    +/*	Check whether string is a valid Internet hostname - kw
    + *	-------------------------------------------------
    + *
    + *  Checks whether
    + *  - contains only valid chars for domain names (actually, the
    + *    restrictions are somewhat relaxed),
    + *  - no leading dots or empty segments,
    + *  - no segment starts with '-' or '+' [this protects telnet command],
    + *  - max. length of dot-separated segment <= 63 (RFC 1034,1035),
    + *  - total length <= 254 (if it ends with dot) or 253 (otherwise)
    + *     [an interpretation of RFC 1034,1035, although RFC 1123
    + *      suggests 255 as limit - kw].
    + *
    + *  Note: user (before '@') and port (after ':') components from
    + *      host part of URL should be already stripped (if appropriate)
    + *      from the input string.
    + *
    + *  On exit,
    + *	returns 1 if valid, otherwise 0.
    + */
    +BOOL valid_hostname(char *name)
    +{
    +    int i = 1, iseg = 0;
    +    char *cp = name;
    +
    +    if (!(name && *name))
    +	return NO;
    +    for (; (*cp && i <= 253); cp++, i++) {
    +	if (*cp == '.') {
    +	    if (iseg == 0) {
    +		return NO;
    +	    } else {
    +		iseg = 0;
    +		continue;
    +	    }
    +	} else if (iseg == 0 && (*cp == '-' || *cp == '+')) {
    +	    return NO;
    +	} else if (++iseg > 63) {
    +	    return NO;
    +	}
    +	if (!isalnum(UCH(*cp)) &&
    +	    *cp != '-' && *cp != '_' &&
    +	    *cp != '$' && *cp != '+') {
    +	    return NO;
    +	}
    +    }
    +    return (BOOL) (*cp == '\0' || (*cp == '.' && iseg != 0 && cp[1] == '\0'));
    +}
    +
    +/* for transfer of status from child to parent: */
    +typedef struct _statuses {
    +    size_t rehostentlen;
    +    int h_length;
    +    int child_errno;		/* sometimes useful to pass this on */
    +    int child_h_errno;
    +    BOOL h_errno_valid;
    +} STATUSES;
    +
    +/*
    + *  Function to allow us to be killed with a normal signal (not
    + *  SIGKILL), but don't go through normal libc exit() processing, which
    + *  would screw up parent's stdio.  -BL
    + */
    +#ifdef NSL_FORK
    +static void quench(int sig GCC_UNUSED)
    +{
    +    _exit(2);
    +}
    +#endif
    +
    +int lynx_nsl_status = HT_OK;
    +
    +#define DEBUG_HOSTENT		/* disable in case of problems */
    +#define DEBUG_HOSTENT_CHILD	/* for NSL_FORK, may screw up trace file */
    +
    +/*
    + *  dump_hostent - dumps the contents of a LYNX_HOSTENT to the
    + *  trace log or stderr, including all pointer values, strings, and
    + *  addresses, in a format inspired by gdb's print format. - kw
    + */
    +static void dump_hostent(const char *msgprefix,
    +			 const void *data)
    +{
    +    if (TRACE) {
    +	int i;
    +	char **pcnt;
    +	const LYNX_HOSTENT *phost = data;
    +
    +	CTRACE((tfp, "%s: %p ", msgprefix, (const void *) phost));
    +	if (phost) {
    +	    CTRACE((tfp, "{ h_name = %p", (void *) phost->h_name));
    +	    if (phost->h_name) {
    +		CTRACE((tfp, " \"%s\",", phost->h_name));
    +	    } else {
    +		CTRACE((tfp, ","));
    +	    }
    +	    CTRACE((tfp, "\n\t h_aliases = %p", (void *) phost->h_aliases));
    +	    if (phost->h_aliases) {
    +		CTRACE((tfp, " {"));
    +		for (pcnt = phost->h_aliases; *pcnt; pcnt++) {
    +		    CTRACE((tfp, "%s %p \"%s\"",
    +			    (pcnt == phost->h_aliases ? " " : ", "),
    +			    (void *) *pcnt, *pcnt));
    +		}
    +		CTRACE((tfp, "%s0x0 },\n\t",
    +			(*phost->h_aliases ? ", " : " ")));
    +	    } else {
    +		CTRACE((tfp, ",\n\t"));
    +	    }
    +	    CTRACE((tfp, " h_addrtype = %d,", phost->h_addrtype));
    +	    CTRACE((tfp, " h_length = %d,\n\t", phost->h_length));
    +	    CTRACE((tfp, " h_addr_list = %p", (void *) phost->h_addr_list));
    +	    if (phost->h_addr_list) {
    +		CTRACE((tfp, " {"));
    +		for (pcnt = phost->h_addr_list; *pcnt; pcnt++) {
    +		    CTRACE((tfp, "%s %p",
    +			    (pcnt == phost->h_addr_list ? "" : ","),
    +			    (void *) *pcnt));
    +		    for (i = 0; i < phost->h_length; i++) {
    +			CTRACE((tfp, "%s%d%s", (i == 0 ? " \"" : "."),
    +				(int) *((unsigned char *) (*pcnt) + i),
    +				(i + 1 == phost->h_length ? "\"" : "")));
    +		    }
    +		}
    +		if (*phost->h_addr_list) {
    +		    CTRACE((tfp, ", 0x0 } }"));
    +		} else {
    +		    CTRACE((tfp, " 0x0 } }"));
    +		}
    +	    } else {
    +		CTRACE((tfp, "}"));
    +	    }
    +	}
    +	CTRACE((tfp, "\n"));
    +	fflush(tfp);
    +    }
    +}
    +
    +#ifdef NSL_FORK
    +
    +/*
    + * Even though it is a small amount, we cannot count on reading the whole
    + * struct via a pipe in one read -TD
    + */
    +static unsigned read_bytes(int fd, char *buffer, size_t length)
    +{
    +    unsigned result = 0;
    +
    +    while (length != 0) {
    +	unsigned got = (unsigned) read(fd, buffer, length);
    +
    +	if ((int) got > 0) {
    +	    result += got;
    +	    buffer += got;
    +	    length -= got;
    +	} else {
    +	    break;
    +	}
    +    }
    +    return result;
    +}
    +
    +static unsigned read_hostent(int fd, char *buffer, size_t length)
    +{
    +    unsigned have = read_bytes(fd, buffer, length);
    +
    +    if (have) {
    +	LYNX_HOSTENT *data = (LYNX_HOSTENT *) (void *) buffer;
    +	char *next_char = (char *) data + sizeof(*data);
    +	char **next_ptr = (char **) (void *) next_char;
    +	long offset = 0;
    +	int n;
    +	int num_addrs = 0;
    +	int num_aliases = 0;
    +
    +	if (data->h_addr_list) {
    +	    data->h_addr_list = next_ptr;
    +	    while (next_ptr[num_addrs] != 0) {
    +		++num_addrs;
    +	    }
    +	    next_ptr += (num_addrs + 1);
    +	    next_char += (size_t) (num_addrs + 1) * sizeof(data->h_addr_list[0]);
    +	}
    +
    +	if (data->h_aliases) {
    +	    data->h_aliases = next_ptr;
    +	    while (next_ptr[num_aliases] != 0) {
    +		++num_aliases;
    +	    }
    +	    next_char += (size_t) (num_aliases + 1) * sizeof(data->h_aliases[0]);
    +	}
    +
    +	if (data->h_name) {
    +	    offset = next_char - data->h_name;
    +	    data->h_name = next_char;
    +	} else if (data->h_addr_list) {
    +	    offset = next_char - (char *) data->h_addr_list[0];
    +	} else if (data->h_aliases) {
    +	    offset = next_char - (char *) data->h_aliases[0];
    +	}
    +
    +	if (data->h_addr_list) {
    +	    for (n = 0; n < num_addrs; ++n) {
    +		data->h_addr_list[n] += offset;
    +	    }
    +	}
    +
    +	if (data->h_aliases) {
    +	    for (n = 0; n < num_aliases; ++n) {
    +		data->h_aliases[n] += offset;
    +	    }
    +	}
    +    }
    +
    +    return have;
    +}
    +#endif /* NSL_FORK */
    +
    +/*
    + *  fill_rehostent - copies as much as possible relevant content from
    + *  the LYNX_HOSTENT pointed to by phost to the char buffer given
    + *  by rehostent, subject to maximum output length rehostentsize,
    + *  following pointers and building self-contained output which can be
    + *  cast to a LYNX_HOSTENT. - kw
    + *  See also description of LYGetHostByName.
    + */
    +#if defined(NSL_FORK) || defined(_WINDOWS_NSL)
    +
    +#define REHOSTENT_SIZE 128	/* not bigger than pipe buffer! */
    +
    +typedef struct {
    +    LYNX_HOSTENT h;
    +    char rest[REHOSTENT_SIZE];
    +} AlignedHOSTENT;
    +
    +static size_t fill_rehostent(void **rehostent,
    +			     const LYNX_HOSTENT *phost)
    +{
    +    static const char *this_func = "fill_rehostent";
    +
    +    LYNX_HOSTENT *data = 0;
    +    int num_addrs = 0;
    +    int num_aliases = 0;
    +    char *result = 0;
    +    char *p_next_char;
    +    char **p_next_charptr;
    +    size_t name_len = 0;
    +    size_t need = sizeof(LYNX_HOSTENT);
    +    int n;
    +
    +    if (!phost)
    +	return 0;
    +
    +    if (phost->h_name) {
    +	name_len = strlen(phost->h_name);
    +	need += name_len + 1;
    +    }
    +    if (phost->h_addr_list) {
    +	while (phost->h_addr_list[num_addrs]) {
    +	    num_addrs++;
    +	}
    +	need += ((size_t) num_addrs + 1) * ((size_t) phost->h_length
    +					    + sizeof(phost->h_addr_list[0]));
    +    }
    +    if (phost->h_aliases) {
    +	while (phost->h_aliases[num_aliases]) {
    +	    need += strlen(phost->h_aliases[num_aliases]) + 1;
    +	    num_aliases++;
    +	}
    +	need += ((size_t) num_aliases + 1) * sizeof(phost->h_aliases[0]);
    +    }
    +
    +    if ((result = calloc(need, sizeof(char))) == 0)
    +	  outofmem(__FILE__, this_func);
    +
    +    *rehostent = result;
    +
    +    data = (LYNX_HOSTENT *) (void *) result;
    +
    +    data->h_addrtype = phost->h_addrtype;
    +    data->h_length = phost->h_length;
    +
    +    p_next_char = result + sizeof(LYNX_HOSTENT);
    +
    +    p_next_charptr = (char **) (void *) p_next_char;
    +    if (phost->h_addr_list)
    +	p_next_char += (size_t) (num_addrs + 1) * sizeof(phost->h_addr_list[0]);
    +    if (phost->h_aliases)
    +	p_next_char += (size_t) (num_aliases + 1) * sizeof(phost->h_aliases[0]);
    +
    +    if (phost->h_name) {
    +	data->h_name = p_next_char;
    +	strcpy(p_next_char, phost->h_name);
    +	p_next_char += name_len + 1;
    +    }
    +
    +    if (phost->h_addr_list) {
    +	data->h_addr_list = p_next_charptr;
    +	for (n = 0; n < num_addrs; ++n) {
    +	    MemCpy(p_next_char, phost->h_addr_list[n], phost->h_length);
    +	    *p_next_charptr++ = p_next_char;
    +	    p_next_char += phost->h_length;
    +	}
    +	++p_next_charptr;
    +    }
    +
    +    if (phost->h_aliases) {
    +	data->h_aliases = p_next_charptr;
    +	for (n = 0; n < num_aliases; ++n) {
    +	    strcpy(p_next_char, phost->h_aliases[n]);
    +	    *p_next_charptr++ = p_next_char;
    +	    p_next_char += strlen(phost->h_aliases[n]) + 1;;
    +	}
    +    }
    +    return need;
    +}
    +#endif /* NSL_FORK */
    +
    +/*
    + * This chunk of code is used in both win32 and cygwin.
    + */
    +#if defined(_WINDOWS_NSL)
    +static LYNX_HOSTENT *gbl_phost;	/* Pointer to host - See netdb.h */
    +
    +#if !(defined(__CYGWIN__) && defined(NSL_FORK))
    +static int donelookup;
    +
    +static unsigned long __stdcall _fork_func(void *arg)
    +{
    +    const char *host = (const char *) arg;
    +    static AlignedHOSTENT aligned_full_rehostent;
    +    char *rehostent = (char *) &aligned_full_rehostent;
    +    size_t rehostentlen = 0;
    +
    +#ifdef SH_EX
    +    unsigned long addr;
    +
    +    addr = (unsigned long) inet_addr(host);
    +    if (addr != INADDR_NONE)
    +	gbl_phost = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
    +    else
    +	gbl_phost = gethostbyname(host);
    +#else
    +    gbl_phost = gethostbyname(host);
    +#endif
    +
    +    if (gbl_phost) {
    +	rehostentlen = fill_rehostent((void **) &rehostent, gbl_phost);
    +	if (rehostentlen == 0) {
    +	    gbl_phost = (LYNX_HOSTENT *) NULL;
    +	} else {
    +	    gbl_phost = (LYNX_HOSTENT *) rehostent;
    +	}
    +    }
    +
    +    donelookup = TRUE;
    +    return (unsigned long) (gbl_phost);
    +}
    +#endif /* __CYGWIN__ */
    +#endif /* _WINDOWS_NSL */
    +
    +#ifdef NSL_FORK
    +
    +#ifndef HAVE_H_ERRNO
    +#undef  h_errno
    +#define h_errno my_errno
    +static int my_errno;
    +
    +#else /* we do HAVE_H_ERRNO: */
    +#ifndef h_errno			/* there may be a macro as well as the extern data */
    +extern int h_errno;
    +#endif
    +#endif
    +
    +static BOOL setup_nsl_fork(void (*really) (const char *,
    +					   const char *,
    +					   STATUSES *,
    +					   void **),
    +			   unsigned (*readit) (int, char *, size_t),
    +			   void (*dumpit) (const char *, const void *),
    +			   const char *host,
    +			   const char *port,
    +			   void **rehostent)
    +{
    +    static const char *this_func = "setup_nsl_fork";
    +
    +    STATUSES statuses;
    +
    +    /*
    +     * fork-based gethostbyname() with checks for interrupts.
    +     * - Tom Zerucha (tz@execpc.com) & FM
    +     */
    +    int got_rehostent = 0;
    +
    +#if HAVE_SIGACTION
    +    sigset_t old_sigset;
    +    sigset_t new_sigset;
    +#endif
    +    /*
    +     * Pipe, child pid, status buffers, start time, select() control
    +     * variables.
    +     */
    +    int fpid, waitret;
    +    int pfd[2], selret;
    +    unsigned readret;
    +
    +#ifdef HAVE_TYPE_UNIONWAIT
    +    union wait waitstat;
    +
    +#else
    +    int waitstat = 0;
    +#endif
    +    time_t start_time = time((time_t *) 0);
    +    fd_set readfds;
    +    struct timeval one_second;
    +    long dns_patience = 30;	/* how many seconds will we wait for DNS? */
    +    int child_exited = 0;
    +
    +    memset(&statuses, 0, sizeof(STATUSES));
    +    statuses.h_errno_valid = NO;
    +
    +    /*
    +     * Reap any children that have terminated since last time through.
    +     * This might include children that we killed, then waited with WNOHANG
    +     * before they were actually ready to be reaped.  (Should be max of 1
    +     * in this state, but the loop is safe if waitpid() is implemented
    +     * correctly:  returns 0 when children exist but none have exited; -1
    +     * with errno == ECHILD when no children.) -BL
    +     */
    +    do {
    +	waitret = waitpid(-1, 0, WNOHANG);
    +    } while (waitret > 0 || (waitret == -1 && errno == EINTR));
    +    waitret = 0;
    +
    +    IGNORE_RC(pipe(pfd));
    +
    +#if HAVE_SIGACTION
    +    /*
    +     * Attempt to prevent a rare situation where the child could execute
    +     * the Lynx signal handlers because it gets killed before it even has a
    +     * chance to reset its handlers, resulting in bogus 'Exiting via
    +     * interrupt' message and screen corruption or worse.
    +     * Should that continue to be reported, for systems without
    +     * sigprocmask(), we need to find a different solutions for those.  -
    +     * kw 19990430
    +     */
    +    sigemptyset(&new_sigset);
    +    sigaddset(&new_sigset, SIGTERM);
    +    sigaddset(&new_sigset, SIGINT);
    +#ifndef NOSIGHUP
    +    sigaddset(&new_sigset, SIGHUP);
    +#endif /* NOSIGHUP */
    +#ifdef SIGTSTP
    +    sigaddset(&new_sigset, SIGTSTP);
    +#endif /* SIGTSTP */
    +#ifdef SIGWINCH
    +    sigaddset(&new_sigset, SIGWINCH);
    +#endif /* SIGWINCH */
    +    sigprocmask(SIG_BLOCK, &new_sigset, &old_sigset);
    +#endif /* HAVE_SIGACTION */
    +
    +    if ((fpid = fork()) == 0) {
    +	/*
    +	 * Child - for the long call.
    +	 *
    +	 * Make sure parent can kill us at will.  -BL
    +	 */
    +	(void) signal(SIGTERM, quench);
    +
    +	/*
    +	 * Also make sure the child does not run one of the signal handlers
    +	 * that may have been installed by Lynx if one of those signals
    +	 * occurs.  For example we don't want the child to remove temp
    +	 * files on ^C, let the parent deal with that.  - kw
    +	 */
    +	(void) signal(SIGINT, quench);
    +#ifndef NOSIGHUP
    +	(void) signal(SIGHUP, quench);
    +#endif /* NOSIGHUP */
    +#ifdef SIGTSTP
    +	if (no_suspend)
    +	    (void) signal(SIGTSTP, SIG_IGN);
    +	else
    +	    (void) signal(SIGTSTP, SIG_DFL);
    +#endif /* SIGTSTP */
    +#ifdef SIGWINCH
    +	(void) signal(SIGWINCH, SIG_IGN);
    +#endif /* SIGWINCH */
    +#ifndef __linux__
    +#ifndef DOSPATH
    +	signal(SIGBUS, SIG_DFL);
    +#endif /* DOSPATH */
    +#endif /* !__linux__ */
    +	signal(SIGSEGV, SIG_DFL);
    +	signal(SIGILL, SIG_DFL);
    +
    +#if HAVE_SIGACTION
    +	/* Restore signal mask to whatever it was before the fork. -kw */
    +	sigprocmask(SIG_SETMASK, &old_sigset, NULL);
    +#endif /* HAVE_SIGACTION */
    +
    +	/*
    +	 * Child won't use read side.  -BL
    +	 */
    +	close(pfd[0]);
    +#ifdef HAVE_H_ERRNO
    +	/* to detect cases when it doesn't get set although it should */
    +	h_errno = -2;
    +#endif
    +	set_errno(0);
    +	really(host, port, &statuses, rehostent);
    +	/*
    +	 * Send variables indicating status of lookup to parent.  That
    +	 * includes rehostentlen, which the parent will use as the size for
    +	 * the second read (if > 0).
    +	 */
    +	if (!statuses.child_errno)
    +	    statuses.child_errno = errno;
    +	IGNORE_RC(write(pfd[1], &statuses, sizeof(statuses)));
    +
    +	if (statuses.rehostentlen) {
    +	    /*
    +	     * Return our resulting rehostent through pipe...
    +	     */
    +	    IGNORE_RC(write(pfd[1], *rehostent, statuses.rehostentlen));
    +	    close(pfd[1]);
    +	    _exit(0);
    +	} else {
    +	    /*
    +	     * ...  or return error as exit code.
    +	     */
    +	    _exit(1);
    +	}
    +    }
    +#if HAVE_SIGACTION
    +    /*
    +     * (parent) Restore signal mask to whatever it was before the fork.  -
    +     * kw
    +     */
    +    sigprocmask(SIG_SETMASK, &old_sigset, NULL);
    +#endif /* HAVE_SIGACTION */
    +
    +    /*
    +     * (parent) Wait until lookup finishes, or interrupt, or cycled too
    +     * many times (just in case) -BL
    +     */
    +
    +    close(pfd[1]);		/* parent won't use write side -BL */
    +
    +    if (fpid < 0) {		/* fork failed */
    +	close(pfd[0]);
    +	goto failed;
    +    }
    +
    +    while (child_exited || (long) (time((time_t *) 0) - start_time) < dns_patience) {
    +
    +	FD_ZERO(&readfds);
    +	/*
    +	 * This allows us to abort immediately, not after 1-second timeout,
    +	 * when user hits abort key.  Can't do this when using SLANG (or at
    +	 * least I don't know how), so SLANG users must live with up-to-1s
    +	 * timeout.  -BL
    +	 *
    +	 * Whoops -- we need to make sure stdin is actually selectable!
    +	 * /dev/null isn't, on some systems, which makes some useful Lynx
    +	 * invocations fail.  -BL
    +	 */
    +	{
    +	    int kbd_fd = LYConsoleInputFD(TRUE);
    +
    +	    if (kbd_fd != INVSOC) {
    +		FD_SET(kbd_fd, &readfds);
    +	    }
    +	}
    +
    +	one_second.tv_sec = 1;
    +	one_second.tv_usec = 0;
    +	FD_SET(pfd[0], &readfds);
    +
    +	/*
    +	 * Return when data received, interrupted, or failed.  If nothing
    +	 * is waiting, we sleep for 1 second in select(), to be nice to the
    +	 * system.  -BL
    +	 */
    +#ifdef SOCKS
    +	if (socks_flag)
    +	    selret = Rselect(pfd[0] + 1, &readfds, NULL, NULL, &one_second);
    +	else
    +#endif /* SOCKS */
    +	    selret = select(pfd[0] + 1, &readfds, NULL, NULL, &one_second);
    +
    +	if ((selret > 0) && FD_ISSET(pfd[0], &readfds)) {
    +	    /*
    +	     * First get status, including length of address.  -BL, kw
    +	     */
    +	    readret = read_bytes(pfd[0], (char *) &statuses, sizeof(statuses));
    +	    if (readret == sizeof(statuses)) {
    +		h_errno = statuses.child_h_errno;
    +		set_errno(statuses.child_errno);
    +#ifdef HAVE_H_ERRNO
    +		if (statuses.h_errno_valid) {
    +		    lynx_nsl_status = HT_H_ERRNO_VALID;
    +		    /*
    +		     * If something went wrong in the child process other
    +		     * than normal lookup errors, and it appears that we
    +		     * have enough info to know what went wrong, generate
    +		     * diagnostic output.  ENOMEM observed on linux in
    +		     * processes constrained with ulimit.  It would be too
    +		     * unkind to abort the session, access to local files
    +		     * or through a proxy may still work.  - kw
    +		     */
    +		    if (
    +#ifdef NETDB_INTERNAL		/* linux glibc: defined in netdb.h */
    +			   (errno && h_errno == NETDB_INTERNAL) ||
    +#endif
    +			   (errno == ENOMEM &&
    +			    statuses.rehostentlen == 0 &&
    +		    /* should probably be NETDB_INTERNAL if child
    +		       memory exhausted, but we may find that
    +		       h_errno remains unchanged. - kw */
    +			    h_errno == -2)) {
    +#ifndef MULTINET
    +			HTInetStatus("CHILD gethostbyname");
    +#endif
    +			HTAlert(LYStrerror(statuses.child_errno));
    +			if (errno == ENOMEM) {
    +			    /*
    +			     * Not much point in continuing, right?  Fake a
    +			     * 'z', should shorten pointless guessing
    +			     * cycle.  - kw
    +			     */
    +			    LYFakeZap(YES);
    +			}
    +		    }
    +		}
    +#endif /* HAVE_H_ERRNO */
    +		if (statuses.rehostentlen != 0) {
    +		    /*
    +		     * Then get the full reorganized hostent.  -BL, kw
    +		     */
    +		    if ((*rehostent = malloc(statuses.rehostentlen)) == 0)
    +			outofmem(__FILE__, this_func);
    +		    readret = (*readit) (pfd[0], *rehostent, statuses.rehostentlen);
    +#ifdef DEBUG_HOSTENT
    +		    dumpit("Read from pipe", *rehostent);
    +#endif
    +		    if (readret == statuses.rehostentlen) {
    +			got_rehostent = 1;
    +			lynx_nsl_status = HT_OK;
    +		    } else if (!statuses.h_errno_valid) {
    +			lynx_nsl_status = HT_INTERNAL;
    +		    }
    +		}
    +	    } else {
    +		lynx_nsl_status = HT_ERROR;
    +	    }
    +	    /*
    +	     * Make sure child is cleaned up.  -BL
    +	     */
    +	    if (!child_exited)
    +		waitret = waitpid(fpid, &waitstat, WNOHANG);
    +	    if (!WIFEXITED(waitstat) && !WIFSIGNALED(waitstat)) {
    +		kill(fpid, SIGTERM);
    +		waitret = waitpid(fpid, &waitstat, WNOHANG);
    +	    }
    +	    break;
    +	}
    +
    +	/*
    +	 * Clean up if child exited before & no data received.  -BL
    +	 */
    +	if (child_exited) {
    +	    waitret = waitpid(fpid, &waitstat, WNOHANG);
    +	    break;
    +	}
    +	/*
    +	 * If child exited, loop once more looking for data.  -BL
    +	 */
    +	if ((waitret = waitpid(fpid, &waitstat, WNOHANG)) > 0) {
    +	    /*
    +	     * Data will be arriving right now, so make sure we don't
    +	     * short-circuit out for too many loops, and skip the interrupt
    +	     * check.  -BL
    +	     */
    +	    child_exited = 1;
    +	    continue;
    +	}
    +
    +	/*
    +	 * Abort if interrupt key pressed.
    +	 */
    +	if (HTCheckForInterrupt()) {
    +	    CTRACE((tfp, "%s: INTERRUPTED gethostbyname.\n", this_func));
    +	    kill(fpid, SIGTERM);
    +	    waitpid(fpid, NULL, WNOHANG);
    +	    close(pfd[0]);
    +	    lynx_nsl_status = HT_INTERRUPTED;
    +	    return FALSE;
    +	}
    +    }
    +    close(pfd[0]);
    +    if (waitret <= 0) {
    +	kill(fpid, SIGTERM);
    +	waitret = waitpid(fpid, &waitstat, WNOHANG);
    +    }
    +    if (waitret > 0) {
    +	if (WIFEXITED(waitstat)) {
    +	    CTRACE((tfp,
    +		    "%s: NSL_FORK child %d exited, status 0x%x.\n",
    +		    this_func, (int) waitret, WEXITSTATUS(waitstat)));
    +	} else if (WIFSIGNALED(waitstat)) {
    +	    CTRACE((tfp,
    +		    "%s: NSL_FORK child %d got signal, status 0x%x!\n",
    +		    this_func, (int) waitret, WTERMSIG(waitstat)));
    +#ifdef WCOREDUMP
    +	    if (WCOREDUMP(waitstat)) {
    +		CTRACE((tfp,
    +			"%s: NSL_FORK child %d dumped core!\n",
    +			this_func, (int) waitret));
    +	    }
    +#endif /* WCOREDUMP */
    +	} else if (WIFSTOPPED(waitstat)) {
    +	    CTRACE((tfp,
    +		    "%s: NSL_FORK child %d is stopped, status 0x%x!\n",
    +		    this_func, (int) waitret, WSTOPSIG(waitstat)));
    +	}
    +    }
    +    if (!got_rehostent) {
    +	goto failed;
    +    }
    +    return TRUE;
    +  failed:
    +    return FALSE;
    +}
    +
    +/*
    + * This is called via the child-side of the fork.
    + */
    +static void really_gethostbyname(const char *host,
    +				 const char *port GCC_UNUSED,
    +				 STATUSES * statuses,
    +				 void **rehostent)
    +{
    +    LYNX_HOSTENT *phost;	/* Pointer to host - See netdb.h */
    +    LYNX_HOSTENT *result = 0;
    +
    +    (void) port;
    +
    +    phost = gethostbyname(host);
    +    statuses->rehostentlen = 0;
    +    statuses->child_errno = errno;
    +    statuses->child_h_errno = h_errno;
    +#ifdef HAVE_H_ERRNO
    +    statuses->h_errno_valid = YES;
    +#endif
    +#ifdef MVS
    +    CTRACE((tfp, "really_gethostbyname() returned %d\n", phost));
    +#endif /* MVS */
    +
    +#ifdef DEBUG_HOSTENT_CHILD
    +    dump_hostent("CHILD gethostbyname", phost);
    +#endif
    +    if (OK_HOST(phost)) {
    +	statuses->rehostentlen = fill_rehostent(rehostent, phost);
    +	result = (LYNX_HOSTENT *) (*rehostent);
    +#ifdef DEBUG_HOSTENT_CHILD
    +	dump_hostent("CHILD fill_rehostent", result);
    +#endif
    +    }
    +    if (statuses->rehostentlen <= sizeof(LYNX_HOSTENT) || !OK_HOST(result)) {
    +	statuses->rehostentlen = 0;
    +	statuses->h_length = 0;
    +    } else {
    +	statuses->h_length = result->h_length;
    +#ifdef HAVE_H_ERRNO
    +	if (h_errno == -2)	/* success, but h_errno unchanged? */
    +	    statuses->h_errno_valid = NO;
    +#endif
    +    }
    +}
    +#endif /* NSL_FORK */
    +
    +/*	Resolve an internet hostname, like gethostbyname
    + *	------------------------------------------------
    + *
    + *  On entry,
    + *	host	points to the given host name, not numeric address,
    + *		without colon or port number.
    + *
    + *  On exit,
    + *	returns a pointer to a LYNX_HOSTENT in static storage,
    + *	or NULL in case of error or user interruption.
    + *
    + *  The interface is intended to be exactly the same as for (Unix)
    + *  gethostbyname(), except for the following:
    + *
    + *  If NSL_FORK is not used, the result of gethostbyname is returned
    + *  directly.  Otherwise:
    + *  All lists, addresses, and strings referred to by pointers in the
    + *  returned struct are located, together with the returned struct
    + *  itself, in a buffer of size REHOSTENT_SIZE.  If not everything fits,
    + *  some info is omitted, but the function is careful to still return
    + *  a valid structure, without truncating strings; it tries to return,
    + *  in order of decreasing priority, the first address (h_addr_list[0]), the
    + *  official name (h_name), the additional addresses, then alias names.
    + *
    + *  If NULL is returned, the reason is made available in the global
    + *  variable lynx_nsl_status, with one of the following values:
    + *	HT_INTERRUPTED		Interrupted by user
    + *	HT_NOT_ACCEPTABLE	Hostname detected as invalid
    + *				(also sets h_errno)
    + *	HT_H_ERRNO_VALID	An error occurred, and h_errno holds
    + *				an appropriate value
    + *	HT_ERROR		Resolver error, reason not known
    + *	HT_INTERNAL		Internal error
    + */
    +static LYNX_HOSTENT *LYGetHostByName(char *host)
    +{
    +    static const char *this_func = "LYGetHostByName";
    +
    +#ifdef NSL_FORK
    +    /* for transfer of result between from child to parent: */
    +    LYNX_HOSTENT *rehostent = 0;
    +#endif /* NSL_FORK */
    +
    +    LYNX_HOSTENT *result_phost = NULL;
    +
    +#ifdef __DJGPP__
    +    _resolve_hook = ResolveYield;
    +#endif
    +
    +    if (!host) {
    +	CTRACE((tfp, "%s: Can't parse `NULL'.\n", this_func));
    +	lynx_nsl_status = HT_INTERNAL;
    +	return NULL;
    +    }
    +    CTRACE((tfp, "%s: parsing `%s'.\n", this_func, host));
    +
    +    /*  Could disable this if all our callers already check - kw */
    +    if (HTCheckForInterrupt()) {
    +	CTRACE((tfp, "%s: INTERRUPTED for '%s'.\n", this_func, host));
    +	lynx_nsl_status = HT_INTERRUPTED;
    +	return NULL;
    +    }
    +
    +    if (!valid_hostname(host)) {
    +	lynx_nsl_status = HT_NOT_ACCEPTABLE;
    +#ifdef NO_RECOVERY
    +#ifdef _WINDOWS
    +	WSASetLastError(NO_RECOVERY);
    +#else
    +	h_errno = NO_RECOVERY;
    +#endif
    +#endif
    +	return NULL;
    +    }
    +#ifdef MVS			/* Outstanding problem with crash in MVS gethostbyname */
    +    CTRACE((tfp, "%s: Calling gethostbyname(%s)\n", this_func, host));
    +#endif /* MVS */
    +
    +    CTRACE_FLUSH(tfp);		/* so child messages will not mess up parent log */
    +
    +    lynx_nsl_status = HT_INTERNAL;	/* should be set to something else below */
    +
    +#ifdef NSL_FORK
    +    if (!setup_nsl_fork(really_gethostbyname,
    +			read_hostent,
    +			dump_hostent,
    +			host, NULL, (void **) &rehostent)) {
    +	goto failed;
    +    }
    +    result_phost = rehostent;
    +#else /* Not NSL_FORK: */
    +
    +#ifdef _WINDOWS_NSL
    +    {
    +	HANDLE hThread;
    +	DWORD dwThreadID;
    +
    +#ifndef __CYGWIN__
    +	if (!system_is_NT) {	/* for Windows9x */
    +	    unsigned long t;
    +
    +	    t = (unsigned long) inet_addr(host);
    +	    if (t != INADDR_NONE)
    +		gbl_phost = gethostbyaddr((char *) &t, sizeof(t), AF_INET);
    +	    else
    +		gbl_phost = gethostbyname(host);
    +	} else {		/* for Windows NT */
    +#endif /* !__CYGWIN__ */
    +	    gbl_phost = (LYNX_HOSTENT *) NULL;
    +	    donelookup = FALSE;
    +
    +#if defined(__CYGWIN__) || defined(USE_WINSOCK2_H)
    +	    SetLastError(WSAHOST_NOT_FOUND);
    +#else
    +	    WSASetLastError(WSAHOST_NOT_FOUND);
    +#endif
    +
    +	    hThread = CreateThread(NULL, 4096UL, _fork_func, host, 0UL,
    +				   &dwThreadID);
    +	    if (!hThread)
    +		MessageBox(NULL, "CreateThread",
    +			   "CreateThread Failed", 0L);
    +
    +	    while (!donelookup) {
    +		if (HTCheckForInterrupt()) {
    +		    /* Note that host is a character array and is not freed */
    +		    /* to avoid possible subthread problems: */
    +		    if (!CloseHandle(hThread)) {
    +			MessageBox((void *) NULL,
    +				   "CloseHandle", "CloseHandle Failed", 0L);
    +		    }
    +		    lynx_nsl_status = HT_INTERRUPTED;
    +		    return NULL;
    +		}
    +	    }
    +#ifndef __CYGWIN__
    +	}
    +#endif /* !__CYGWIN__ */
    +	if (gbl_phost) {
    +	    lynx_nsl_status = HT_OK;
    +	    result_phost = gbl_phost;
    +	} else {
    +	    lynx_nsl_status = HT_ERROR;
    +	    goto failed;
    +	}
    +    }
    +
    +#else /* !NSL_FORK, !_WINDOWS_NSL: */
    +    {
    +	LYNX_HOSTENT *phost;
    +
    +	phost = gethostbyname(host);	/* See netdb.h */
    +#ifdef MVS
    +	CTRACE((tfp, "%s: gethostbyname() returned %d\n", this_func, phost));
    +#endif /* MVS */
    +	if (phost) {
    +	    lynx_nsl_status = HT_OK;
    +	    result_phost = phost;
    +	} else {
    +	    lynx_nsl_status = HT_H_ERRNO_VALID;
    +	    goto failed;
    +	}
    +    }
    +#endif /* !NSL_FORK, !_WINDOWS_NSL */
    +#endif /* !NSL_FORK */
    +
    +#ifdef DEBUG_HOSTENT
    +    dump_hostent(this_func, result_phost);
    +    CTRACE((tfp, "%s: Resolved name to a hostent.\n", this_func));
    +#endif
    +
    +    return result_phost;	/* OK */
    +
    +  failed:
    +    CTRACE((tfp, "%s: Can't find internet node name `%s'.\n", this_func, host));
    +    return NULL;
    +}
    +
    +BOOLEAN LYCheckHostByName(char *host)
    +{
    +    LYNX_HOSTENT *data = LYGetHostByName(host);
    +    BOOLEAN result = (data != NULL);
    +
    +    FREE_NSL_FORK(data);
    +    return result;
    +}
    +
    +/*	Parse a network node address and port
    + *	-------------------------------------
    + *
    + *  On entry,
    + *	str	points to a string with a node name or number,
    + *		with optional trailing colon and port number.
    + *	soc_in	points to the binary internet or decnet address field.
    + *
    + *  On exit,
    + *	*soc_in is filled in.  If no port is specified in str, that
    + *		field is left unchanged in *soc_in.
    + */
    +#ifndef INET6
    +static int HTParseInet(SockA *soc_in, const char *str)
    +{
    +    static const char *this_func = "HTParseInet";
    +
    +    char *port;
    +    int dotcount_ip = 0;	/* for dotted decimal IP addr */
    +    char *strptr;
    +    char *host = NULL;
    +
    +    if (!str) {
    +	CTRACE((tfp, "%s: Can't parse `NULL'.\n", this_func));
    +	return -1;
    +    }
    +    CTRACE((tfp, "%s: parsing `%s'.\n", this_func, str));
    +    if (HTCheckForInterrupt()) {
    +	CTRACE((tfp, "%s: INTERRUPTED for '%s'.\n", this_func, str));
    +	return -1;
    +    }
    +    StrAllocCopy(host, str);	/* Make a copy we can mutilate */
    +    /*
    +     * Parse port number if present.
    +     */
    +    if ((port = StrChr(host, ':')) != NULL) {
    +	*port++ = 0;		/* Chop off port */
    +	strptr = port;
    +	if (port[0] >= '0' && port[0] <= '9') {
    +#ifdef UNIX
    +	    soc_in->sin_port = (PortNumber) htons(strtol(port, &strptr, 10));
    +#else /* VMS: */
    +#ifdef DECNET
    +	    soc_in->sdn_objnum = (unsigned char) (strtol(port, &strptr, 10));
    +#else
    +	    soc_in->sin_port = htons((PortNumber) strtol(port, &strptr, 10));
    +#endif /* Decnet */
    +#endif /* Unix vs. VMS */
    +#ifdef SUPPRESS			/* 1. crashes!?!.  2. URL syntax has number not name */
    +	} else {
    +	    struct servent *serv = getservbyname(port, (char *) 0);
    +
    +	    if (serv) {
    +		soc_in->sin_port = serv->s_port;
    +	    } else {
    +		CTRACE((tfp, "TCP: Unknown service %s\n", port));
    +	    }
    +#endif /* SUPPRESS */
    +	}
    +	if (strptr && *strptr != '\0') {
    +	    FREE(host);
    +	    HTAlwaysAlert(NULL, gettext("Address has invalid port"));
    +	    return -1;
    +	}
    +    }
    +#ifdef DECNET
    +    /*
    +     * Read Decnet node name.  @@ Should know about DECnet addresses, but it's
    +     * probably worth waiting until the Phase transition from IV to V.
    +     */
    +    soc_in->sdn_nam.n_len = min(DN_MAXNAML, strlen(host));	/* <=6 in phase 4 */
    +    StrNCpy(soc_in->sdn_nam.n_name, host, soc_in->sdn_nam.n_len + 1);
    +    CTRACE((tfp,
    +	    "DECnet: Parsed address as object number %d on host %.6s...\n",
    +	    soc_in->sdn_objnum, host));
    +#else /* parse Internet host: */
    +
    +    if (*host >= '0' && *host <= '9') {		/* Test for numeric node address: */
    +	strptr = host;
    +	while (*strptr) {
    +	    if (*strptr == '.') {
    +		dotcount_ip++;
    +	    } else if (!isdigit(UCH(*strptr))) {
    +		break;
    +	    }
    +	    strptr++;
    +	}
    +	if (*strptr) {		/* found non-numeric, assume domain name */
    +	    dotcount_ip = 0;
    +	}
    +    }
    +
    +    /*
    +     * Parse host number if present.
    +     */
    +    if (dotcount_ip == 3)	/* Numeric node address: */
    +    {
    +#ifdef GUSI
    +	soc_in->sin_addr = inet_addr(host);	/* See netinet/in.h */
    +#else
    +#ifdef HAVE_INET_ATON
    +	if (!inet_aton(host, &(soc_in->sin_addr))) {
    +	    CTRACE((tfp, "inet_aton(%s) returns error\n", host));
    +	    FREE(host);
    +	    return -1;
    +	}
    +#else
    +	soc_in->sin_addr.s_addr = inet_addr(host);	/* See arpa/inet.h */
    +#endif /* HAVE_INET_ATON */
    +#endif /* GUSI */
    +	FREE(host);
    +    } else {			/* Alphanumeric node name: */
    +
    +#ifdef MVS			/* Outstanding problem with crash in MVS gethostbyname */
    +	CTRACE((tfp, "%s: Calling LYGetHostByName(%s)\n", this_func, host));
    +#endif /* MVS */
    +
    +#ifdef _WINDOWS_NSL
    +	gbl_phost = LYGetHostByName(host);	/* See above */
    +	if (!gbl_phost)
    +	    goto failed;
    +	MemCpy((void *) &soc_in->sin_addr, gbl_phost->h_addr_list[0], gbl_phost->h_length);
    +	FREE(gbl_phost);
    +#else /* !_WINDOWS_NSL */
    +	{
    +	    LYNX_HOSTENT *phost;
    +
    +	    phost = LYGetHostByName(host);	/* See above */
    +
    +	    if (!phost)
    +		goto failed;
    +	    if (phost->h_length != sizeof soc_in->sin_addr) {
    +		HTAlwaysAlert(host, gettext("Address length looks invalid"));
    +	    }
    +	    MemCpy((void *) &soc_in->sin_addr, phost->h_addr_list[0], phost->h_length);
    +	    FREE_NSL_FORK(phost);
    +	}
    +#endif /* _WINDOWS_NSL */
    +
    +	FREE(host);
    +    }				/* Alphanumeric node name */
    +
    +    CTRACE((tfp,
    +	    "%s: Parsed address as port %d, IP address %d.%d.%d.%d\n",
    +	    this_func,
    +	    (int) ntohs(soc_in->sin_port),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 0),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 1),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 2),
    +	    (int) *((unsigned char *) (&soc_in->sin_addr) + 3)));
    +#endif /* Internet vs. Decnet */
    +
    +    return 0;			/* OK */
    +
    +  failed:
    +    CTRACE((tfp, "%s: Can't find internet node name `%s'.\n",
    +	    this_func, host));
    +    FREE(host);
    +    switch (lynx_nsl_status) {
    +    case HT_NOT_ACCEPTABLE:
    +    case HT_INTERRUPTED:
    +	return lynx_nsl_status;
    +    default:
    +	return -1;
    +    }
    +}
    +#endif /* !INET6 */
    +
    +#ifdef INET6
    +
    +static void dump_addrinfo(const char *tag, const void *data)
    +{
    +    const LYNX_ADDRINFO *res;
    +    int count = 0;
    +
    +    CTRACE((tfp, "dump_addrinfo %s:\n", tag));
    +    for (res = (const LYNX_ADDRINFO *) data; res; res = res->ai_next) {
    +	char hostbuf[1024], portbuf[1024];
    +
    +	++count;
    +	hostbuf[0] = '\0';
    +	portbuf[0] = '\0';
    +	getnameinfo(res->ai_addr, res->ai_addrlen,
    +		    hostbuf, (socklen_t) sizeof(hostbuf),
    +		    portbuf, (socklen_t) sizeof(portbuf),
    +		    NI_NUMERICHOST | NI_NUMERICSERV);
    +
    +	CTRACE((tfp,
    +		"\t[%d] family %d, socktype %d, protocol %d addr %s port %s\n",
    +		count,
    +		res->ai_family,
    +		res->ai_socktype,
    +		res->ai_protocol,
    +		hostbuf,
    +		portbuf));
    +    }
    +}
    +
    +#if defined(NSL_FORK)
    +
    +/*
    + * Copy the relevant information (on the child-side).
    + */
    +static size_t fill_addrinfo(void **buffer,
    +			    const LYNX_ADDRINFO *phost)
    +{
    +    static const char *this_func = "fill_addinfo";
    +
    +    const LYNX_ADDRINFO *q;
    +    LYNX_ADDRINFO *actual;
    +    LYNX_ADDRINFO *result;
    +    int count = 0;
    +    int limit = 0;
    +    size_t need = sizeof(LYNX_ADDRINFO);
    +    char *heap;
    +
    +    CTRACE((tfp, "filladdr_info %p\n", (const void *) phost));
    +    for (q = phost; q != 0; q = q->ai_next) {
    +	++limit;
    +	need += phost->ai_addrlen;
    +	need += sizeof(LYNX_ADDRINFO);
    +    }
    +    CTRACE((tfp, "...fill_addrinfo %d:%lu\n", limit, (unsigned long) need));
    +
    +    if ((result = (LYNX_ADDRINFO *) calloc(1, need)) == 0)
    +	outofmem(__FILE__, this_func);
    +
    +    *buffer = actual = result;
    +    heap = ((char *) actual) + ((size_t) limit * sizeof(LYNX_ADDRINFO));
    +
    +    for (count = 0; count < limit; ++count) {
    +
    +	/*
    +	 * copying the whole structure seems simpler but because it is not
    +	 * packed, uninitialized gaps make it hard to analyse with valgrind.
    +	 */
    +	/* *INDENT-EQLS* */
    +	actual->ai_flags    = phost->ai_flags;
    +	actual->ai_family   = phost->ai_family;
    +	actual->ai_socktype = phost->ai_socktype;
    +	actual->ai_protocol = phost->ai_protocol;
    +	actual->ai_addrlen  = phost->ai_addrlen;
    +	actual->ai_addr     = (struct sockaddr *) (void *) heap;
    +
    +	MemCpy(heap, phost->ai_addr, phost->ai_addrlen);
    +	heap += phost->ai_addrlen;
    +
    +	phost = phost->ai_next;
    +
    +	actual->ai_next = ((count + 1 < limit)
    +			   ? (actual + 1)
    +			   : 0);
    +	++actual;
    +    }
    +    return (size_t) (heap - (char *) result);
    +}
    +
    +/*
    + * Read data, repair pointers as done in fill_addrinfo().
    + */
    +static unsigned read_addrinfo(int fd, char *buffer, size_t length)
    +{
    +    unsigned result = read_bytes(fd, buffer, length);
    +    LYNX_ADDRINFO *actual = (LYNX_ADDRINFO *) (void *) buffer;
    +    LYNX_ADDRINFO *res;
    +    int count = 0;
    +    int limit;
    +    char *heap;
    +
    +    CTRACE((tfp, "read_addrinfo length %lu\n", (unsigned long) length));
    +    for (limit = 0; actual[limit].ai_next; ++limit) {
    +    }
    +    ++limit;
    +    heap = (char *) (actual + limit);
    +    CTRACE((tfp, "...read_addrinfo %d items\n", limit));
    +
    +    for (res = actual, count = 0; count < limit; ++count) {
    +	res->ai_addr = (struct sockaddr *) (void *) heap;
    +	heap += res->ai_addrlen;
    +	if (count < limit - 1) {
    +	    res->ai_next = (res + 1);
    +	    ++res;
    +	} else {
    +	    res->ai_next = 0;
    +	}
    +    }
    +
    +#ifdef DEBUG_HOSTENT
    +    dump_addrinfo("read_addrinfo", buffer);
    +#endif
    +    return result;
    +}
    +
    +/*
    + * This is called via the child-side of the fork.
    + */
    +static void really_getaddrinfo(const char *host,
    +			       const char *port,
    +			       STATUSES * statuses,
    +			       void **result)
    +{
    +    LYNX_ADDRINFO hints, *res = 0;
    +    int error;
    +
    +    memset(&hints, 0, sizeof(hints));
    +    hints.ai_family = PF_UNSPEC;
    +    hints.ai_socktype = SOCK_STREAM;
    +    error = getaddrinfo(host, port, &hints, &res);
    +    if (error) {
    +	CTRACE((tfp, "HTGetAddrInfo: getaddrinfo(%s, %s): %s\n", host, port,
    +		gai_strerror(error)));
    +    } else {
    +	statuses->child_errno = errno;
    +	statuses->child_h_errno = h_errno;
    +#ifdef HAVE_H_ERRNO
    +	statuses->h_errno_valid = YES;
    +#endif
    +
    +#ifdef DEBUG_HOSTENT_CHILD
    +	dump_addrinfo("CHILD getaddrinfo", res);
    +#endif
    +	statuses->rehostentlen = fill_addrinfo(result, res);
    +#ifdef DEBUG_HOSTENT_CHILD
    +	dump_addrinfo("CHILD fill_addrinfo", (const LYNX_ADDRINFO *) (*result));
    +#endif
    +	if (statuses->rehostentlen <= sizeof(LYNX_ADDRINFO) || (*result) == NULL) {
    +	    statuses->rehostentlen = 0;
    +	    statuses->h_length = 0;
    +	} else {
    +	    statuses->h_length = (int) (((LYNX_ADDRINFO *) (*result))->ai_addrlen);
    +	}
    +	freeaddrinfo(res);
    +    }
    +}
    +#endif /* NSL_FORK */
    +
    +static LYNX_ADDRINFO *HTGetAddrInfo(const char *str,
    +				    const int defport)
    +{
    +#ifdef NSL_FORK
    +    /* for transfer of result between from child to parent: */
    +    void *readdrinfo = 0;
    +
    +#else
    +    LYNX_ADDRINFO hints;
    +    int error;
    +#endif /* NSL_FORK */
    +    LYNX_ADDRINFO *res;
    +    char *p;
    +    char *s = NULL;
    +    char *host, *port;
    +    char pbuf[80];
    +
    +    StrAllocCopy(s, str);
    +
    +    if (s[0] == '[' && (p = StrChr(s, ']')) != NULL) {
    +	*p++ = '\0';
    +	host = s + 1;
    +    } else {
    +	p = s;
    +	host = &s[0];
    +    }
    +    port = strrchr(p, ':');
    +    if (port) {
    +	*port++ = '\0';
    +    } else {
    +	sprintf(pbuf, "%d", defport);
    +	port = pbuf;
    +    }
    +
    +#ifdef NSL_FORK
    +    if (setup_nsl_fork(really_getaddrinfo,
    +		       read_addrinfo,
    +		       dump_addrinfo,
    +		       host, port, &readdrinfo)) {
    +	res = readdrinfo;
    +    } else {
    +	res = NULL;
    +    }
    +#else
    +    memset(&hints, 0, sizeof(hints));
    +    hints.ai_family = PF_UNSPEC;
    +    hints.ai_socktype = SOCK_STREAM;
    +    error = getaddrinfo(host, port, &hints, &res);
    +    if (error || !res) {
    +	CTRACE((tfp, "HTGetAddrInfo: getaddrinfo(%s, %s): %s\n", host, port,
    +		gai_strerror(error)));
    +	res = NULL;
    +    }
    +#endif
    +
    +    free(s);
    +#ifdef DEBUG_HOSTENT
    +    dump_addrinfo("HTGetAddrInfo", res);
    +#endif
    +    return res;
    +}
    +
    +BOOLEAN HTCheckAddrInfo(const char *str, const int defport)
    +{
    +    LYNX_ADDRINFO *data = HTGetAddrInfo(str, defport);
    +    BOOLEAN result = (data != 0);
    +
    +    FREE_NSL_FORK(data);
    +    return result;
    +}
    +#endif /* INET6 */
    +
    +#ifdef LY_FIND_LEAKS
    +/*	Free our name for the host on which we are - FM
    + *	-------------------------------------------
    + *
    + */
    +static void free_HTTCP_hostname(void)
    +{
    +    FREE(hostname);
    +}
    +#endif /* LY_FIND_LEAKS */
    +
    +/*	Derive the name of the host on which we are
    + *	-------------------------------------------
    + *
    + */
    +static void get_host_details(void)
    +{
    +    char name[MAXHOSTNAMELEN + 1];	/* The name of this host */
    +
    +#ifdef UCX
    +    char *domain_name;		/* The name of this host domain */
    +#endif /* UCX */
    +#ifdef NEED_HOST_ADDRESS	/* no -- needs name server! */
    +#ifdef INET6
    +    LYNX_ADDRINFO hints, *res;
    +    int error;
    +
    +#else
    +    LYNX_HOSTENT *phost;	/* Pointer to host -- See netdb.h */
    +#endif /* INET6 */
    +#endif /* NEED_HOST_ADDRESS */
    +    size_t namelength = sizeof(name);
    +
    +    if (hostname)
    +	return;			/* Already done */
    +    gethostname(name, namelength);	/* Without domain */
    +    StrAllocCopy(hostname, name);
    +#ifdef LY_FIND_LEAKS
    +    atexit(free_HTTCP_hostname);
    +#endif
    +#ifdef UCX
    +    /*
    +     * UCX doesn't give the complete domain name.  Get rest from UCX$BIND_DOM
    +     * logical.
    +     */
    +    if (StrChr(hostname, '.') == NULL) {	/* Not full address */
    +	domain_name = LYGetEnv("UCX$BIND_DOMAIN");
    +	if (domain_name == NULL)
    +	    domain_name = LYGetEnv("TCPIP$BIND_DOMAIN");
    +	if (domain_name != NULL) {
    +	    StrAllocCat(hostname, ".");
    +	    StrAllocCat(hostname, domain_name);
    +	}
    +    }
    +#endif /* UCX */
    +    CTRACE((tfp, "TCP: Local host name is %s\n", hostname));
    +
    +#ifndef DECNET			/* Decnet ain't got no damn name server 8#OO */
    +#ifdef NEED_HOST_ADDRESS	/* no -- needs name server! */
    +#ifdef INET6
    +    memset(&hints, 0, sizeof(hints));
    +    hints.ai_family = PF_UNSPEC;
    +    hints.ai_socktype = SOCK_STREAM;
    +    hints.ai_flags = AI_CANONNAME;
    +    error = getaddrinfo(name, NULL, &hints, &res);
    +    if (error || !res || !res->ai_canonname) {
    +	CTRACE((tfp, "TCP: %s: `%s'\n", gai_strerror(error), name));
    +	if (res)
    +	    freeaddrinfo(res);
    +	return;			/* Fail! */
    +    }
    +    StrAllocCopy(hostname, res->ai_canonname);
    +    MemCpy(&HTHostAddress, res->ai_addr, res->ai_addrlen);
    +    freeaddrinfo(res);
    +#else
    +    phost = gethostbyname(name);	/* See netdb.h */
    +    if (!OK_HOST(phost)) {
    +	CTRACE((tfp,
    +		"TCP: Can't find my own internet node address for `%s'!!\n",
    +		name));
    +	return;			/* Fail! */
    +    }
    +    StrAllocCopy(hostname, phost->h_name);
    +    MemCpy(&HTHostAddress, &phost->h_addr_list[0], phost->h_length);
    +#endif /* INET6 */
    +    CTRACE((tfp, "     Name server says that I am `%s' = %s\n",
    +	    hostname, HTInetString(&HTHostAddress)));
    +#endif /* NEED_HOST_ADDRESS */
    +
    +#endif /* !DECNET */
    +}
    +
    +const char *HTHostName(void)
    +{
    +    get_host_details();
    +    return hostname;
    +}
    +
    +#ifdef _WINDOWS
    +#define SET_EINTR WSASetLastError(EINTR)
    +#else
    +#define SET_EINTR SOCKET_ERRNO = EINTR
    +#endif
    +
    +static BOOL HTWasInterrupted(int *status)
    +{
    +    BOOL result = FALSE;
    +
    +    if (HTCheckForInterrupt()) {
    +	result = TRUE;
    +	*status = HT_INTERRUPTED;
    +	SET_EINTR;
    +    }
    +    return result;
    +}
    +
    +#define TRIES_PER_SECOND 10
    +
    +/*
    + * Set the select-timeout to 0.1 seconds.
    + */
    +static void set_timeout(struct timeval *timeoutp)
    +{
    +    timeoutp->tv_sec = 0;
    +    timeoutp->tv_usec = 100000;
    +}
    +
    +#ifndef MULTINET		/* SOCKET_ERRNO != errno ? */
    +#if !defined(UCX) || !defined(VAXC)	/* errno not modifiable ? */
    +#define SOCKET_DEBUG_TRACE	/* show errno status after some system calls */
    +#endif /* UCX && VAXC */
    +#endif /* MULTINET */
    +/*
    + *  Interruptible connect as implemented for Mosaic by Marc Andreesen
    + *  and hacked in for Lynx years ago by Lou Montulli, and further
    + *  modified over the years by numerous Lynx lovers. - FM
    + */
    +int HTDoConnect(const char *url,
    +		const char *protocol,
    +		int default_port,
    +		int *s)
    +{
    +    char *socks5_host = NULL;
    +    unsigned socks5_host_len = 0;
    +    int socks5_port;
    +    const char *socks5_orig_url;
    +    char *socks5_new_url = NULL;
    +    char *socks5_protocol = NULL;
    +    int status = HT_OK;
    +    char *line = NULL;
    +    char *p1 = NULL;
    +    char *host = NULL;
    +    char const *emsg;
    +
    +#ifdef INET6
    +    LYNX_ADDRINFO *res = 0, *res0 = 0;
    +
    +#else
    +    struct sockaddr_in sock_A;
    +    struct sockaddr_in *soc_in = &sock_A;
    +#endif
    +
    +    *s = -1;			/* nothing is open yet */
    +
    +    /* In case of a present SOCKS5 proxy, marshal */
    +    if (socks5_proxy == NULL)
    +	socks5_proxy = LYGetEnv("SOCKS5_PROXY");
    +    if ((socks5_orig_url = socks5_proxy) != NULL) {
    +	int xport;
    +
    +	xport = default_port;
    +	socks5_orig_url = url;
    +	StrAllocCopy(socks5_new_url, url);
    +
    +	/* Get node name and optional port number of wanted URL */
    +	if ((p1 = HTParse(socks5_new_url, "", PARSE_HOST)) != NULL) {
    +	    StrAllocCopy(socks5_host, p1);
    +	    strip_userid(socks5_host, FALSE);
    +	    FREE(p1);
    +	}
    +
    +	if (isEmpty(socks5_host)) {
    +	    emsg = gettext("SOCKS5: no hostname found.");
    +	    status = HT_ERROR;
    +	    goto report_error;
    +	}
    +
    +	if (strlen(socks5_host) > 255) {
    +	    emsg = gettext("SOCKS5: hostname too long.");
    +	    status = HT_ERROR;
    +	    goto report_error;
    +	}
    +	socks5_host_len = (unsigned) strlen(socks5_host);
    +
    +	if (HTParsePort(socks5_new_url, &socks5_port) == NULL)
    +	    socks5_port = xport;
    +	FREE(socks5_new_url);
    +
    +	/* And switch over to our SOCKS5 config; in order to embed that into
    +	 * lynx environment, prepend protocol prefix */
    +	default_port = 1080;	/* RFC 1928 */
    +	HTSACat(&socks5_new_url, "socks://");
    +	HTSACat(&socks5_new_url, socks5_proxy);
    +	url = socks5_new_url;
    +
    +	HTSprintf0(&socks5_protocol,
    +		   gettext("(for %s at %s) SOCKS5"),
    +		   protocol, socks5_host);
    +	protocol = socks5_protocol;
    +    }
    +#ifndef INET6
    +    /*
    +     * Set up defaults.
    +     */
    +    memset(soc_in, 0, sizeof(*soc_in));
    +    soc_in->sin_family = AF_INET;
    +    soc_in->sin_port = htons((PortNumber) default_port);
    +#endif /* INET6 */
    +
    +    /*
    +     * Get node name and optional port number.
    +     */
    +    p1 = HTParse(url, "", PARSE_HOST);
    +    StrAllocCopy(host, p1);
    +    strip_userid(host, FALSE);
    +    FREE(p1);
    +
    +    HTSprintf0(&line, "%s%s", WWW_FIND_MESSAGE, host);
    +    _HTProgress(line);
    +#ifdef INET6
    +    /* HTParseInet() is useless! */
    +    res0 = HTGetAddrInfo(host, default_port);
    +    if (res0 == NULL) {
    +	HTSprintf0(&line, gettext("Unable to locate remote host %s."), host);
    +	_HTProgress(line);
    +	status = HT_NO_DATA;
    +	goto cleanup;
    +    }
    +#else
    +    status = HTParseInet(soc_in, host);
    +    if (status) {
    +	if (status != HT_INTERRUPTED) {
    +	    if (status == HT_NOT_ACCEPTABLE) {
    +		/* Not HTProgress, so warning won't be overwritten immediately;
    +		 * but not HTAlert, because typically there will be other
    +		 * alerts from the callers.  - kw
    +		 */
    +		HTUserMsg2(gettext("Invalid hostname %s"), host);
    +	    } else {
    +		HTSprintf0(&line,
    +			   gettext("Unable to locate remote host %s."), host);
    +		_HTProgress(line);
    +	    }
    +	    status = HT_NO_DATA;
    +	}
    +	goto cleanup;
    +    }
    +#endif /* INET6 */
    +
    +    HTSprintf0(&line, gettext("Making %s connection to %s"), protocol, host);
    +    _HTProgress(line);
    +
    +    /*
    +     * Now, let's get a socket set up from the server for the data.
    +     */
    +#ifndef INET6
    +    *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    +    if (*s == -1) {
    +	status = HT_NO_DATA;
    +	emsg = gettext("socket failed.");
    +	goto report_error;
    +    }
    +#else
    +    for (res = res0; res; res = res->ai_next) {
    +	*s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    +	if (*s == -1) {
    +	    char hostbuf[1024], portbuf[1024];
    +
    +	    getnameinfo(res->ai_addr, res->ai_addrlen,
    +			hostbuf, (socklen_t) sizeof(hostbuf),
    +			portbuf, (socklen_t) sizeof(portbuf),
    +			NI_NUMERICHOST | NI_NUMERICSERV);
    +	    HTSprintf0(&line,
    +		       gettext("socket failed: family %d addr %s port %s."),
    +		       res->ai_family, hostbuf, portbuf);
    +	    _HTProgress(line);
    +	    continue;
    +	}
    +#endif /* INET6 */
    +
    +#if !defined(DOSPATH) || defined(__DJGPP__)
    +#if !defined(NO_IOCTL) || defined(USE_FCNTL)
    +	/*
    +	 * Make the socket non-blocking, so the connect can be canceled.  This
    +	 * means that when we issue the connect we should NOT have to wait for
    +	 * the accept on the other end.
    +	 */
    +	{
    +#ifdef USE_FCNTL
    +	    int ret = fcntl(*s, F_SETFL, O_NONBLOCK);
    +
    +#else
    +	    int val = 1;
    +	    int ret = IOCTL(*s, FIONBIO, &val);
    +#endif /* USE_FCNTL */
    +	    if (ret == -1)
    +		_HTProgress(gettext("Could not make connection non-blocking."));
    +	}
    +#endif /* !NO_IOCTL || USE_FCNTL */
    +#endif /* !DOSPATH || __DJGPP__ */
    +
    +	/*
    +	 * Issue the connect.  Since the server can't do an instantaneous
    +	 * accept and we are non-blocking, this will almost certainly return a
    +	 * negative status.
    +	 */
    +#ifdef SOCKS
    +	if (socks_flag) {
    +#ifdef INET6
    +	    status = Rconnect(*s, res->ai_addr, res->ai_addrlen);
    +#else
    +	    status = Rconnect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A));
    +#endif /* INET6 */
    +	} else
    +#endif /* SOCKS */
    +#ifdef INET6
    +	    status = connect(*s, res->ai_addr, res->ai_addrlen);
    +#else
    +	    status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A));
    +#endif /* INET6 */
    +
    +	/*
    +	 * According to the Sun man page for connect:
    +	 *  EINPROGRESS         The socket is non-blocking and the  con-
    +	 *                      nection cannot be completed immediately.
    +	 *                      It is possible to select(2) for  comple-
    +	 *                      tion  by  selecting the socket for writ-
    +	 *                      ing.
    +	 * According to the Motorola SVR4 man page for connect:
    +	 *  EAGAIN              The socket is non-blocking and the  con-
    +	 *                      nection cannot be completed immediately.
    +	 *                      It is possible to select for  completion
    +	 *                      by  selecting  the  socket  for writing.
    +	 *                      However, this is only  possible  if  the
    +	 *                      socket  STREAMS  module  is  the topmost
    +	 *                      module on  the  protocol  stack  with  a
    +	 *                      write  service  procedure.  This will be
    +	 *                      the normal case.
    +	 */
    +	CTRACE((tfp, "connect(): status: %d, SOCK_ERRNO: %d\n", status, SOCKET_ERRNO));
    +
    +	if ((status < 0) &&
    +	    (SOCKET_ERRNO == EINPROGRESS
    +#ifdef EAGAIN
    +	     || SOCKET_ERRNO == EAGAIN
    +#endif
    +	    )) {
    +	    struct timeval select_timeout;
    +	    int ret;
    +	    int tries = 0;
    +
    +#ifdef SOCKET_DEBUG_TRACE
    +	    if (SOCKET_ERRNO != EINPROGRESS) {
    +		HTInetStatus("this socket's first connect");
    +	    }
    +#endif /* SOCKET_DEBUG_TRACE */
    +	    ret = 0;
    +	    while (ret <= 0) {
    +		fd_set writefds;
    +
    +		/*
    +		 * Protect against an infinite loop.
    +		 */
    +		if ((tries++ / TRIES_PER_SECOND) >= connect_timeout) {
    +		    HTAlert(gettext("Connection failed (too many retries)."));
    +#ifdef INET6
    +#ifndef NSL_FORK
    +		    if (res0)
    +			freeaddrinfo(res0);
    +#endif
    +#endif /* INET6 */
    +		    status = HT_NO_DATA;
    +		    goto cleanup;
    +		}
    +		set_timeout(&select_timeout);
    +		FD_ZERO(&writefds);
    +		FD_SET((LYNX_FD) *s, &writefds);
    +#ifdef SOCKS
    +		if (socks_flag)
    +		    ret = Rselect(*s + 1, NULL,
    +				  &writefds, NULL, &select_timeout);
    +		else
    +#endif /* SOCKS */
    +		    ret = select(*s + 1,
    +				 NULL,
    +				 &writefds,
    +				 NULL,
    +				 &select_timeout);
    +
    +#ifdef SOCKET_DEBUG_TRACE
    +		if (tries == 1) {
    +		    if (SOCKET_ERRNO != EINPROGRESS) {
    +			HTInetStatus("this socket's first select");
    +		    }
    +		}
    +#endif /* SOCKET_DEBUG_TRACE */
    +		/*
    +		 * If we suspend, then it is possible that select will be
    +		 * interrupted.  Allow for this possibility.  - JED
    +		 */
    +		if ((ret == -1) && (SOCKET_ERRNO == EINTR))
    +		    continue;
    +
    +#ifdef SOCKET_DEBUG_TRACE
    +		if (ret < 0) {
    +		    HTInetStatus("failed select");
    +		}
    +#endif /* SOCKET_DEBUG_TRACE */
    +		/*
    +		 * Again according to the Sun and Motorola man pages for
    +		 * connect:
    +		 *  EALREADY    The socket is non-blocking and a  previ-
    +		 *              ous  connection attempt has not yet been
    +		 *              completed.
    +		 * Thus if the SOCKET_ERRNO is NOT EALREADY we have a real
    +		 * error, and should break out here and return that error.
    +		 * Otherwise if it is EALREADY keep on trying to complete the
    +		 * connection.
    +		 */
    +		if ((ret < 0) && (SOCKET_ERRNO != EALREADY)) {
    +		    status = ret;
    +		    break;
    +		} else if (((SOCKET_ERRNO == EALREADY) ||
    +			    (SOCKET_ERRNO == EINPROGRESS)) &&
    +			   HTCheckForInterrupt()) {
    +		    status = HT_INTERRUPTED;
    +		    break;
    +		} else if (ret > 0) {
    +		    /*
    +		     * Extra check here for connection success, if we try to
    +		     * connect again, and get EISCONN, it means we have a
    +		     * successful connection.  But don't check with SOCKS.
    +		     */
    +#ifdef SOCKS
    +		    if (socks_flag) {
    +			status = 0;
    +		    } else {
    +#endif /* SOCKS */
    +#ifdef INET6
    +			status = connect(*s, res->ai_addr, res->ai_addrlen);
    +#else
    +			status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A));
    +#endif /* INET6 */
    +#ifdef UCX
    +			/*
    +			 * A UCX feature:  Instead of returning EISCONN UCX
    +			 * returns EADDRINUSE.  Test for this status also.
    +			 */
    +			if ((status < 0) && ((SOCKET_ERRNO == EISCONN) ||
    +					     (SOCKET_ERRNO == EADDRINUSE)))
    +#else
    +			if ((status < 0) && (SOCKET_ERRNO == EISCONN))
    +#endif /* UCX */
    +			{
    +			    status = 0;
    +			}
    +
    +			if (status && (SOCKET_ERRNO == EALREADY))	/* new stuff LJM */
    +			    ret = 0;	/* keep going */
    +			else {
    +#ifdef SOCKET_DEBUG_TRACE
    +			    if (status < 0) {
    +				HTInetStatus("confirm-ready connect");
    +			    }
    +#endif /* SOCKET_DEBUG_TRACE */
    +			    break;
    +			}
    +#ifdef SOCKS
    +		    }
    +#endif /* SOCKS */
    +		}
    +#ifdef SOCKS
    +		else if (!socks_flag)
    +#else
    +		else
    +#endif /* SOCKS */
    +		{
    +		    /*
    +		     * The select says we aren't ready yet.  Try to connect
    +		     * again to make sure.  If we don't get EALREADY or
    +		     * EISCONN, something has gone wrong.  Break out and report
    +		     * it.
    +		     *
    +		     * For some reason, SVR4 returns EAGAIN here instead of
    +		     * EALREADY, even though the man page says it should be
    +		     * EALREADY.
    +		     *
    +		     * For some reason, UCX pre 3 apparently returns errno =
    +		     * 18242 instead of EALREADY or EISCONN.
    +		     */
    +#ifdef INET6
    +		    status = connect(*s, res->ai_addr, res->ai_addrlen);
    +#else
    +		    status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A));
    +#endif /* INET6 */
    +		    if ((status < 0) &&
    +			(SOCKET_ERRNO != EALREADY
    +#ifdef EAGAIN
    +			 && SOCKET_ERRNO != EAGAIN
    +#endif
    +			) &&
    +#ifdef UCX
    +			(SOCKET_ERRNO != 18242) &&
    +#endif /* UCX */
    +			(SOCKET_ERRNO != EISCONN)) {
    +#ifdef SOCKET_DEBUG_TRACE
    +			HTInetStatus("confirm-not-ready connect");
    +#endif /* SOCKET_DEBUG_TRACE */
    +			break;
    +		    }
    +		}
    +		if (HTWasInterrupted(&status)) {
    +		    CTRACE((tfp, "*** INTERRUPTED in middle of connect.\n"));
    +		    break;
    +		}
    +	    }
    +	}
    +#ifdef SOCKET_DEBUG_TRACE
    +	else if (status < 0) {
    +	    HTInetStatus("this socket's first and only connect");
    +	}
    +#endif /* SOCKET_DEBUG_TRACE */
    +#ifdef INET6
    +	if (status < 0) {
    +	    NETCLOSE(*s);
    +	    *s = -1;
    +	    if (status != HT_INTERRUPTED)
    +		continue;
    +	}
    +	break;
    +    }
    +#endif /* INET6 */
    +
    +#ifdef INET6
    +    if (*s < 0)
    +#else
    +    if (status < 0)
    +#endif /* INET6 */
    +    {
    +	/*
    +	 * The connect attempt failed or was interrupted, so close up the
    +	 * socket.
    +	 */
    +	NETCLOSE(*s);
    +    }
    +#if !defined(DOSPATH) || defined(__DJGPP__)
    +#if !defined(NO_IOCTL) || defined(USE_FCNTL)
    +    else {
    +	/*
    +	 * Make the socket blocking again on good connect.
    +	 */
    +#ifdef USE_FCNTL
    +	int ret = fcntl(*s, F_SETFL, 0);
    +
    +#else
    +	int val = 0;
    +	int ret = IOCTL(*s, FIONBIO, &val);
    +#endif /* USE_FCNTL */
    +	if (ret == -1)
    +	    _HTProgress(gettext("Could not restore socket to blocking."));
    +    }
    +#endif /* !NO_IOCTL || USE_FCNTL */
    +#endif /* !DOSPATH || __DJGPP__ */
    +
    +#ifdef INET6
    +#ifdef NSL_FORK
    +    FREE_NSL_FORK(res0);
    +#else
    +    if (res0)
    +	freeaddrinfo(res0);
    +#endif
    +#endif /* INET6 */
    +
    +    /* Now if this was a SOCKS5 proxy connection, go for the real one */
    +    if (status >= 0 && socks5_orig_url != NULL) {
    +	unsigned char pbuf[4 + 1 + 255 + 2];
    +	unsigned i;
    +
    +	/* RFC 1928: version identifier/method selection message */
    +	pbuf[0] = 0x05;		/* VER: protocol version: X'05' */
    +	pbuf[1] = 0x01;		/* NMETHODS: 1 */
    +	pbuf[2] = 0x00;		/* METHOD: X'00' NO AUTHENTICATION REQUIRED */
    +	if (NETWRITE(*s, (char *) pbuf, 3) != 3) {
    +	    goto report_system_err;
    +	} else if (HTDoRead(*s, pbuf, 2) != 2) {
    +	    goto report_system_err;
    +	} else if (pbuf[0] != 0x05 || pbuf[1] != 0x00) {
    +	    goto report_unexpected_reply;
    +	}
    +
    +	/* RFC 1928: CONNECT request */
    +	HTSprintf0(&line, gettext("SOCKS5: connecting to %s"), socks5_host);
    +	_HTProgress(line);
    +	pbuf[0] = 0x05;		/* VER: protocol version: X'05' */
    +	pbuf[1] = 0x01;		/* CMD: CONNECT X'01' */
    +	pbuf[2] = 0x00;		/* RESERVED */
    +	pbuf[3] = 0x03;		/* ATYP: domain name */
    +	pbuf[4] = (unsigned char) socks5_host_len;
    +	memcpy(&pbuf[i = 5], socks5_host, socks5_host_len);
    +	i += socks5_host_len;
    +	/* C99 */  {
    +	    unsigned short x;	/* XXX 16-bit? */
    +
    +	    x = htons((PortNumber) socks5_port);
    +	    memcpy(&pbuf[i], (unsigned char *) &x, sizeof x);
    +	    i += (unsigned) sizeof(x);
    +	}
    +	if ((size_t) NETWRITE(*s, (char *) pbuf, i) != i) {
    +	    goto report_system_err;
    +	} else if ((unsigned) HTDoRead(*s, pbuf, 4) != 4) {
    +	    goto report_system_err;
    +	}
    +	/* Version 5, reserved must be 0 */
    +	if (pbuf[0] == 0x05 && pbuf[2] == 0x00) {
    +	    /* Result */
    +	    switch (pbuf[1]) {
    +	    case 0x00:
    +		emsg = NULL;
    +		break;
    +	    case 0x01:
    +		emsg = gettext("SOCKS server failure");
    +		break;
    +	    case 0x02:
    +		emsg = gettext("connection not allowed by ruleset");
    +		break;
    +	    case 0x03:
    +		emsg = gettext("network unreachable");
    +		break;
    +	    case 0x04:
    +		emsg = gettext("host unreachable");
    +		break;
    +	    case 0x05:
    +		emsg = gettext("connection refused");
    +		break;
    +	    case 0x06:
    +		emsg = gettext("TTL expired");
    +		break;
    +	    case 0x07:
    +		emsg = gettext("command not supported");
    +		break;
    +	    case 0x08:
    +		emsg = gettext("address type not supported");
    +		break;
    +	    default:
    +		emsg = gettext("unknown SOCKS error code");
    +		break;
    +	    }
    +	    if (emsg != NULL) {
    +		goto report_no_connection;
    +	    }
    +	} else {
    +	    goto report_unexpected_reply;
    +	}
    +
    +	/* Address type variable; read the BND.PORT with it.
    +	 * This is actually false since RFC 1928 says that the BND.ADDR reply
    +	 * to CONNECT contains the IP address, so only 0x01 and 0x04 are
    +	 * allowed */
    +	switch (pbuf[3]) {
    +	case 0x01:
    +	    i = 4;
    +	    break;
    +	case 0x03:
    +	    i = 1;
    +	    break;
    +	case 0x04:
    +	    i = 16;
    +	    break;
    +	default:
    +	    goto report_unexpected_reply;
    +	}
    +	i += (unsigned) sizeof(unsigned short);
    +
    +	if ((size_t) HTDoRead(*s, pbuf, i) != i) {
    +	    goto report_system_err;
    +	} else if (i == 1 + sizeof(unsigned short)) {
    +	    i = pbuf[0];
    +	    if ((size_t) HTDoRead(*s, pbuf, i) != i) {
    +		goto report_system_err;
    +	    }
    +	}
    +    }
    +    goto cleanup;
    +
    +  report_system_err:
    +    emsg = LYStrerror(errno);
    +    goto report_no_connection;
    +
    +  report_unexpected_reply:
    +    emsg = gettext("unexpected reply\n");
    +    /* FALLTHRU */
    +
    +  report_no_connection:
    +    status = HT_NO_CONNECTION;
    +    /* FALLTHRU */
    +
    +  report_error:
    +    HTAlert(emsg);
    +    if (*s != -1) {
    +	NETCLOSE(*s);
    +    }
    +
    +  cleanup:
    +    if (socks5_proxy != NULL) {
    +	FREE(socks5_new_url);
    +	FREE(socks5_protocol);
    +	FREE(socks5_host);
    +    }
    +    FREE(host);
    +    FREE(line);
    +    return status;
    +}
    +
    +/*
    + *  This is interruptible so reads can be implemented cleanly.
    + */
    +int HTDoRead(int fildes,
    +	     void *buf,
    +	     unsigned nbyte)
    +{
    +    int result;
    +    BOOL ready;
    +
    +#if !defined(NO_IOCTL)
    +    int ret;
    +    fd_set readfds;
    +    struct timeval select_timeout;
    +    int tries = 0;
    +
    +#ifdef USE_READPROGRESS
    +    int otries = 0;
    +    time_t otime = time((time_t *) 0);
    +    time_t start = otime;
    +#endif
    +#endif /* !NO_IOCTL */
    +
    +#if defined(UNIX) && !defined(__BEOS__)
    +    if (fildes == 0) {
    +	/*
    +	 * 0 can be a valid socket fd, but if it's a tty something must have
    +	 * gone wrong.  - kw
    +	 */
    +	if (isatty(fildes)) {
    +	    CTRACE((tfp, "HTDoRead - refusing to read fd 0 which is a tty!\n"));
    +	    return -1;
    +	}
    +    } else
    +#endif
    +    if (fildes <= 0) {
    +	CTRACE((tfp, "HTDoRead - no file descriptor!\n"));
    +	return -1;
    +    }
    +
    +    if (HTWasInterrupted(&result)) {
    +	CTRACE((tfp, "HTDoRead - interrupted before starting!\n"));
    +	return (result);
    +    }
    +#if defined(NO_IOCTL)
    +    ready = TRUE;
    +#else
    +    ready = FALSE;
    +    while (!ready) {
    +	/*
    +	 * Protect against an infinite loop.
    +	 */
    +	if ((tries++ / TRIES_PER_SECOND) >= reading_timeout) {
    +	    HTAlert(gettext("Socket read failed (too many tries)."));
    +	    SET_EINTR;
    +	    result = HT_INTERRUPTED;
    +	    break;
    +	}
    +#ifdef USE_READPROGRESS
    +	if (tries - otries > TRIES_PER_SECOND) {
    +	    time_t t = time((time_t *) 0);
    +
    +	    otries = tries;
    +	    if (t - otime >= 5) {
    +		otime = t;
    +		HTReadProgress((off_t) (-1), (off_t) 0);	/* Put "stalled" message */
    +	    }
    +	}
    +#endif
    +
    +	/*
    +	 * If we suspend, then it is possible that select will be interrupted.
    +	 * Allow for this possibility.  - JED
    +	 */
    +	do {
    +	    set_timeout(&select_timeout);
    +	    FD_ZERO(&readfds);
    +	    FD_SET((LYNX_FD) fildes, &readfds);
    +#ifdef SOCKS
    +	    if (socks_flag)
    +		ret = Rselect(fildes + 1,
    +			      &readfds, NULL, NULL, &select_timeout);
    +	    else
    +#endif /* SOCKS */
    +		ret = select(fildes + 1,
    +			     &readfds, NULL, NULL, &select_timeout);
    +	} while ((ret == -1) && (errno == EINTR));
    +
    +	if (ret < 0) {
    +	    result = -1;
    +	    break;
    +	} else if (ret > 0) {
    +	    ready = TRUE;
    +	} else if (HTWasInterrupted(&result)) {
    +	    break;
    +	}
    +    }
    +#endif /* !NO_IOCTL */
    +
    +    if (ready) {
    +#if defined(UCX) && defined(VAXC)
    +	/*
    +	 * VAXC and UCX problem only.
    +	 */
    +	errno = vaxc$errno = 0;
    +	result = SOCKET_READ(fildes, buf, nbyte);
    +	CTRACE((tfp,
    +		"Read - result,errno,vaxc$errno: %d %d %d\n", result, errno, vaxc$errno));
    +	if ((result <= 0) && TRACE)
    +	    perror("HTTCP.C:HTDoRead:read");	/* RJF */
    +	/*
    +	 * An errno value of EPIPE and result < 0 indicates end-of-file on VAXC.
    +	 */
    +	if ((result <= 0) && (errno == EPIPE)) {
    +	    result = 0;
    +	    set_errno(0);
    +	}
    +#else
    +#ifdef UNIX
    +	while ((result = (int) SOCKET_READ(fildes, buf, nbyte)) == -1) {
    +	    if (errno == EINTR)
    +		continue;
    +#ifdef ERESTARTSYS
    +	    if (errno == ERESTARTSYS)
    +		continue;
    +#endif /* ERESTARTSYS */
    +	    HTInetStatus("read");
    +	    break;
    +	}
    +#else /* UNIX */
    +	result = NETREAD(fildes, (char *) buf, nbyte);
    +#endif /* !UNIX */
    +#endif /* UCX && VAXC */
    +    }
    +#ifdef USE_READPROGRESS
    +    CTRACE2(TRACE_TIMING, (tfp, "...HTDoRead returns %d (%" PRI_time_t
    +			   " seconds)\n",
    +			   result, CAST_time_t (time((time_t *)0) - start)));
    +#endif
    +    return result;
    +}
    +
    +#ifdef SVR4_BSDSELECT
    +/*
    + *  This is a fix for the difference between BSD's select() and
    + *  SVR4's select().  SVR4's select() can never return a value larger
    + *  than the total number of file descriptors being checked.  So, if
    + *  you select for read and write on one file descriptor, and both
    + *  are true, SVR4 select() will only return 1.  BSD select in the
    + *  same situation will return 2.
    + *
    + *	Additionally, BSD select() on timing out, will zero the masks,
    + *	while SVR4 does not.  This is fixed here as well.
    + *
    + *	Set your tabstops to 4 characters to have this code nicely formatted.
    + *
    + *	Jerry Whelan, guru@bradley.edu, June 12th, 1993
    + */
    +#ifdef select
    +#undef select
    +#endif /* select */
    +
    +#ifdef SOCKS
    +#ifdef Rselect
    +#undef Rselect
    +#endif /* Rselect */
    +#endif /* SOCKS */
    +
    +#include 
    +#include 
    +#include 
    +
    +int BSDselect(int nfds,
    +	      fd_set * readfds,
    +	      fd_set * writefds,
    +	      fd_set * exceptfds,
    +	      struct timeval *select_timeout)
    +{
    +    int rval, i;
    +
    +#ifdef SOCKS
    +    if (socks_flag)
    +	rval = Rselect(nfds, readfds, writefds, exceptfds, select_timeout);
    +    else
    +#endif /* SOCKS */
    +	rval = select(nfds, readfds, writefds, exceptfds, select_timeout);
    +
    +    switch (rval) {
    +    case -1:
    +	return (rval);
    +
    +    case 0:
    +	if (readfds != NULL)
    +	    FD_ZERO(readfds);
    +	if (writefds != NULL)
    +	    FD_ZERO(writefds);
    +	if (exceptfds != NULL)
    +	    FD_ZERO(exceptfds);
    +	return (rval);
    +
    +    default:
    +	for (i = 0, rval = 0; i < nfds; i++) {
    +	    if ((readfds != NULL) && FD_ISSET(i, readfds))
    +		rval++;
    +	    if ((writefds != NULL) && FD_ISSET(i, writefds))
    +		rval++;
    +	    if ((exceptfds != NULL) && FD_ISSET(i, exceptfds))
    +		rval++;
    +
    +	}
    +	return (rval);
    +    }
    +/* Should never get here */
    +}
    +#endif /* SVR4_BSDSELECT */
    diff --git a/WWW/Library/Implementation/HTTCP.h b/WWW/Library/Implementation/HTTCP.h
    new file mode 100644
    index 0000000..a15b290
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTCP.h
    @@ -0,0 +1,111 @@
    +/*
    + * $LynxId: HTTCP.h,v 1.27 2018/05/16 19:48:16 tom Exp $
    + *
    + *                               /Net/dxcern/userd/timbl/hypertext/WWW/Library/src/HTTCP.html
    + *                             GENERIC TCP/IP COMMUNICATION
    + *
    + * This module has the common code for handling TCP/IP connections etc.
    + */
    +#ifndef HTTCP_H
    +#define HTTCP_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +/*      Produce a string for an internet address
    + *      ---------------------------------------
    + *
    + * On exit:
    + *           returns a pointer to a static string which must be copied if
    + *                it is to be kept.
    + */
    +    extern const char *HTInetString(LY_SOCKADDR * mysin);
    +
    +/*      Encode INET status (as in sys/errno.h)                    inet_status()
    + *      ------------------
    + *
    + * On entry:
    + *              where gives a description of what caused the error
    + *      global errno gives the error number in the unix way.
    + *
    + * On return:
    + *      returns a negative status in the unix way.
    + */
    +    extern int HTInetStatus(const char *where);
    +
    +/*      Publicly accessible variables
    +*/
    +/* extern struct sockaddr_in HTHostAddress; */
    +    /* The internet address of the host */
    +    /* Valid after call to HTHostName() */
    +
    +/*      Parse a cardinal value                                 parse_cardinal()
    + *      ----------------------
    + *
    + * On entry:
    + *      *pp points to first character to be interpreted, terminated by
    + *      non 0..9 character.
    + *      *pstatus points to status already valid,
    + *      maxvalue gives the largest allowable value.
    + *
    + * On exit:
    + *      *pp points to first unread character,
    + *      *pstatus points to status updated iff bad
    + */
    +
    +    extern unsigned int HTCardinal(int *pstatus,
    +				   char **pp,
    +				   unsigned int max_value);
    +
    +/*	Check whether string is a valid Internet hostname
    + *	-------------------------------------------------
    + */
    +
    +    extern BOOL valid_hostname(char *name);
    +
    +/*	Resolve an internet hostname, like gethostbyname
    + *	------------------------------------------------
    + *
    + *  On entry,
    + *	str	points to the given host name, not numeric address,
    + *		without colon or port number.
    + *
    + *  On exit,
    + *	returns a pointer to a struct hostent in static storage,
    + *	or NULL in case of error or user interruption.
    + *
    + *  The interface is intended to be the same as for gethostbyname(),
    + *  but additional status is returned in lynx_nsl_status.
    + */
    +    extern int lynx_nsl_status;
    +
    +    extern BOOLEAN LYCheckHostByName(char *host);
    +
    +#ifdef INET6
    +    extern BOOLEAN HTCheckAddrInfo(const char *str, const int defport);
    +#endif
    +
    +/*      Get Name of This Machine
    + *      ------------------------
    + *
    + */
    +
    +    extern const char *HTHostName(void);
    +
    +    extern int HTDoConnect(const char *url,
    +			   const char *protocol,
    +			   int default_port,
    +			   int *s);
    +
    +    extern int HTDoRead(int fildes,
    +			void *buf,
    +			unsigned nbyte);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTTCP_H */
    diff --git a/WWW/Library/Implementation/HTTP.c b/WWW/Library/Implementation/HTTP.c
    new file mode 100644
    index 0000000..e3c5a4a
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTP.c
    @@ -0,0 +1,2838 @@
    +/*
    + * $LynxId: HTTP.c,v 1.183 2022/04/01 00:10:19 tom Exp $
    + *
    + * HyperText Transfer Protocol	- Client implementation		HTTP.c
    + * ===========================
    + * Modified:
    + * 27 Jan 1994	PDM  Added Ari Luotonen's Fix for Reload when using proxy
    + *		     servers.
    + * 28 Apr 1997	AJL,FM Do Proxy Authorisation.
    + */
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef USE_SSL
    +#include 
    +#endif
    +
    +#define HTTP_PORT   80
    +#define HTTPS_PORT  443
    +#define SNEWS_PORT  563
    +
    +#define INIT_LINE_SIZE		1536	/* Start with line buffer this big */
    +#define LINE_EXTEND_THRESH	256	/* Minimum read size */
    +#define VERSION_LENGTH		20	/* for returned protocol version */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 		/* Are we using an HTTP gateway? */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef USE_SSL
    +
    +#ifdef USE_OPENSSL_INCL
    +#include 
    +#endif
    +
    +#if defined(LIBRESSL_VERSION_NUMBER)
    +/* OpenSSL and LibreSSL version numbers do not correspond */
    +
    +#if LIBRESSL_VERSION_NUMBER >= 0x2060100fL
    +#define SSL_set_no_TLSV1()		SSL_set_min_proto_version(handle, TLS1_1_VERSION)
    +#endif
    +
    +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    +
    +#define SSLEAY_VERSION_NUMBER		OPENSSL_VERSION_NUMBER
    +#undef  SSL_load_error_strings
    +#undef  SSLeay_add_ssl_algorithms
    +#define ASN1_STRING_data		ASN1_STRING_get0_data
    +#define TLS_client_method()		SSLv23_client_method()
    +#define SSL_load_error_strings()	/* nothing */
    +#define SSLeay_add_ssl_algorithms()	/* nothing */
    +#define SSL_set_no_TLSV1()		SSL_set_min_proto_version(handle, TLS1_1_VERSION)
    +
    +#elif defined(SSLEAY_VERSION_NUMBER)
    +
    +#define TLS_client_method()		SSLv23_client_method()
    +
    +#endif
    +
    +#ifndef SSL_set_no_TLSV1
    +#define SSL_set_no_TLSV1()		SSL_set_options(handle, SSL_OP_NO_TLSv1)
    +#endif
    +
    +#ifdef USE_GNUTLS_INCL
    +#include 
    +#endif
    +
    +#endif /* USE_SSL */
    +
    +BOOLEAN reloading = FALSE;	/* Reloading => send no-cache pragma to proxy */
    +char *redirecting_url = NULL;	/* Location: value. */
    +BOOL permanent_redirection = FALSE;	/* Got 301 status? */
    +BOOL redirect_post_content = FALSE;	/* Don't convert to GET? */
    +
    +#ifdef USE_SSL
    +SSL_CTX *ssl_ctx = NULL;	/* SSL ctx */
    +SSL *SSL_handle = NULL;
    +static int ssl_okay;
    +
    +static void free_ssl_ctx(void)
    +{
    +    if (ssl_ctx != NULL)
    +	SSL_CTX_free(ssl_ctx);
    +}
    +
    +static BOOL needs_limit(const char *actual)
    +{
    +    return ((int) strlen(actual) > LYcols - 7) ? TRUE : FALSE;
    +}
    +
    +static char *limited_string(const char *source, const char *actual)
    +{
    +    int limit = ((int) strlen(source)
    +		 - ((int) strlen(actual) - (LYcols - 10)));
    +    char *temp = NULL;
    +
    +    StrAllocCopy(temp, source);
    +    if (limit < 0)
    +	limit = 0;
    +    strcpy(temp + limit, "...");
    +    return temp;
    +}
    +
    +/*
    + * If the error message is too long to fit in the line, truncate that to fit
    + * within the limits for prompting.
    + */
    +static void SSL_single_prompt(char **target, const char *source)
    +{
    +    HTSprintf0(target, SSL_FORCED_PROMPT, source);
    +    if (needs_limit(*target)) {
    +	char *temp = limited_string(source, *target);
    +
    +	*target = NULL;
    +	HTSprintf0(target, SSL_FORCED_PROMPT, temp);
    +	free(temp);
    +    }
    +}
    +
    +static void SSL_double_prompt(char **target, const char *format, const char
    +			      *arg1, const char *arg2)
    +{
    +    HTSprintf0(target, format, arg1, arg2);
    +    if (needs_limit(*target)) {
    +	char *parg2 = limited_string(arg2, *target);
    +
    +	*target = NULL;
    +	HTSprintf0(target, format, arg1, parg2);
    +	if (needs_limit(*target)) {
    +	    char *parg1 = limited_string(arg1, *target);
    +
    +	    *target = NULL;
    +	    HTSprintf0(target, format, parg1, parg2);
    +	    free(parg1);
    +	}
    +	free(parg2);
    +    }
    +}
    +
    +static int HTSSLCallback(int preverify_ok, X509_STORE_CTX * x509_ctx GCC_UNUSED)
    +{
    +    char *msg = NULL;
    +    int result = 1;
    +
    +#ifdef USE_X509_SUPPORT
    +    HTSprintf0(&msg,
    +	       gettext("SSL callback:%s, preverify_ok=%d, ssl_okay=%d"),
    +	       X509_verify_cert_error_string((long) X509_STORE_CTX_get_error(x509_ctx)),
    +	       preverify_ok, ssl_okay);
    +    _HTProgress(msg);
    +    FREE(msg);
    +#endif
    +
    +#ifndef USE_NSS_COMPAT_INCL
    +    if (!(preverify_ok || ssl_okay || ssl_noprompt)) {
    +#ifdef USE_X509_SUPPORT
    +	SSL_single_prompt(&msg,
    +			  X509_verify_cert_error_string((long)
    +							X509_STORE_CTX_get_error(x509_ctx)));
    +	if (HTForcedPrompt(ssl_noprompt, msg, NO))
    +	    ssl_okay = 1;
    +	else
    +	    result = 0;
    +#endif
    +
    +	FREE(msg);
    +    }
    +#endif
    +    return result;
    +}
    +
    +SSL *HTGetSSLHandle(void)
    +{
    +#ifdef USE_GNUTLS_INCL
    +    static char *certfile = NULL;
    +#endif
    +    static char *client_keyfile = NULL;
    +    static char *client_certfile = NULL;
    +
    +    if (ssl_ctx == NULL) {
    +	/*
    +	 * First time only.
    +	 */
    +#if SSLEAY_VERSION_NUMBER < 0x0800
    +	if ((ssl_ctx = SSL_CTX_new()) != NULL) {
    +	    X509_set_default_verify_paths(ssl_ctx->cert);
    +	}
    +#else
    +	SSLeay_add_ssl_algorithms();
    +	if ((ssl_ctx = SSL_CTX_new(TLS_client_method())) != NULL) {
    +#ifdef SSL_OP_NO_SSLv2
    +	    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
    +#else
    +	    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
    +#endif
    +#ifdef SSL_OP_NO_COMPRESSION
    +	    SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_COMPRESSION);
    +#endif
    +#ifdef SSL_MODE_AUTO_RETRY
    +	    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
    +#endif
    +#ifdef SSL_MODE_RELEASE_BUFFERS
    +	    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
    +#endif
    +	    SSL_CTX_set_default_verify_paths(ssl_ctx);
    +	    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, HTSSLCallback);
    +	}
    +#endif /* SSLEAY_VERSION_NUMBER < 0x0800 */
    +#if defined(USE_PROGRAM_DIR) & !defined(USE_GNUTLS_INCL)
    +	if (ssl_ctx != NULL) {
    +	    X509_LOOKUP *lookup;
    +
    +	    lookup = X509_STORE_add_lookup(ssl_ctx->cert_store,
    +					   X509_LOOKUP_file());
    +	    if (lookup != NULL) {
    +		char *certfile = NULL;
    +
    +		HTSprintf0(&certfile, "%s\\cert.pem", program_dir);
    +		X509_LOOKUP_load_file(lookup, certfile, X509_FILETYPE_PEM);
    +		FREE(certfile);
    +	    }
    +	}
    +#endif
    +#ifdef USE_GNUTLS_INCL
    +	if ((certfile = LYGetEnv("SSL_CERT_FILE")) != NULL) {
    +	    CTRACE((tfp,
    +		    "HTGetSSLHandle: certfile is set to %s by SSL_CERT_FILE\n",
    +		    certfile));
    +	} else {
    +	    if (non_empty(SSL_cert_file)) {
    +		certfile = SSL_cert_file;
    +		CTRACE((tfp,
    +			"HTGetSSLHandle: certfile is set to %s by config SSL_CERT_FILE\n",
    +			certfile));
    +	    }
    +#if defined(USE_PROGRAM_DIR)
    +	    else {
    +		HTSprintf0(&(certfile), "%s\\cert.pem", program_dir);
    +		CTRACE((tfp,
    +			"HTGetSSLHandle: certfile is set to %s by installed directory\n", certfile));
    +	    }
    +#endif
    +	}
    +#endif
    +	atexit(free_ssl_ctx);
    +    }
    +
    +    if (non_empty(SSL_client_key_file)) {
    +	client_keyfile = SSL_client_key_file;
    +	CTRACE((tfp,
    +		"HTGetSSLHandle: client key file is set to %s by config SSL_CLIENT_KEY_FILE\n",
    +		client_keyfile));
    +    }
    +
    +    if (non_empty(SSL_client_cert_file)) {
    +	client_certfile = SSL_client_cert_file;
    +	CTRACE((tfp,
    +		"HTGetSSLHandle: client cert file is set to %s by config SSL_CLIENT_CERT_FILE\n",
    +		client_certfile));
    +    }
    +#ifdef USE_GNUTLS_INCL
    +    ssl_ctx->certfile = certfile;
    +    ssl_ctx->certfile_type = GNUTLS_X509_FMT_PEM;
    +    ssl_ctx->client_keyfile = client_keyfile;
    +    ssl_ctx->client_keyfile_type = GNUTLS_X509_FMT_PEM;
    +    ssl_ctx->client_certfile = client_certfile;
    +    ssl_ctx->client_certfile_type = GNUTLS_X509_FMT_PEM;
    +#elif SSLEAY_VERSION_NUMBER >= 0x0930
    +    if (client_certfile != NULL) {
    +	if (client_keyfile == NULL) {
    +	    client_keyfile = client_certfile;
    +	}
    +	SSL_CTX_use_certificate_chain_file(ssl_ctx, client_certfile);
    +	SSL_CTX_use_PrivateKey_file(ssl_ctx, client_keyfile, SSL_FILETYPE_PEM);
    +    }
    +#endif
    +    ssl_okay = 0;
    +    return (SSL_new(ssl_ctx));
    +}
    +
    +void HTSSLInitPRNG(void)
    +{
    +#if SSLEAY_VERSION_NUMBER >= 0x00905100
    +    if (RAND_status() == 0) {
    +	char rand_file[256];
    +	time_t t;
    +	long l, seed;
    +
    +#ifndef _WINDOWS
    +	pid_t pid;
    +
    +#else
    +	DWORD pid;
    +#endif
    +
    +	t = time(NULL);
    +
    +#ifndef _WINDOWS
    +	pid = getpid();
    +#else
    +	pid = GetCurrentThreadId();
    +#endif
    +
    +	RAND_file_name(rand_file, 256L);
    +	CTRACE((tfp, "HTTP: Seeding PRNG\n"));
    +	/* Seed as much as 1024 bytes from RAND_file_name */
    +	RAND_load_file(rand_file, 1024L);
    +	/* Seed in time (mod_ssl does this) */
    +	RAND_seed((unsigned char *) &t, (int) sizeof(time_t));
    +
    +	/* Seed in pid (mod_ssl does this) */
    +	RAND_seed((unsigned char *) &pid, (int) sizeof(pid));
    +	/* Initialize system's random number generator */
    +	RAND_bytes((unsigned char *) &seed, (int) sizeof(long));
    +
    +	lynx_srand((unsigned) seed);
    +	while (RAND_status() == 0) {
    +	    /* Repeatedly seed the PRNG using the system's random number generator until it has been seeded with enough data */
    +	    l = (long) lynx_rand();
    +	    RAND_seed((unsigned char *) &l, (int) sizeof(long));
    +	}
    +	/* Write a rand_file */
    +	RAND_write_file(rand_file);
    +    }
    +#endif /* SSLEAY_VERSION_NUMBER >= 0x00905100 */
    +    return;
    +}
    +
    +#define HTTP_NETREAD(sock, buff, size, handle) \
    +	(handle \
    +	 ? SSL_read(handle, buff, size) \
    +	 : NETREAD(sock, buff, size))
    +
    +#define HTTP_NETWRITE(sock, buff, size, handle) \
    +	(handle \
    +	 ? SSL_write(handle, buff, size) \
    +	 : NETWRITE(sock, buff, size))
    +
    +#define HTTP_NETCLOSE(sock, handle)  \
    +	{ (void)NETCLOSE(sock); \
    +	  if (handle) \
    +	      SSL_free(handle); \
    +	  SSL_handle = handle = NULL; \
    +	}
    +
    +#else
    +#define HTTP_NETREAD(a, b, c, d)   NETREAD(a, b, c)
    +#define HTTP_NETWRITE(a, b, c, d)  NETWRITE(a, b, c)
    +#define HTTP_NETCLOSE(a, b)  (void)NETCLOSE(a)
    +#endif /* USE_SSL */
    +
    +#ifdef _WINDOWS			/* 1997/11/06 (Thu) 13:00:08 */
    +
    +#define	BOX_TITLE	"Lynx " __FILE__
    +#define	BOX_FLAG	(MB_ICONINFORMATION | MB_SETFOREGROUND)
    +
    +typedef struct {
    +    int fd;
    +    char *buf;
    +    int len;
    +} recv_data_t;
    +
    +int ws_read_per_sec = 0;
    +static int ws_errno = 0;
    +
    +static DWORD g_total_times = 0;
    +static DWORD g_total_bytes = 0;
    +
    +/* The same like read, but takes care of EINTR and uses select to
    +   timeout the stale connections.  */
    +
    +static int ws_read(int fd, char *buf, int len)
    +{
    +    int res;
    +    int retry = 3;
    +
    +    do {
    +	res = recv(fd, buf, len, 0);
    +	if (WSAEWOULDBLOCK == WSAGetLastError()) {
    +	    Sleep(100);
    +	    if (retry-- > 0)
    +		continue;
    +	}
    +    } while (res == SOCKET_ERROR && SOCKET_ERRNO == EINTR);
    +
    +    return res;
    +}
    +
    +#define DWORD_ERR ((DWORD)-1)
    +
    +static DWORD __stdcall _thread_func(void *p)
    +{
    +    DWORD result;
    +    int i, val;
    +    recv_data_t *q = (recv_data_t *) p;
    +
    +    i = 0;
    +    i++;
    +    val = ws_read(q->fd, q->buf, q->len);
    +
    +    if (val == SOCKET_ERROR) {
    +	ws_errno = WSAGetLastError();
    +#if 0
    +	char buff[256];
    +
    +	sprintf(buff, "Thread read: %d, error (%ld), fd = %d, len = %d",
    +		i, ws_errno, q->fd, q->len);
    +	MessageBox(NULL, buff, BOX_TITLE, BOX_FLAG);
    +#endif
    +	result = DWORD_ERR;
    +    } else {
    +	result = val;
    +    }
    +
    +    return result;
    +}
    +
    +/* The same like read, but takes care of EINTR and uses select to
    +   timeout the stale connections.  */
    +
    +int ws_netread(int fd, char *buf, int len)
    +{
    +    int i;
    +    char buff[256];
    +
    +    /* 1998/03/30 (Mon) 09:01:21 */
    +    HANDLE hThread;
    +    DWORD dwThreadID;
    +    DWORD exitcode = 0;
    +    DWORD ret_val = DWORD_ERR;
    +    DWORD val, process_time, now_TickCount, save_TickCount;
    +
    +    static recv_data_t para;
    +
    +#define TICK	5
    +#define STACK_SIZE	0x2000uL
    +
    +    EnterCriticalSection(&critSec_READ);
    +
    +    para.fd = fd;
    +    para.buf = buf;
    +    para.len = len;
    +
    +    ws_read_per_sec = 0;
    +    save_TickCount = GetTickCount();
    +
    +    hThread = CreateThread(NULL, STACK_SIZE,
    +			   _thread_func,
    +			   (void *) ¶, 0UL, &dwThreadID);
    +
    +    if (hThread == 0) {
    +	HTInfoMsg("CreateThread Failed (read)");
    +	goto read_exit;
    +    }
    +
    +    i = 0;
    +    while (1) {
    +	val = WaitForSingleObject(hThread, 1000 / TICK);
    +	i++;
    +	if (val == WAIT_FAILED) {
    +	    HTInfoMsg("Wait Failed");
    +	    ret_val = DWORD_ERR;
    +	    break;
    +	} else if (val == WAIT_TIMEOUT) {
    +	    i++;
    +	    if (i / TICK > (AlertSecs + 2)) {
    +		sprintf(buff, "Read Waiting (%2d.%01d) for %d Bytes",
    +			i / TICK, (i % TICK) * 10 / TICK, len);
    +		SetConsoleTitle(buff);
    +	    }
    +	    if (win32_check_interrupt() || ((i / TICK) > lynx_timeout)) {
    +		if (CloseHandle(hThread) == FALSE) {
    +		    HTInfoMsg("Thread terminate Failed");
    +		}
    +		WSASetLastError(ETIMEDOUT);
    +		ret_val = HT_INTERRUPTED;
    +		break;
    +	    }
    +	} else if (val == WAIT_OBJECT_0) {
    +	    if (GetExitCodeThread(hThread, &exitcode) == FALSE) {
    +		exitcode = DWORD_ERR;
    +	    }
    +	    if (CloseHandle(hThread) == FALSE) {
    +		HTInfoMsg("Thread terminate Failed");
    +	    }
    +	    now_TickCount = GetTickCount();
    +	    if (now_TickCount >= save_TickCount)
    +		process_time = now_TickCount - save_TickCount;
    +	    else
    +		process_time = now_TickCount + (0xffffffff - save_TickCount);
    +
    +	    if (process_time == 0)
    +		process_time = 1;
    +	    g_total_times += process_time;
    +
    +	    /*
    +	     * DWORD is unsigned, and could be an error code which is signed.
    +	     */
    +	    if ((long) exitcode > 0)
    +		g_total_bytes += exitcode;
    +
    +	    ws_read_per_sec = g_total_bytes;
    +	    if (ws_read_per_sec > 2000000) {
    +		if (g_total_times > 1000)
    +		    ws_read_per_sec /= (g_total_times / 1000);
    +	    } else {
    +		ws_read_per_sec *= 1000;
    +		ws_read_per_sec /= g_total_times;
    +	    }
    +
    +	    ret_val = exitcode;
    +	    break;
    +	}
    +    }				/* end while(1) */
    +
    +  read_exit:
    +    LeaveCriticalSection(&critSec_READ);
    +    return ret_val;
    +}
    +#endif /* _WINDOWS */
    +
    +/*
    + * RFC-1738 says we can have user/password using these ASCII characters
    + *    safe           = "$" | "-" | "_" | "." | "+"
    + *    extra          = "!" | "*" | "'" | "(" | ")" | ","
    + *    hex            = digit | "A" | "B" | "C" | "D" | "E" | "F" |
    + *                             "a" | "b" | "c" | "d" | "e" | "f"
    + *    escape         = "%" hex hex
    + *    unreserved     = alpha | digit | safe | extra
    + *    uchar          = unreserved | escape
    + *    user           = *[ uchar | ";" | "?" | "&" | "=" ]
    + *    password       = *[ uchar | ";" | "?" | "&" | "=" ]
    + * and we cannot have a password without user, i.e., no leading ":"
    + * and ":", "@", "/" must be encoded, i.e., will not appear as such.
    + *
    + * However, in a URL
    + *    //:@:/
    + * valid characters in the host are different, not allowing most of those
    + * punctuation characters.
    + *
    + * RFC-3986 amends this, using
    + *     userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
    + *     unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
    + *     reserved      = gen-delims / sub-delims
    + *     gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
    + *     sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
    + *                     / "*" / "+" / "," / ";" / "="
    + * and
    + *     host          = IP-literal / IPv4address / reg-name
    + *     reg-name      = *( unreserved / pct-encoded / sub-delims )
    + */
    +char *HTSkipToAt(char *host, int *gen_delims)
    +{
    +    char *result = 0;
    +    char *s = host;
    +    int pass = 0;
    +    int ch;
    +    int last = -1;
    +
    +    *gen_delims = 0;
    +    while ((ch = UCH(*s)) != '\0') {
    +	if (ch == ':') {
    +	    if (pass++)
    +		break;
    +	} else if (ch == '@') {
    +	    if (s != host && last != ':')
    +		result = s;
    +	    break;
    +	} else if (RFC_3986_GEN_DELIMS(ch)) {
    +	    *gen_delims += 1;
    +	    if (!RFC_3986_GEN_DELIMS(s[1]))
    +		break;
    +	} else if (ch == '%') {
    +	    if (!(isxdigit(UCH(s[1])) && isxdigit(UCH(s[2]))))
    +		break;
    +	} else if (!(RFC_3986_UNRESERVED(ch) ||
    +		     RFC_3986_SUB_DELIMS(ch))) {
    +	    break;
    +	}
    +	++s;
    +	last = ch;
    +    }
    +    return result;
    +}
    +
    +static char *fake_hostname(char *auth)
    +{
    +    char *result = NULL;
    +    char *colon = NULL;
    +
    +    StrAllocCopy(result, auth);
    +    if ((colon = strchr(result, ':')) != 0)
    +	*colon = '\0';
    +    if (strchr(result, '.') == 0)
    +	FREE(result);
    +    return result;
    +}
    +
    +/*
    + * Strip any username from the given string so we retain only the host.
    + */
    +void strip_userid(char *host, int parse_only)
    +{
    +    int gen_delims = 0;
    +    char *p1 = host;
    +    char *p2 = HTSkipToAt(host, &gen_delims);
    +
    +    if (p2 != 0) {
    +	char *msg = NULL;
    +	char *auth = NULL;
    +	char *fake = NULL;
    +	char *p3 = p2;
    +	int sub_delims = 0;
    +	int my_delimit = UCH(*p2);
    +	int do_trimming = (my_delimit == '@');
    +
    +	*p2++ = '\0';
    +
    +	StrAllocCopy(auth, host);
    +
    +	/*
    +	 * Trailing "gen-delims" demonstrates that there is no user/password.
    +	 */
    +	while ((p3 != host) && RFC_3986_GEN_DELIMS(p3[-1])) {
    +	    *(--p3) = '\0';
    +	}
    +	/*
    +	 * While legal, punctuation-only user/password is questionable.
    +	 */
    +	while ((p3 != host) && RFC_3986_SUB_DELIMS(p3[-1])) {
    +	    ++sub_delims;
    +	    *(--p3) = '\0';
    +	}
    +	/*
    +	 * Trim trailing "gen-delims" from the real hostname.
    +	 */
    +	for (p3 = p2; *p3 != '\0'; ++p3) {
    +	    if (RFC_3986_GEN_DELIMS(*p3)) {
    +		*p3 = '\0';
    +		break;
    +	    }
    +	}
    +	CTRACE((tfp, "trim auth:    result:`%s'\n", host));
    +
    +	if (gen_delims || strcmp(host, auth)) {
    +	    do_trimming = !gen_delims;
    +	}
    +	if (*host == '\0' && sub_delims) {
    +	    HTSprintf0(&msg,
    +		       gettext("User/password contains only punctuation: %s"),
    +		       auth);
    +	} else if ((fake = fake_hostname(host)) != NULL) {
    +	    HTSprintf0(&msg,
    +		       gettext("User/password may be confused with hostname: '%s' (e.g, '%s')"),
    +		       auth, fake);
    +	}
    +	if (msg != 0 && !parse_only)
    +	    HTAlert(msg);
    +	if (do_trimming) {
    +	    while ((*p1++ = *p2++) != '\0') {
    +		;
    +	    }
    +	    CTRACE((tfp, "trim host:    result:`%s'\n", host));
    +	}
    +	FREE(fake);
    +	FREE(auth);
    +	FREE(msg);
    +    }
    +}
    +
    +/*
    + * Check if the user's options specified to use the given encoding.  Normally
    + * all encodings with compiled-in support are specified (encodingALL).
    + */
    +static BOOL acceptEncoding(int code)
    +{
    +    BOOL result = FALSE;
    +
    +    if ((code & LYAcceptEncoding) != 0) {
    +	const char *program = 0;
    +
    +	switch (code) {
    +	case encodingGZIP:
    +	    program = HTGetProgramPath(ppGZIP);
    +	    break;
    +	case encodingDEFLATE:
    +	    program = HTGetProgramPath(ppINFLATE);
    +	    break;
    +	case encodingCOMPRESS:
    +	    program = HTGetProgramPath(ppCOMPRESS);
    +	    break;
    +	case encodingBZIP2:
    +	    program = HTGetProgramPath(ppBZIP2);
    +	    break;
    +	case encodingBROTLI:
    +	    program = HTGetProgramPath(ppBROTLI);
    +	    break;
    +	default:
    +	    break;
    +	}
    +	/*
    +	 * FIXME:  if lynx did not rely upon external programs to decompress
    +	 * files for external viewers, this check could be relaxed.
    +	 */
    +	result = (BOOL) (program != 0);
    +    }
    +    return result;
    +}
    +
    +#ifdef USE_SSL
    +static void show_cert_issuer(X509 * peer_cert GCC_UNUSED)
    +{
    +#if defined(USE_OPENSSL_INCL) || defined(USE_GNUTLS_FUNCS)
    +    char ssl_dn[1024];
    +    char *msg = NULL;
    +
    +    X509_NAME_oneline(X509_get_issuer_name(peer_cert), ssl_dn, (int) sizeof(ssl_dn));
    +    HTSprintf0(&msg, gettext("Certificate issued by: %s"), ssl_dn);
    +    _HTProgress(msg);
    +    FREE(msg);
    +#elif defined(USE_GNUTLS_INCL)
    +    /* the OpenSSL "compat" code compiles but dumps core with GNU TLS */
    +#endif
    +}
    +#endif
    +
    +/*
    + * Remove IPv6 brackets (and any port-number) from the given host-string.
    + */
    +#ifdef USE_SSL
    +static char *StripIpv6Brackets(char *host)
    +{
    +    int port_number;
    +    char *p;
    +
    +    if ((p = HTParsePort(host, &port_number)) != 0)
    +	*p = '\0';
    +
    +    if (*host == '[') {
    +	p = host + strlen(host) - 1;
    +	if (*p == ']') {
    +	    *p = '\0';
    +	    for (p = host; (p[0] = p[1]) != '\0'; ++p) {
    +		;		/* EMPTY */
    +	    }
    +	}
    +    }
    +    return host;
    +}
    +#endif
    +
    +/*
    + * Remove user/password, if any, from the given host-string.
    + */
    +#ifdef USE_SSL
    +static char *StripUserAuthents(char *host)
    +{
    +    char *p = strchr(host, '@');
    +
    +    if (p != NULL) {
    +	char *q = host;
    +
    +	while ((*q++ = *++p) != '\0') ;
    +    }
    +    return host;
    +}
    +#endif
    +
    +/*		Load Document from HTTP Server			HTLoadHTTP()
    + *		==============================
    + *
    + *	Given a hypertext address, this routine loads a document.
    + *
    + *
    + *  On entry,
    + *	arg	is the hypertext reference of the article to be loaded.
    + *
    + *  On exit,
    + *	returns >=0	If no error, a good socket number
    + *		<0	Error.
    + *
    + *	The socket must be closed by the caller after the document has been
    + *	read.
    + *
    + */
    +static int HTLoadHTTP(const char *arg,
    +		      HTParentAnchor *anAnchor,
    +		      HTFormat format_out,
    +		      HTStream *sink)
    +{
    +    static char empty[1];
    +    int s;			/* Socket number for returned data */
    +    const char *url = arg;	/* The URL which get_physical() returned */
    +    bstring *command = NULL;	/* The whole command */
    +    char *eol;			/* End of line if found */
    +    char *start_of_data;	/* Start of body of reply */
    +    int status;			/* tcp return */
    +    off_t bytes_already_read;
    +    char crlf[3];		/* A CR LF equivalent string */
    +    HTStream *target;		/* Unconverted data */
    +    HTFormat format_in;		/* Format arriving in the message */
    +    BOOL do_head = FALSE;	/* Whether or not we should do a head */
    +    BOOL do_post = FALSE;	/* ARE WE posting ? */
    +    const char *METHOD;
    +
    +    char *line_buffer = NULL;
    +    char *line_kept_clean = NULL;
    +
    +#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
    +    int real_length_of_line = 0;
    +#endif
    +    BOOL extensions;		/* Assume good HTTP server */
    +    char *linebuf = NULL;
    +    char temp[80];
    +    BOOL first_Accept = TRUE;
    +    BOOL show_401 = FALSE;
    +    BOOL show_407 = FALSE;
    +    BOOL auth_proxy = NO;	/* Generate a proxy authorization. - AJL */
    +
    +    int length, rawlength, rv;
    +    int server_status = 0;
    +    BOOL doing_redirect, already_retrying = FALSE;
    +    int len = 0;
    +
    +#ifdef USE_SSL
    +    unsigned long SSLerror;
    +    BOOL do_connect = FALSE;	/* ARE WE going to use a proxy tunnel ? */
    +    BOOL did_connect = FALSE;	/* ARE WE actually using a proxy tunnel ? */
    +    const char *connect_url = NULL;	/* The URL being proxied */
    +    char *connect_host = NULL;	/* The host being proxied */
    +    SSL *handle = NULL;		/* The SSL handle */
    +    X509 *peer_cert;		/* The peer certificate */
    +    char ssl_dn[1024];
    +    char *cert_host;
    +    char *ssl_host;
    +    char *p;
    +    char *msg = NULL;
    +    int status_sslcertcheck;
    +    char *ssl_dn_start;
    +    char *ssl_all_cns = NULL;
    +
    +#ifdef USE_GNUTLS_INCL
    +    int ret;
    +    unsigned tls_status;
    +#endif
    +
    +#if (SSLEAY_VERSION_NUMBER >= 0x0900) && !defined(USE_GNUTLS_FUNCS)
    +    BOOL try_tls = TRUE;
    +#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
    +    SSL_handle = NULL;
    +#else
    +    void *handle = NULL;
    +#endif /* USE_SSL */
    +
    +    if (anAnchor->isHEAD)
    +	do_head = TRUE;
    +    else if (anAnchor->post_data)
    +	do_post = TRUE;
    +
    +    if (!url) {
    +	status = -3;
    +	_HTProgress(BAD_REQUEST);
    +	goto done;
    +    }
    +    if (!*url) {
    +	status = -2;
    +	_HTProgress(BAD_REQUEST);
    +	goto done;
    +    }
    +#ifdef USE_SSL
    +    if (using_proxy && !StrNCmp(url, "http://", 7)) {
    +	int portnumber;
    +
    +	if ((connect_url = strstr((url + 7), "https://"))) {
    +	    do_connect = TRUE;
    +	    connect_host = HTParse(connect_url, "https", PARSE_HOST);
    +	    if (!HTParsePort(connect_host, &portnumber)) {
    +		sprintf(temp, ":%d", HTTPS_PORT);
    +		StrAllocCat(connect_host, temp);
    +	    }
    +	    CTRACE((tfp, "HTTP: connect_url = '%s'\n", connect_url));
    +	    CTRACE((tfp, "HTTP: connect_host = '%s'\n", connect_host));
    +	} else if ((connect_url = strstr((url + 7), "snews://"))) {
    +	    do_connect = TRUE;
    +	    connect_host = HTParse(connect_url, "snews", PARSE_HOST);
    +	    if (!HTParsePort(connect_host, &portnumber)) {
    +		sprintf(temp, ":%d", SNEWS_PORT);
    +		StrAllocCat(connect_host, temp);
    +	    }
    +	    CTRACE((tfp, "HTTP: connect_url = '%s'\n", connect_url));
    +	    CTRACE((tfp, "HTTP: connect_host = '%s'\n", connect_host));
    +	}
    +    }
    +#endif /* USE_SSL */
    +
    +    sprintf(crlf, "%c%c", CR, LF);
    +
    +    /*
    +     * At this point, we're talking HTTP/1.0.
    +     */
    +    extensions = YES;
    +
    +  try_again:
    +    /*
    +     * All initializations are moved down here from up above, so we can start
    +     * over here...
    +     */
    +    eol = 0;
    +    length = 0;
    +    doing_redirect = FALSE;
    +    permanent_redirection = FALSE;
    +    redirect_post_content = FALSE;
    +    target = NULL;
    +    line_buffer = NULL;
    +    line_kept_clean = NULL;
    +
    +#ifdef USE_SSL
    +    if (!StrNCmp(url, "https", 5))
    +	status = HTDoConnect(url, "HTTPS", HTTPS_PORT, &s);
    +    else
    +	status = HTDoConnect(url, "HTTP", HTTP_PORT, &s);
    +#else
    +    if (!StrNCmp(url, "https", 5)) {
    +	HTAlert(gettext("This client does not contain support for HTTPS URLs."));
    +	status = HT_NOT_LOADED;
    +	goto done;
    +    }
    +    status = HTDoConnect(arg, "HTTP", HTTP_PORT, &s);
    +#endif /* USE_SSL */
    +    if (status == HT_INTERRUPTED) {
    +	/*
    +	 * Interrupt cleanly.
    +	 */
    +	CTRACE((tfp, "HTTP: Interrupted on connect; recovering cleanly.\n"));
    +	_HTProgress(CONNECTION_INTERRUPTED);
    +	status = HT_NOT_LOADED;
    +	goto done;
    +    }
    +    if (status < 0) {
    +#ifdef _WINDOWS
    +	CTRACE((tfp, "HTTP: Unable to connect to remote host for `%s'\n"
    +		" (status = %d, sock_errno = %d).\n",
    +		url, status, SOCKET_ERRNO));
    +#else
    +	CTRACE((tfp,
    +		"HTTP: Unable to connect to remote host for `%s' (errno = %d).\n",
    +		url, SOCKET_ERRNO));
    +#endif
    +	HTAlert(gettext("Unable to connect to remote host."));
    +	status = HT_NOT_LOADED;
    +	goto done;
    +    }
    +#ifdef USE_SSL
    +  use_tunnel:
    +    /*
    +     * If this is an https document, then do the SSL stuff here.
    +     */
    +    if (did_connect || !StrNCmp(url, "https", 5)) {
    +	SSL_handle = handle = HTGetSSLHandle();
    +	SSL_set_fd(handle, s);
    +	/* get host we're connecting to */
    +	ssl_host = HTParse(url, "", PARSE_HOST);
    +	ssl_host = StripIpv6Brackets(ssl_host);
    +	ssl_host = StripUserAuthents(ssl_host);
    +#if defined(USE_GNUTLS_FUNCS)
    +	ret = gnutls_server_name_set(handle->gnutls_state,
    +				     GNUTLS_NAME_DNS,
    +				     ssl_host, strlen(ssl_host));
    +	CTRACE((tfp, "...called gnutls_server_name_set(%s) ->%d\n", ssl_host, ret));
    +#elif SSLEAY_VERSION_NUMBER >= 0x0900
    +#ifndef USE_NSS_COMPAT_INCL
    +	if (!try_tls) {
    +	    SSL_set_no_TLSV1();
    +	    CTRACE((tfp, "...adding SSL_OP_NO_TLSv1\n"));
    +	}
    +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
    +	else {
    +	    int ret = (int) SSL_set_tlsext_host_name(handle, ssl_host);
    +
    +	    CTRACE((tfp, "...called SSL_set_tlsext_host_name(%s) ->%d\n",
    +		    ssl_host, ret));
    +	}
    +#endif
    +#endif
    +#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
    +	HTSSLInitPRNG();
    +	status = SSL_connect(handle);
    +
    +	if (status <= 0) {
    +#if (SSLEAY_VERSION_NUMBER >= 0x0900)
    +#if !defined(USE_GNUTLS_FUNCS)
    +	    if (try_tls) {
    +		_HTProgress(gettext("Retrying connection without TLS."));
    +		try_tls = FALSE;
    +		if (did_connect)
    +		    HTTP_NETCLOSE(s, handle);
    +		goto try_again;
    +	    } else
    +#endif
    +	    {
    +		CTRACE((tfp,
    +			"HTTP: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
    +			url, status));
    +		SSL_load_error_strings();
    +		while ((SSLerror = ERR_get_error()) != 0) {
    +		    CTRACE((tfp, "HTTP: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
    +		}
    +		HTAlert("Unable to make secure connection to remote host.");
    +		if (did_connect)
    +		    HTTP_NETCLOSE(s, handle);
    +		status = HT_NOT_LOADED;
    +		goto done;
    +	    }
    +#else
    +	    unsigned long SSLerror;
    +
    +	    CTRACE((tfp,
    +		    "HTTP: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
    +		    url, status));
    +	    SSL_load_error_strings();
    +	    while ((SSLerror = ERR_get_error()) != 0) {
    +		CTRACE((tfp, "HTTP: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
    +	    }
    +	    HTAlert("Unable to make secure connection to remote host.");
    +	    if (did_connect)
    +		HTTP_NETCLOSE(s, handle);
    +	    status = HT_NOT_LOADED;
    +	    goto done;
    +#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
    +	}
    +#ifdef USE_GNUTLS_INCL
    +	gnutls_certificate_set_verify_flags(handle->gnutls_cred,
    +					    GNUTLS_VERIFY_DO_NOT_ALLOW_SAME |
    +					    GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
    +	ret = gnutls_certificate_verify_peers2(handle->gnutls_state, &tls_status);
    +	if (ret < 0 || tls_status != 0) {
    +	    int flag_continue = 1;
    +
    +#if GNUTLS_VERSION_NUMBER >= 0x030104
    +	    int type;
    +	    gnutls_datum_t out;
    +
    +	    if (ret < 0) {
    +		SSL_single_prompt(&msg,
    +				  gettext("GnuTLS error when trying to verify certificate."));
    +	    } else {
    +		type = gnutls_certificate_type_get(handle->gnutls_state);
    +		(void) gnutls_certificate_verification_status_print(tls_status,
    +								    type,
    +								    &out, 0);
    +		SSL_single_prompt(&msg, (const char *) out.data);
    +		gnutls_free(out.data);
    +	    }
    +#else
    +	    char *msg2;
    +
    +	    if (ret == 0 && tls_status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
    +		msg2 = gettext("the certificate has no known issuer");
    +	    } else if (tls_status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
    +		msg2 = gettext("no issuer was found");
    +	    } else if (tls_status & GNUTLS_CERT_SIGNER_NOT_CA) {
    +		msg2 = gettext("issuer is not a CA");
    +	    } else if (tls_status & GNUTLS_CERT_REVOKED) {
    +		msg2 = gettext("the certificate has been revoked");
    +	    } else {
    +		msg2 = gettext("the certificate is not trusted");
    +	    }
    +	    SSL_single_prompt(&msg, msg2);
    +#endif
    +	    CTRACE((tfp, "HTLoadHTTP: %s\n", msg));
    +	    if (!ssl_noprompt) {
    +		if (!HTForcedPrompt(ssl_noprompt, msg, NO)) {
    +		    flag_continue = 0;
    +		}
    +	    } else if (ssl_noprompt == FORCE_PROMPT_NO) {
    +		flag_continue = 0;
    +	    }
    +	    FREE(msg);
    +	    if (flag_continue == 0) {
    +		status = HT_NOT_LOADED;
    +		FREE(msg);
    +		goto done;
    +	    }
    +	}
    +#endif
    +
    +	peer_cert = (X509 *) SSL_get_peer_certificate(handle);
    +#if defined(USE_OPENSSL_INCL) || defined(USE_GNUTLS_FUNCS)
    +	X509_NAME_oneline(X509_get_subject_name(peer_cert),
    +			  ssl_dn, (int) sizeof(ssl_dn));
    +#elif defined(USE_GNUTLS_INCL)
    +	X509_NAME_oneline(X509_get_subject_name(peer_cert),
    +			  ssl_dn + 1, (int) sizeof(ssl_dn) - 1);
    +
    +	/* Iterate over DN in incompatible GnuTLS format to bring it into OpenSSL format */
    +	ssl_dn[0] = '/';
    +	ssl_dn_start = ssl_dn;
    +	while (*ssl_dn_start) {
    +	    if ((*ssl_dn_start == ',') && (*(ssl_dn_start + 1) == ' ')) {
    +		*ssl_dn_start++ = '/';
    +		if (*(p = ssl_dn_start) != 0) {
    +		    while ((p[0] = p[1]) != 0)
    +			++p;
    +		}
    +	    } else {
    +		ssl_dn_start++;
    +	    }
    +	}
    +#endif
    +
    +	/*
    +	 * X.509 DN validation taking ALL CN fields into account
    +	 * (c) 2006 Thorsten Glaser 
    +	 */
    +
    +	/* initialise status information */
    +	status_sslcertcheck = 0;	/* 0 = no CN found in DN */
    +	ssl_dn_start = ssl_dn;
    +
    +	/* validate all CNs found in DN */
    +	CTRACE((tfp, "Validating CNs in '%s'\n", ssl_dn_start));
    +	while ((cert_host = strstr(ssl_dn_start, "/CN=")) != NULL) {
    +	    status_sslcertcheck = 1;	/* 1 = could not verify CN */
    +	    /* start of CommonName */
    +	    cert_host += 4;
    +	    /* find next part of DistinguishedName */
    +	    if ((p = StrChr(cert_host, '/')) != NULL) {
    +		*p = '\0';
    +		ssl_dn_start = p;	/* yes this points to the NUL byte */
    +	    } else
    +		ssl_dn_start = NULL;
    +	    cert_host = StripIpv6Brackets(cert_host);
    +
    +	    /* verify this CN */
    +	    CTRACE((tfp, "Matching\n\tssl_host  '%s'\n\tcert_host '%s'\n",
    +		    ssl_host, cert_host));
    +	    if (!strcasecomp_asterisk(ssl_host, cert_host)) {
    +		status_sslcertcheck = 2;	/* 2 = verified peer */
    +		/* I think this is cool to have in the logs -TG */
    +		HTSprintf0(&msg,
    +			   gettext("Verified connection to %s (cert=%s)"),
    +			   ssl_host, cert_host);
    +		_HTProgress(msg);
    +		FREE(msg);
    +		/* no need to continue the verification loop */
    +		break;
    +	    }
    +
    +	    /* add this CN to list of failed CNs */
    +	    if (ssl_all_cns == NULL)
    +		StrAllocCopy(ssl_all_cns, "CN<");
    +	    else
    +		StrAllocCat(ssl_all_cns, ":CN<");
    +	    StrAllocCat(ssl_all_cns, cert_host);
    +	    StrAllocCat(ssl_all_cns, ">");
    +	    /* if we cannot retry, don't try it */
    +	    if (ssl_dn_start == NULL)
    +		break;
    +	    /* now retry next CN found in DN */
    +	    *ssl_dn_start = '/';	/* formerly NUL byte */
    +	}
    +
    +	/* check the X.509v3 Subject Alternative Name */
    +#ifdef USE_GNUTLS_INCL
    +	if (status_sslcertcheck < 2) {
    +	    int i;
    +	    size_t size;
    +	    gnutls_x509_crt_t cert;
    +	    static char buf[2048];
    +
    +	    /* import the certificate to the x509_crt format */
    +	    if (gnutls_x509_crt_init(&cert) == 0) {
    +
    +		if (gnutls_x509_crt_import(cert, peer_cert,
    +					   GNUTLS_X509_FMT_DER) < 0) {
    +		    gnutls_x509_crt_deinit(cert);
    +		    goto done;
    +		}
    +
    +		ret = 0;
    +		for (i = 0; !(ret < 0); i++) {
    +		    size = sizeof(buf);
    +		    ret = gnutls_x509_crt_get_subject_alt_name(cert,
    +							       (unsigned) i,
    +							       buf, &size,
    +							       NULL);
    +
    +		    if (strcasecomp_asterisk(ssl_host, buf) == 0) {
    +			status_sslcertcheck = 2;
    +			HTSprintf0(&msg,
    +				   gettext("Verified connection to %s (subj=%s)"),
    +				   ssl_host, buf);
    +			_HTProgress(msg);
    +			FREE(msg);
    +			break;
    +		    }
    +
    +		}
    +	    }
    +	}
    +#endif
    +#ifdef USE_OPENSSL_INCL
    +	if (status_sslcertcheck < 2) {
    +	    STACK_OF(GENERAL_NAME) * gens;
    +	    int i, numalts;
    +	    const GENERAL_NAME *gn;
    +
    +	    gens = (STACK_OF(GENERAL_NAME) *)
    +		X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL);
    +
    +	    if (gens != NULL) {
    +		numalts = sk_GENERAL_NAME_num(gens);
    +		for (i = 0; i < numalts; ++i) {
    +		    gn = sk_GENERAL_NAME_value(gens, i);
    +		    if (gn->type == GEN_DNS)
    +			cert_host = (char *) ASN1_STRING_data(gn->d.ia5);
    +		    else if (gn->type == GEN_IPADD) {
    +			/* XXX untested -TG */
    +			size_t j = (size_t) ASN1_STRING_length(gn->d.ia5);
    +
    +			cert_host = (char *) malloc(j + 1);
    +			MemCpy(cert_host, ASN1_STRING_data(gn->d.ia5), j);
    +			cert_host[j] = '\0';
    +		    } else
    +			continue;
    +		    status_sslcertcheck = 1;	/* got at least one */
    +		    /* verify this SubjectAltName (see above) */
    +		    cert_host = StripIpv6Brackets(cert_host);
    +		    if (!(gn->type == GEN_IPADD ? strcasecomp :
    +			  strcasecomp_asterisk) (ssl_host, cert_host)) {
    +			status_sslcertcheck = 2;
    +			HTSprintf0(&msg,
    +				   gettext("Verified connection to %s (subj=%s)"),
    +				   ssl_host, cert_host);
    +			_HTProgress(msg);
    +			FREE(msg);
    +			if (gn->type == GEN_IPADD)
    +			    free(cert_host);
    +			break;
    +		    }
    +		    /* add to list of failed CNs */
    +		    if (ssl_all_cns == NULL)
    +			StrAllocCopy(ssl_all_cns, "SAN<");
    +		    else
    +			StrAllocCat(ssl_all_cns, ":SAN<");
    +		    if (gn->type == GEN_DNS)
    +			StrAllocCat(ssl_all_cns, "DNS=");
    +		    else if (gn->type == GEN_IPADD)
    +			StrAllocCat(ssl_all_cns, "IP=");
    +		    StrAllocCat(ssl_all_cns, cert_host);
    +		    StrAllocCat(ssl_all_cns, ">");
    +		    if (gn->type == GEN_IPADD)
    +			free(cert_host);
    +		}
    +		sk_GENERAL_NAME_free(gens);
    +	    }
    +	}
    +#endif /* USE_OPENSSL_INCL */
    +
    +	/* if an error occurred, format the appropriate message */
    +	if (status_sslcertcheck == 0) {
    +	    SSL_single_prompt(&msg,
    +			      gettext("Can't find common name in certificate"));
    +	} else if (status_sslcertcheck == 1) {
    +	    SSL_double_prompt(&msg,
    +			      gettext("SSL error:host(%s)!=cert(%s)-Continue?"),
    +			      ssl_host, ssl_all_cns);
    +	}
    +
    +	/* if an error occurred, let the user decide how much he trusts */
    +	if (status_sslcertcheck < 2) {
    +	    if (msg == NULL)
    +		StrAllocCopy(msg, gettext("SSL error"));
    +	    if (!HTForcedPrompt(ssl_noprompt, msg, NO)) {
    +		status = HT_NOT_LOADED;
    +		FREE(msg);
    +		FREE(ssl_all_cns);
    +		goto done;
    +	    }
    +	    SSL_double_prompt(&msg,
    +			      gettext("UNVERIFIED connection to %s (cert=%s)"),
    +			      ssl_host, ssl_all_cns ? ssl_all_cns : "NONE");
    +	    _HTProgress(msg);
    +	    FREE(msg);
    +	}
    +
    +	show_cert_issuer(peer_cert);
    +
    +	HTSprintf0(&msg,
    +		   gettext("Secure %d-bit %s (%s) HTTP connection"),
    +		   SSL_get_cipher_bits(handle, NULL),
    +		   SSL_get_cipher_version(handle),
    +		   SSL_get_cipher(handle));
    +	_HTProgress(msg);
    +	FREE(msg);
    +	FREE(ssl_all_cns);
    +	FREE(ssl_host);
    +    }
    +#endif /* USE_SSL */
    +
    +    /* Ask that node for the document, omitting the host name & anchor
    +     */
    +    {
    +	char *p1 = (HTParse(url, "", PARSE_PATH | PARSE_PUNCTUATION));
    +
    +#ifdef USE_SSL
    +	if (do_connect) {
    +	    METHOD = "CONNECT";
    +	    BStrCopy0(command, "CONNECT ");
    +	} else
    +#endif /* USE_SSL */
    +	if (do_post) {
    +	    METHOD = "POST";
    +	    BStrCopy0(command, "POST ");
    +	} else if (do_head) {
    +	    METHOD = "HEAD";
    +	    BStrCopy0(command, "HEAD ");
    +	} else {
    +	    METHOD = "GET";
    +	    BStrCopy0(command, "GET ");
    +	}
    +
    +	/*
    +	 * If we are using a proxy gateway don't copy in the first slash of
    +	 * say:  /gopher://a;lkdjfl;ajdf;lkj/;aldk/adflj so that just
    +	 * gopher://....  is sent.
    +	 */
    +#ifdef USE_SSL
    +	if (using_proxy && !did_connect) {
    +	    if (do_connect)
    +		BStrCat0(command, connect_host);
    +	    else
    +		BStrCat0(command, p1 + 1);
    +	}
    +#else
    +	if (using_proxy)
    +	    BStrCat0(command, p1 + 1);
    +#endif /* USE_SSL */
    +	else
    +	    BStrCat0(command, p1);
    +	FREE(p1);
    +    }
    +    if (extensions) {
    +	BStrCat0(command, " ");
    +	BStrCat0(command, ((HTprotocolLevel == HTTP_1_0)
    +			   ? "HTTP/1.0"
    +			   : "HTTP/1.1"));
    +    }
    +
    +    BStrCat0(command, crlf);	/* CR LF, as in rfc 977 */
    +
    +    if (extensions) {
    +	int n, i;
    +	char *host = NULL;
    +
    +	if ((host = HTParse(anAnchor->address, "", PARSE_HOST)) != NULL) {
    +	    strip_userid(host, TRUE);
    +	    HTBprintf(&command, "Host: %s%c%c", host, CR, LF);
    +	    FREE(host);
    +	}
    +	if (HTprotocolLevel >= HTTP_1_1) {
    +	    HTBprintf(&command, "Connection: close%c%c", CR, LF);
    +	}
    +
    +	if (!HTPresentations)
    +	    HTFormatInit();
    +	n = HTList_count(HTPresentations);
    +
    +	first_Accept = TRUE;
    +	len = 0;
    +	for (i = 0; i < n; i++) {
    +	    HTPresentation *pres =
    +	    (HTPresentation *) HTList_objectAt(HTPresentations, i);
    +
    +	    if (pres->get_accept) {
    +		if (pres->quality < 1.0) {
    +		    if (pres->maxbytes > 0) {
    +			sprintf(temp, ";q=%4.3f;mxb=%" PRI_off_t "",
    +				pres->quality, CAST_off_t (pres->maxbytes));
    +		    } else {
    +			sprintf(temp, ";q=%4.3f", pres->quality);
    +		    }
    +		} else if (pres->maxbytes > 0) {
    +		    sprintf(temp, ";mxb=%" PRI_off_t "", CAST_off_t (pres->maxbytes));
    +		} else {
    +		    temp[0] = '\0';
    +		}
    +		HTSprintf0(&linebuf, "%s%s%s",
    +			   (first_Accept ?
    +			    "Accept: " : ", "),
    +			   HTAtom_name(pres->rep),
    +			   temp);
    +		len += (int) strlen(linebuf);
    +		if (len > 252 && !first_Accept) {
    +		    BStrCat0(command, crlf);
    +		    HTSprintf0(&linebuf, "Accept: %s%s",
    +			       HTAtom_name(pres->rep),
    +			       temp);
    +		    len = (int) strlen(linebuf);
    +		}
    +		BStrCat0(command, linebuf);
    +		first_Accept = FALSE;
    +	    }
    +	}
    +	HTBprintf(&command, "%s*/*;q=0.01%c%c",
    +		  (first_Accept ?
    +		   "Accept: " : ", "), CR, LF);
    +
    +	/*
    +	 * FIXME:  suppressing the "Accept-Encoding" in this case is done to
    +	 * work around limitations of the presentation logic used for the
    +	 * command-line "-base" option.  The remote site may transmit the
    +	 * document gzip'd, but the ensuing logic in HTSaveToFile() would see
    +	 * the mime-type as gzip rather than text/html, and not prepend the
    +	 * base URL.  This is less efficient than accepting the compressed data
    +	 * and uncompressing it, adding the base URL but is simpler than
    +	 * augmenting the dump's presentation logic -TD
    +	 */
    +	if (LYPrependBaseToSource && dump_output_immediately) {
    +	    CTRACE((tfp,
    +		    "omit Accept-Encoding to work-around interaction with -source\n"));
    +	} else {
    +	    char *list = 0;
    +	    int j, k;
    +
    +	    for (j = 1; j < encodingALL; j <<= 1) {
    +		if (acceptEncoding(j)) {
    +		    for (k = 0; tbl_preferred_encoding[k].name != 0; ++k) {
    +			if (tbl_preferred_encoding[k].value == j) {
    +			    if (list != 0)
    +				StrAllocCat(list, ", ");
    +			    StrAllocCat(list, tbl_preferred_encoding[k].name);
    +			    break;
    +			}
    +		    }
    +		}
    +	    }
    +
    +	    if (list != 0) {
    +		HTBprintf(&command, "Accept-Encoding: %s%c%c", list, CR, LF);
    +		free(list);
    +	    }
    +	}
    +
    +	if (non_empty(language)) {
    +	    HTBprintf(&command, "Accept-Language: %s%c%c", language, CR, LF);
    +	}
    +
    +	if (non_empty(pref_charset)) {
    +	    BStrCat0(command, "Accept-Charset: ");
    +	    StrAllocCopy(linebuf, pref_charset);
    +	    if (linebuf[strlen(linebuf) - 1] == ',')
    +		linebuf[strlen(linebuf) - 1] = '\0';
    +	    LYLowerCase(linebuf);
    +	    if (strstr(linebuf, "iso-8859-1") == NULL)
    +		StrAllocCat(linebuf, ", iso-8859-1;q=0.01");
    +	    if (strstr(linebuf, "us-ascii") == NULL)
    +		StrAllocCat(linebuf, ", us-ascii;q=0.01");
    +	    BStrCat0(command, linebuf);
    +	    HTBprintf(&command, "%c%c", CR, LF);
    +	}
    +#if 0
    +	/*
    +	 * Promote 300 (Multiple Choices) replies, if supported, over 406 (Not
    +	 * Acceptable) replies.  - FM
    +	 *
    +	 * This used to be done in versions 2.7 and 2.8*, but violates the
    +	 * specs for transparent content negotiation and has the effect that
    +	 * servers supporting those specs will send 300 (Multiple Choices)
    +	 * instead of a normal response (e.g.  200 OK), since they will assume
    +	 * that the client wants to make the choice.  It is not clear whether
    +	 * there are any servers or sites for which sending this header really
    +	 * improves anything.
    +	 *
    +	 * If there ever is a need to send "Negotiate:  trans" and really mean
    +	 * it, we should send "Negotiate:  trans,trans" or similar, since that
    +	 * is semantically equivalent and some servers may ignore "Negotiate:
    +	 * trans" as a special case when it comes from Lynx (to work around the
    +	 * old faulty behavior).  - kw
    +	 *
    +	 * References:
    +	 * RFC 2295 (see also RFC 2296), and mail to lynx-dev and
    +	 * new-httpd@apache.org from Koen Holtman, Jan 1999.
    +	 */
    +	if (!do_post) {
    +	    HTBprintf(&command, "Negotiate: trans%c%c", CR, LF);
    +	}
    +#endif /* 0 */
    +
    +	/*
    +	 * When reloading give no-cache pragma to proxy server to make it
    +	 * refresh its cache.  -- Ari L.  
    +	 *
    +	 * Also send it as a Cache-Control header for HTTP/1.1.  - FM
    +	 */
    +	if (reloading) {
    +	    HTBprintf(&command, "Pragma: no-cache%c%c", CR, LF);
    +	    HTBprintf(&command, "Cache-Control: no-cache%c%c", CR, LF);
    +	}
    +
    +	if (LYSendUserAgent || no_useragent) {
    +	    if (non_empty(LYUserAgent)) {
    +		char *cp = LYSkipBlanks(LYUserAgent);
    +
    +		/* Won't send it at all if all blank - kw */
    +		if (*cp != '\0')
    +		    HTBprintf(&command, "User-Agent: %.*s%c%c",
    +			      INIT_LINE_SIZE - 15, LYUserAgent, CR, LF);
    +	    } else {
    +		HTBprintf(&command, "User-Agent: %s/%s  libwww-FM/%s%c%c",
    +			  HTAppName ? HTAppName : "unknown",
    +			  HTAppVersion ? HTAppVersion : "0.0",
    +			  HTLibraryVersion, CR, LF);
    +	    }
    +	}
    +
    +	if (non_empty(personal_mail_address) && !LYNoFromHeader) {
    +	    HTBprintf(&command, "From: %s%c%c", personal_mail_address, CR, LF);
    +	}
    +
    +	if (!(LYUserSpecifiedURL ||
    +	      LYNoRefererHeader || LYNoRefererForThis) &&
    +	    strcmp(HTLoadedDocumentURL(), "")) {
    +	    const char *cp = LYRequestReferer;
    +
    +	    if (!cp)
    +		cp = HTLoadedDocumentURL();	/* @@@ Try both? - kw */
    +	    BStrCat0(command, "Referer: ");
    +	    if (isLYNXIMGMAP(cp)) {
    +		char *pound = findPoundSelector(cp);
    +		int nn = (pound ? (int) (pound - cp) : (int) strlen(cp));
    +
    +		HTSABCat(&command, cp + LEN_LYNXIMGMAP, nn);
    +	    } else {
    +		BStrCat0(command, cp);
    +	    }
    +	    HTBprintf(&command, "%c%c", CR, LF);
    +	} {
    +	    char *abspath;
    +	    char *docname;
    +	    char *hostname;
    +	    char *colon;
    +	    int portnumber;
    +	    char *auth, *cookie = NULL;
    +	    BOOL secure = (BOOL) (StrNCmp(anAnchor->address, "https", 5)
    +				  ? FALSE
    +				  : TRUE);
    +
    +	    abspath = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION);
    +	    docname = HTParse(arg, "", PARSE_PATH);
    +	    hostname = HTParse(arg, "", PARSE_HOST);
    +	    if (hostname &&
    +		NULL != (colon = HTParsePort(hostname, &portnumber))) {
    +		*colon = '\0';	/* Chop off port number */
    +	    } else if (!StrNCmp(arg, "https", 5)) {
    +		portnumber = HTTPS_PORT;
    +	    } else {
    +		portnumber = HTTP_PORT;
    +	    }
    +
    +	    /*
    +	     * Add Authorization, Proxy-Authorization, and/or Cookie headers,
    +	     * if applicable.
    +	     */
    +	    if (using_proxy) {
    +		/*
    +		 * If we are using a proxy, first determine if we should
    +		 * include an Authorization header and/or Cookie header for the
    +		 * ultimate target of this request.  - FM & AJL
    +		 */
    +		char *host2 = NULL, *path2 = NULL;
    +		int port2 = (StrNCmp(docname, "https", 5) ?
    +			     HTTP_PORT : HTTPS_PORT);
    +
    +		host2 = HTParse(docname, "", PARSE_HOST);
    +		path2 = HTParse(docname, "", PARSE_PATH | PARSE_PUNCTUATION);
    +		if ((colon = HTParsePort(host2, &port2)) != NULL) {
    +		    /* Use non-default port number */
    +		    *colon = '\0';
    +		}
    +
    +		/*
    +		 * This composeAuth() does file access, i.e., for the ultimate
    +		 * target of the request.  - AJL
    +		 */
    +		auth_proxy = NO;
    +		auth = HTAA_composeAuth(host2, port2, path2, auth_proxy);
    +		if (auth == NULL) {
    +		    CTRACE((tfp, "HTTP: Not sending authorization (yet).\n"));
    +		} else if (*auth != '\0') {
    +		    /*
    +		     * We have an Authorization header to be included.
    +		     */
    +		    HTBprintf(&command, "%s%c%c", auth, CR, LF);
    +		    CTRACE((tfp, "HTTP: Sending authorization: %s\n", auth));
    +		} else {
    +		    /*
    +		     * The user either cancelled or made a mistake with the
    +		     * username and password prompt.
    +		     */
    +		    if (!(traversal || dump_output_immediately) &&
    +			HTConfirm(CONFIRM_WO_PASSWORD)) {
    +			show_401 = TRUE;
    +		    } else {
    +			if (traversal || dump_output_immediately)
    +			    HTAlert(FAILED_NEED_PASSWD);
    +#ifdef USE_SSL
    +			if (did_connect)
    +			    HTTP_NETCLOSE(s, handle);
    +#endif /* USE_SSL */
    +			BStrFree(command);
    +			FREE(hostname);
    +			FREE(docname);
    +			FREE(abspath);
    +			FREE(host2);
    +			FREE(path2);
    +			status = HT_NOT_LOADED;
    +			goto done;
    +		    }
    +		}
    +		/*
    +		 * Add 'Cookie:' header, if it's HTTP or HTTPS document being
    +		 * proxied.
    +		 */
    +		if (!StrNCmp(docname, "http", 4)) {
    +		    cookie = LYAddCookieHeader(host2, path2, port2, secure);
    +		}
    +		FREE(host2);
    +		FREE(path2);
    +		/*
    +		 * The next composeAuth() will be for the proxy.  - AJL
    +		 */
    +		auth_proxy = YES;
    +	    } else {
    +		/*
    +		 * Add cookie for a non-proxied request.  - FM
    +		 */
    +		cookie = LYAddCookieHeader(hostname, abspath, portnumber, secure);
    +		auth_proxy = NO;
    +	    }
    +	    /*
    +	     * If we do have a cookie set, add it to the request buffer.  - FM
    +	     */
    +	    if (cookie != NULL) {
    +		if (*cookie != '$' && USE_RFC_2965) {
    +		    /*
    +		     * It's a historical cookie, so signal to the server that
    +		     * we support modern cookies.  - FM
    +		     */
    +		    BStrCat0(command, "Cookie2: $Version=\"1\"");
    +		    BStrCat0(command, crlf);
    +		    CTRACE((tfp, "HTTP: Sending Cookie2: $Version =\"1\"\n"));
    +		}
    +		if (*cookie != '\0') {
    +		    /*
    +		     * It's not a zero-length string, so add the header.  Note
    +		     * that any folding of long strings has been done already
    +		     * in LYCookie.c.  - FM
    +		     */
    +		    BStrCat0(command, "Cookie: ");
    +		    BStrCat0(command, cookie);
    +		    BStrCat0(command, crlf);
    +		    CTRACE((tfp, "HTTP: Sending Cookie: %s\n", cookie));
    +		}
    +		FREE(cookie);
    +	    }
    +	    FREE(abspath);
    +
    +	    /*
    +	     * If we are using a proxy, auth_proxy should be YES, and we check
    +	     * here whether we want a Proxy-Authorization header for it.  If we
    +	     * are not using a proxy, auth_proxy should still be NO, and we
    +	     * check here for whether we want an Authorization header.  - FM &
    +	     * AJL
    +	     */
    +	    if ((auth = HTAA_composeAuth(hostname,
    +					 portnumber,
    +					 docname,
    +					 auth_proxy)) != NULL &&
    +		*auth != '\0') {
    +		/*
    +		 * If auth is not NULL nor zero-length, it's an Authorization
    +		 * or Proxy-Authorization header to be included.  - FM
    +		 */
    +		HTBprintf(&command, "%s%c%c", auth, CR, LF);
    +		CTRACE((tfp, (auth_proxy ?
    +			      "HTTP: Sending proxy authorization: %s\n" :
    +			      "HTTP: Sending authorization: %s\n"),
    +			auth));
    +	    } else if (auth && *auth == '\0') {
    +		/*
    +		 * If auth is a zero-length string, the user either cancelled
    +		 * or goofed at the username and password prompt.  - FM
    +		 */
    +		if (!(traversal || dump_output_immediately) && HTConfirm(CONFIRM_WO_PASSWORD)) {
    +		    if (auth_proxy == TRUE) {
    +			show_407 = TRUE;
    +		    } else {
    +			show_401 = TRUE;
    +		    }
    +		} else {
    +		    if (traversal || dump_output_immediately)
    +			HTAlert(FAILED_NEED_PASSWD);
    +		    BStrFree(command);
    +		    FREE(hostname);
    +		    FREE(docname);
    +		    status = HT_NOT_LOADED;
    +		    goto done;
    +		}
    +	    } else {
    +		CTRACE((tfp, (auth_proxy ?
    +			      "HTTP: Not sending proxy authorization (yet).\n" :
    +			      "HTTP: Not sending authorization (yet).\n")));
    +	    }
    +	    FREE(hostname);
    +	    FREE(docname);
    +	}
    +    }
    +
    +    if (
    +#ifdef USE_SSL
    +	   !do_connect &&
    +#endif /* USE_SSL */
    +	   do_post) {
    +	CTRACE((tfp, "HTTP: Doing post, content-type '%s'\n",
    +		anAnchor->post_content_type
    +		? anAnchor->post_content_type
    +		: "lose"));
    +	HTBprintf(&command, "Content-Type: %s%c%c",
    +		  anAnchor->post_content_type
    +		  ? anAnchor->post_content_type
    +		  : "lose",
    +		  CR, LF);
    +
    +	HTBprintf(&command, "Content-Length: %d%c%c",
    +		  !isBEmpty(anAnchor->post_data)
    +		  ? BStrLen(anAnchor->post_data)
    +		  : 0,
    +		  CR, LF);
    +
    +	BStrCat0(command, crlf);	/* Blank line means "end" of headers */
    +
    +	BStrCat(command, anAnchor->post_data);
    +    } else
    +	BStrCat0(command, crlf);	/* Blank line means "end" of headers */
    +
    +    if (TRACE) {
    +	CTRACE((tfp, "Writing:\n"));
    +	trace_bstring(command);
    +#ifdef USE_SSL
    +	CTRACE((tfp, "%s",
    +		(anAnchor->post_data && !do_connect ? crlf : "")));
    +#else
    +	CTRACE((tfp, "%s",
    +		(anAnchor->post_data ? crlf : "")));
    +#endif /* USE_SSL */
    +	CTRACE((tfp, "----------------------------------\n"));
    +    }
    +
    +    _HTProgress(gettext("Sending HTTP request."));
    +
    +#ifdef    NOT_ASCII		/* S/390 -- gil -- 0548 */
    +    {
    +	char *p2;
    +
    +	for (p2 = BStrData(command);
    +	     p2 < BStrData(command) + BStrLen(command);
    +	     p2++)
    +	    *p2 = TOASCII(*p2);
    +    }
    +#endif /* NOT_ASCII */
    +    status = (int) HTTP_NETWRITE(s,
    +				 BStrData(command),
    +				 BStrLen(command),
    +				 handle);
    +    BStrFree(command);
    +    FREE(linebuf);
    +    if (status <= 0) {
    +	if (status == 0) {
    +	    CTRACE((tfp, "HTTP: Got status 0 in initial write\n"));
    +	    /* Do nothing. */
    +	} else if ((SOCKET_ERRNO == ENOTCONN ||
    +		    SOCKET_ERRNO == ECONNRESET ||
    +		    SOCKET_ERRNO == EPIPE) &&
    +		   !already_retrying &&
    +	    /* Don't retry if we're posting. */ !do_post) {
    +	    /*
    +	     * Arrrrgh, HTTP 0/1 compatibility problem, maybe.
    +	     */
    +	    CTRACE((tfp,
    +		    "HTTP: BONZO ON WRITE Trying again with HTTP0 request.\n"));
    +	    _HTProgress(RETRYING_AS_HTTP0);
    +	    HTTP_NETCLOSE(s, handle);
    +	    extensions = NO;
    +	    already_retrying = TRUE;
    +	    goto try_again;
    +	} else {
    +	    CTRACE((tfp,
    +		    "HTTP: Hit unexpected network WRITE error; aborting connection.\n"));
    +	    HTTP_NETCLOSE(s, handle);
    +	    status = -1;
    +	    HTAlert(gettext("Unexpected network write error; connection aborted."));
    +	    goto done;
    +	}
    +    }
    +
    +    CTRACE((tfp, "HTTP: WRITE delivered OK\n"));
    +    _HTProgress(gettext("HTTP request sent; waiting for response."));
    +
    +    /*    Read the first line of the response
    +     * -----------------------------------
    +     */
    +    {
    +	/* Get numeric status etc */
    +	BOOL end_of_file = NO;
    +	int buffer_length = INIT_LINE_SIZE;
    +
    +	line_buffer = typecallocn(char, (size_t) buffer_length);
    +
    +	if (line_buffer == NULL)
    +	    outofmem(__FILE__, "HTLoadHTTP");
    +
    +	HTReadProgress(bytes_already_read = 0, (off_t) 0);
    +	do {			/* Loop to read in the first line */
    +	    /*
    +	     * Extend line buffer if necessary for those crazy WAIS URLs ;-)
    +	     */
    +	    if (buffer_length - length < LINE_EXTEND_THRESH) {
    +		buffer_length = buffer_length + buffer_length;
    +		line_buffer =
    +		    (char *) realloc(line_buffer, ((unsigned) buffer_length *
    +						   sizeof(char)));
    +
    +		if (line_buffer == NULL)
    +		    outofmem(__FILE__, "HTLoadHTTP");
    +	    }
    +	    CTRACE((tfp, "HTTP: Trying to read %d\n", buffer_length - length - 1));
    +	    status = HTTP_NETREAD(s,
    +				  line_buffer + length,
    +				  (buffer_length - length - 1),
    +				  handle);
    +	    CTRACE((tfp, "HTTP: Read %d\n", status));
    +	    if (status <= 0) {
    +		/*
    +		 * Retry if we get nothing back too.
    +		 * Bomb out if we get nothing twice.
    +		 */
    +		if (status == HT_INTERRUPTED) {
    +		    CTRACE((tfp, "HTTP: Interrupted initial read.\n"));
    +		    _HTProgress(CONNECTION_INTERRUPTED);
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = HT_NO_DATA;
    +		    goto clean_up;
    +		} else if (status < 0 &&
    +			   (SOCKET_ERRNO == ENOTCONN ||
    +#ifdef _WINDOWS			/* 1997/11/09 (Sun) 16:59:58 */
    +			    SOCKET_ERRNO == ETIMEDOUT ||
    +#endif
    +			    SOCKET_ERRNO == ECONNRESET ||
    +			    SOCKET_ERRNO == EPIPE) &&
    +			   !already_retrying && !do_post) {
    +		    /*
    +		     * Arrrrgh, HTTP 0/1 compatibility problem, maybe.
    +		     */
    +		    CTRACE((tfp,
    +			    "HTTP: BONZO Trying again with HTTP0 request.\n"));
    +		    HTTP_NETCLOSE(s, handle);
    +		    FREE(line_buffer);
    +		    FREE(line_kept_clean);
    +
    +		    extensions = NO;
    +		    already_retrying = TRUE;
    +		    _HTProgress(RETRYING_AS_HTTP0);
    +		    goto try_again;
    +		}
    +#ifdef USE_SSL
    +		else if ((SSLerror = ERR_get_error()) != 0) {
    +		    CTRACE((tfp,
    +			    "HTTP: Hit unexpected network read error; aborting connection; status %d:%s.\n",
    +			    status, ERR_error_string(SSLerror, NULL)));
    +		    HTAlert(gettext("Unexpected network read error; connection aborted."));
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = -1;
    +		    goto clean_up;
    +		}
    +#endif
    +		else {
    +		    CTRACE((tfp,
    +			    "HTTP: Hit unexpected network read error; aborting connection; status %d.\n",
    +			    status));
    +		    HTAlert(gettext("Unexpected network read error; connection aborted."));
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = -1;
    +		    goto clean_up;
    +		}
    +	    }
    +#ifdef    NOT_ASCII		/* S/390 -- gil -- 0564 */
    +	    {
    +		char *p2;
    +
    +		for (p2 = line_buffer + length;
    +		     p2 < line_buffer + length + status;
    +		     p2++)
    +		    *p2 = FROMASCII(*p2);
    +	    }
    +#endif /* NOT_ASCII */
    +
    +	    bytes_already_read += status;
    +	    HTReadProgress(bytes_already_read, (off_t) 0);
    +
    +#ifdef UCX			/* UCX returns -1 on EOF */
    +	    if (status == 0 || status == -1)
    +#else
    +	    if (status == 0)
    +#endif
    +	    {
    +		break;
    +	    }
    +	    line_buffer[length + status] = 0;
    +
    +	    if (line_buffer) {
    +		FREE(line_kept_clean);
    +		line_kept_clean = (char *) malloc((unsigned) buffer_length *
    +						  sizeof(char));
    +
    +		if (line_kept_clean == NULL)
    +		    outofmem(__FILE__, "HTLoadHTTP");
    +		MemCpy(line_kept_clean, line_buffer, buffer_length);
    +#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
    +		real_length_of_line = length + status;
    +#endif
    +	    }
    +
    +	    eol = StrChr(line_buffer + length, LF);
    +	    /* Do we *really* want to do this? */
    +	    if (eol && eol != line_buffer && *(eol - 1) == CR)
    +		*(eol - 1) = ' ';
    +
    +	    length = length + status;
    +
    +	    /* Do we really want to do *this*? */
    +	    if (eol)
    +		*eol = 0;	/* Terminate the line */
    +	}
    +	/* All we need is the first line of the response.  If it's a HTTP/1.0
    +	 * response, then the first line will be absurdly short and therefore
    +	 * we can safely gate the number of bytes read through this code (as
    +	 * opposed to below) to ~1000.
    +	 *
    +	 * Well, let's try 100.
    +	 */
    +	while (!eol && !end_of_file && bytes_already_read < 100);
    +    }				/* Scope of loop variables */
    +
    +    /* save total length, in case we decide later to show it all - kw */
    +    rawlength = length;
    +
    +    /*    We now have a terminated unfolded line.  Parse it.
    +     * --------------------------------------------------
    +     */
    +    CTRACE((tfp, "HTTP: Rx: %s\n", line_buffer));
    +
    +    /*
    +     * Kludge to work with old buggy servers and the VMS Help gateway.  They
    +     * can't handle the third word, so we try again without it.
    +     */
    +    if (extensions &&		/* Old buggy server or Help gateway? */
    +	(0 == StrNCmp(line_buffer, "Bad File Request", 31) ||
    +	 0 == StrNCmp(line_buffer, "Address should begin with", 25) ||
    +	 0 == StrNCmp(line_buffer, "Help ", 12) ||
    +	 0 == strcmp(line_buffer,
    +		     "Document address invalid or access not authorised"))) {
    +	FREE(line_buffer);
    +	FREE(line_kept_clean);
    +	extensions = NO;
    +	already_retrying = TRUE;
    +	CTRACE((tfp, "HTTP: close socket %d to retry with HTTP0\n", s));
    +	HTTP_NETCLOSE(s, handle);
    +	/* print a progress message */
    +	_HTProgress(RETRYING_AS_HTTP0);
    +	goto try_again;
    +    } {
    +	int fields;
    +	char server_version[VERSION_LENGTH + 1];
    +
    +	server_version[0] = 0;
    +
    +	fields = sscanf(line_buffer, "%20s %d",
    +			server_version,
    +			&server_status);
    +
    +	CTRACE((tfp, "HTTP: Scanned %d fields from line_buffer\n", fields));
    +
    +	if (non_empty(http_error_file)) {
    +	    /* Make the status code externally available */
    +	    FILE *error_file;
    +
    +#ifdef SERVER_STATUS_ONLY
    +	    error_file = fopen(http_error_file, TXT_W);
    +	    if (error_file) {	/* Managed to open the file */
    +		fprintf(error_file, "error=%d\n", server_status);
    +		fclose(error_file);
    +	    }
    +#else
    +	    error_file = fopen(http_error_file, TXT_A);
    +	    if (error_file) {	/* Managed to open the file */
    +		fprintf(error_file, "   URL=%s (%s)\n", url, METHOD);
    +		fprintf(error_file, "STATUS=%s\n", line_buffer);
    +		fclose(error_file);
    +	    }
    +#endif /* SERVER_STATUS_ONLY */
    +	}
    +
    +	/*
    +	 * Rule out a non-HTTP/1.n reply as best we can.
    +	 */
    +	if (fields < 2 || !server_version[0] || server_version[0] != 'H' ||
    +	    server_version[1] != 'T' || server_version[2] != 'T' ||
    +	    server_version[3] != 'P' || server_version[4] != '/' ||
    +	    server_version[6] != '.') {
    +	    /*
    +	     * Ugh!  An HTTP0 reply,
    +	     */
    +	    HTAtom *encoding;
    +
    +	    CTRACE((tfp, "--- Talking HTTP0.\n"));
    +
    +	    format_in = HTFileFormat(url, &encoding, NULL);
    +	    /*
    +	     * Treat all plain text as HTML.  This sucks but its the only
    +	     * solution without without looking at content.
    +	     */
    +	    if (!StrNCmp(HTAtom_name(format_in), STR_PLAINTEXT, 10)) {
    +		CTRACE((tfp, "HTTP: format_in being changed to text/HTML\n"));
    +		format_in = WWW_HTML;
    +	    }
    +	    if (!IsUnityEnc(encoding)) {
    +		/*
    +		 * Change the format to that for "www/compressed".
    +		 */
    +		CTRACE((tfp, "HTTP: format_in is '%s',\n", HTAtom_name(format_in)));
    +		StrAllocCopy(anAnchor->content_type, HTAtom_name(format_in));
    +		StrAllocCopy(anAnchor->content_encoding, HTAtom_name(encoding));
    +		format_in = HTAtom_for("www/compressed");
    +		CTRACE((tfp, "        Treating as '%s' with encoding '%s'\n",
    +			"www/compressed", HTAtom_name(encoding)));
    +	    }
    +
    +	    start_of_data = line_kept_clean;
    +	} else {
    +	    /*
    +	     * Set up to decode full HTTP/1.n response.  - FM
    +	     */
    +	    format_in = HTAtom_for("www/mime");
    +	    CTRACE((tfp, "--- Talking HTTP1.\n"));
    +
    +	    /*
    +	     * We set start_of_data to "" when !eol here because there will be
    +	     * a put_block done below; we do *not* use the value of
    +	     * start_of_data (as a pointer) in the computation of length (or
    +	     * anything else) when !eol.  Otherwise, set the value of length to
    +	     * what we have beyond eol (i.e., beyond the status line).  - FM
    +	     */
    +	    if (eol != 0) {
    +		start_of_data = (eol + 1);
    +	    } else {
    +		start_of_data = empty;
    +	    }
    +	    length = (eol
    +		      ? length - (int) (start_of_data - line_buffer)
    +		      : 0);
    +
    +	    /*
    +	     * Trim trailing spaces in line_buffer so that we can use it in
    +	     * messages which include the status line.  - FM
    +	     */
    +	    while (line_buffer[strlen(line_buffer) - 1] == ' ')
    +		line_buffer[strlen(line_buffer) - 1] = '\0';
    +
    +	    /*
    +	     * Take appropriate actions based on the status.  - FM
    +	     */
    +	    switch (server_status / 100) {
    +	    case 1:
    +		/*
    +		 * HTTP/1.1 Informational statuses.
    +		 * 100 Continue.
    +		 * 101 Switching Protocols.
    +		 * > 101 is unknown.
    +		 * We should never get these, and they have only the status
    +		 * line and possibly other headers, so we'll deal with them by
    +		 * showing the full header to the user as text/plain.  - FM
    +		 */
    +		HTAlert(gettext("Got unexpected Informational Status."));
    +		do_head = TRUE;
    +		break;
    +
    +	    case 2:
    +		/*
    +		 * Good:  Got MIME object!  (Successful) - FM
    +		 */
    +		if (do_head) {
    +		    /*
    +		     * If HEAD was requested, show headers (and possibly bogus
    +		     * body) for all 2xx status codes as text/plain - KW
    +		     */
    +		    HTProgress(line_buffer);
    +		    break;
    +		}
    +		switch (server_status) {
    +		case 204:
    +		    /*
    +		     * No Content.
    +		     */
    +		    HTAlert(line_buffer);
    +		    HTTP_NETCLOSE(s, handle);
    +		    HTNoDataOK = 1;
    +		    status = HT_NO_DATA;
    +		    goto clean_up;
    +
    +		case 205:
    +		    /*
    +		     * Reset Content.  The server has fulfilled the request but
    +		     * nothing is returned and we should reset any form
    +		     * content.  We'll instruct the user to do that, and
    +		     * restore the current document.  - FM
    +		     */
    +		    HTAlert(gettext("Request fulfilled.  Reset Content."));
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = HT_NO_DATA;
    +		    goto clean_up;
    +
    +		case 206:
    +		    /*
    +		     * Partial Content.  We didn't send a Range so something
    +		     * went wrong somewhere.  Show the status message and
    +		     * restore the current document.  - FM
    +		     */
    +		    HTAlert(line_buffer);
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = HT_NO_DATA;
    +		    goto clean_up;
    +
    +		default:
    +		    /*
    +		     * 200 OK.
    +		     * 201 Created.
    +		     * 202 Accepted.
    +		     * 203 Non-Authoritative Information.
    +		     * > 206 is unknown.
    +		     * All should return something to display.
    +		     */
    +#if defined(USE_SSL)		/* && !defined(DISABLE_NEWS)  _H */
    +		    if (do_connect) {
    +			CTRACE((tfp,
    +				"HTTP: Proxy tunnel to '%s' established.\n",
    +				connect_host));
    +			do_connect = FALSE;
    +			url = connect_url;
    +			FREE(line_buffer);
    +			FREE(line_kept_clean);
    +#ifndef DISABLE_NEWS
    +			if (!StrNCmp(connect_url, "snews", 5)) {
    +			    CTRACE((tfp,
    +				    "      Will attempt handshake and snews connection.\n"));
    +			    status = HTNewsProxyConnect(s, url, anAnchor,
    +							format_out, sink);
    +			    goto done;
    +			}
    +#endif /* DISABLE_NEWS */
    +			did_connect = TRUE;
    +			already_retrying = TRUE;
    +			eol = 0;
    +			length = 0;
    +			doing_redirect = FALSE;
    +			permanent_redirection = FALSE;
    +			target = NULL;
    +			CTRACE((tfp,
    +				"      Will attempt handshake and resubmit headers.\n"));
    +			goto use_tunnel;
    +		    }
    +#endif /* USE_SSL */
    +		    HTProgress(line_buffer);
    +		}		/* case 2 switch */
    +		break;
    +
    +	    case 3:
    +		/*
    +		 * Various forms of Redirection.  - FM
    +		 * 300 Multiple Choices.
    +		 * 301 Moved Permanently.
    +		 * 302 Found (temporary; we can, and do, use GET).
    +		 * 303 See Other (temporary; always use GET).
    +		 * 304 Not Modified.
    +		 * 305 Use Proxy.
    +		 * 306 Set Proxy.
    +		 * 307 Temporary Redirect with method retained.
    +		 * > 308 is unknown.
    +		 */
    +		if (no_url_redirection || do_head || keep_mime_headers) {
    +		    /*
    +		     * If any of these flags are set, we do not redirect, but
    +		     * instead show what was returned to the user as
    +		     * text/plain.  - FM
    +		     */
    +		    HTProgress(line_buffer);
    +		    break;
    +		}
    +
    +		if (server_status == 300) {	/* Multiple Choices */
    +		    /*
    +		     * For client driven content negotiation.  The server
    +		     * should be sending some way for the user-agent to make a
    +		     * selection, so we'll show the user whatever the server
    +		     * returns.  There might be a Location:  header with the
    +		     * server's preference present, but the choice should be up
    +		     * to the user, someday based on an Alternates:  header,
    +		     * and a body always should be present with descriptions
    +		     * and links for the choices (i.e., we use the latter, for
    +		     * now).  - FM
    +		     */
    +		    HTAlert(line_buffer);
    +		    if (traversal) {
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			goto clean_up;
    +		    }
    +		    if (!dump_output_immediately &&
    +			format_out == WWW_DOWNLOAD) {
    +			/*
    +			 * Convert a download request to a presentation request
    +			 * for interactive users.  - FM
    +			 */
    +			format_out = WWW_PRESENT;
    +		    }
    +		    break;
    +		}
    +
    +		if (server_status == 304) {	/* Not Modified */
    +		    /*
    +		     * We didn't send an "If-Modified-Since" header, so this
    +		     * status is inappropriate.  We'll deal with it by showing
    +		     * the full header to the user as text/plain.  - FM
    +		     */
    +		    HTAlert(gettext("Got unexpected 304 Not Modified status."));
    +		    do_head = TRUE;
    +		    break;
    +		}
    +
    +		if (server_status == 305 ||
    +		    server_status == 306 ||
    +		    server_status > 307) {
    +		    /*
    +		     * Show user the content, if any, for 305, 306, or unknown
    +		     * status.  - FM
    +		     */
    +		    HTAlert(line_buffer);
    +		    if (traversal) {
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			goto clean_up;
    +		    }
    +		    if (!dump_output_immediately &&
    +			format_out == WWW_DOWNLOAD) {
    +			/*
    +			 * Convert a download request to a presentation request
    +			 * for interactive users.  - FM
    +			 */
    +			format_out = WWW_PRESENT;
    +		    }
    +		    break;
    +		}
    +
    +		/*
    +		 * We do not load the file, but read the headers for the
    +		 * "Location:", check out that redirecting_url and if it's
    +		 * acceptable (e.g., not a telnet URL when we have that
    +		 * disabled), initiate a new fetch.  If that's another
    +		 * redirecting_url, we'll repeat the checks, and fetch
    +		 * initiations if acceptable, until we reach the actual URL, or
    +		 * the redirection limit set in HTAccess.c is exceeded.  If the
    +		 * status was 301 indicating that the relocation is permanent,
    +		 * we set the permanent_redirection flag to make it permanent
    +		 * for the current anchor tree (i.e., will persist until the
    +		 * tree is freed or the client exits).  If the redirection
    +		 * would include POST content, we seek confirmation from an
    +		 * interactive user, with option to use 303 for 301 (but not
    +		 * for 307), and otherwise refuse the redirection.  We also
    +		 * don't allow permanent redirection if we keep POST content.
    +		 * If we don't find the Location header or it's value is
    +		 * zero-length, we display whatever the server returned, and
    +		 * the user should RELOAD that to try again, or make a
    +		 * selection from it if it contains links, or Left-Arrow to the
    +		 * previous document.  - FM
    +		 */
    +		{
    +		    if ((dump_output_immediately || traversal) &&
    +			do_post &&
    +			server_status != 303 &&
    +			server_status != 302 &&
    +			server_status != 301) {
    +			/*
    +			 * Don't redirect POST content without approval from an
    +			 * interactive user.  - FM
    +			 */
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			HTAlert(gettext("Redirection of POST content requires user approval."));
    +			if (traversal)
    +			    HTProgress(line_buffer);
    +			goto clean_up;
    +		    }
    +
    +		    HTProgress(line_buffer);
    +		    if (server_status == 301) {		/* Moved Permanently */
    +			if (do_post) {
    +			    /*
    +			     * Don't make the redirection permanent if we have
    +			     * POST content.  - FM
    +			     */
    +			    CTRACE((tfp,
    +				    "HTTP: Have POST content.  Treating 301 (Permanent) as Temporary.\n"));
    +			    HTAlert(gettext("Have POST content.  Treating Permanent Redirection as Temporary.\n"));
    +			} else {
    +			    permanent_redirection = TRUE;
    +			}
    +		    }
    +		    doing_redirect = TRUE;
    +
    +		    break;
    +		}
    +
    +	    case 4:
    +		/*
    +		 * "I think I goofed!" (Client Error) - FM
    +		 */
    +		switch (server_status) {
    +		case 401:	/* Unauthorized */
    +		    /*
    +		     * Authorization for origin server required.  If show_401
    +		     * is set, proceed to showing the 401 body.  Otherwise, if
    +		     * we can set up authorization based on the
    +		     * WWW-Authenticate header, and the user provides a
    +		     * username and password, try again.  Otherwise, check
    +		     * whether to show the 401 body or restore the current
    +		     * document - FM
    +		     */
    +		    if (show_401)
    +			break;
    +		    if (HTAA_shouldRetryWithAuth(start_of_data, (size_t)
    +						 length, s, NO)) {
    +
    +			HTTP_NETCLOSE(s, handle);
    +			if (dump_output_immediately &&
    +			    !HTAA_HaveUserinfo(HTParse(arg, "", PARSE_HOST)) &&
    +			    !authentication_info[0]) {
    +			    fprintf(stderr,
    +				    "HTTP: Access authorization required.\n");
    +			    fprintf(stderr,
    +				    "       Use the -auth=id:pw parameter.\n");
    +			    status = HT_NO_DATA;
    +			    goto clean_up;
    +			}
    +
    +			CTRACE((tfp, "%s %d %s\n",
    +				"HTTP: close socket", s,
    +				"to retry with Access Authorization"));
    +
    +			_HTProgress(gettext("Retrying with access authorization information."));
    +			FREE(line_buffer);
    +			FREE(line_kept_clean);
    +#ifdef USE_SSL
    +			if (using_proxy && !StrNCmp(url, "https://", 8)) {
    +			    url = arg;
    +			    do_connect = TRUE;
    +			    did_connect = FALSE;
    +			}
    +#endif /* USE_SSL */
    +			goto try_again;
    +		    } else if (!(traversal || dump_output_immediately) &&
    +			       HTConfirm(gettext("Show the 401 message body?"))) {
    +			break;
    +		    } else {
    +			if (traversal || dump_output_immediately)
    +			    HTAlert(FAILED_RETRY_WITH_AUTH);
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			goto clean_up;
    +		    }
    +
    +		case 407:
    +		    /*
    +		     * Authorization for proxy server required.  If we are not
    +		     * in fact using a proxy, or show_407 is set, proceed to
    +		     * showing the 407 body.  Otherwise, if we can set up
    +		     * authorization based on the Proxy-Authenticate header,
    +		     * and the user provides a username and password, try
    +		     * again.  Otherwise, check whether to show the 401 body or
    +		     * restore the current document.  - FM & AJL
    +		     */
    +		    if (!using_proxy || show_407)
    +			break;
    +		    if (HTAA_shouldRetryWithAuth(start_of_data, (size_t)
    +						 length, s, YES)) {
    +
    +			HTTP_NETCLOSE(s, handle);
    +			if (dump_output_immediately && !proxyauth_info[0]) {
    +			    fprintf(stderr,
    +				    "HTTP: Proxy authorization required.\n");
    +			    fprintf(stderr,
    +				    "       Use the -pauth=id:pw parameter.\n");
    +			    status = HT_NO_DATA;
    +			    goto clean_up;
    +			}
    +
    +			CTRACE((tfp, "%s %d %s\n",
    +				"HTTP: close socket", s,
    +				"to retry with Proxy Authorization"));
    +
    +			_HTProgress(HTTP_RETRY_WITH_PROXY);
    +			FREE(line_buffer);
    +			FREE(line_kept_clean);
    +			goto try_again;
    +		    } else if (!(traversal || dump_output_immediately) &&
    +			       HTConfirm(gettext("Show the 407 message body?"))) {
    +			if (!dump_output_immediately &&
    +			    format_out == WWW_DOWNLOAD) {
    +			    /*
    +			     * Convert a download request to a presentation
    +			     * request for interactive users.  - FM
    +			     */
    +			    format_out = WWW_PRESENT;
    +			}
    +			break;
    +		    } else {
    +			if (traversal || dump_output_immediately)
    +			    HTAlert(FAILED_RETRY_WITH_PROXY);
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			goto clean_up;
    +		    }
    +
    +		case 408:
    +		    /*
    +		     * Request Timeout.  Show the status message and restore
    +		     * the current document.  - FM
    +		     */
    +		    HTAlert(line_buffer);
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = HT_NO_DATA;
    +		    goto clean_up;
    +
    +		default:
    +		    /*
    +		     * 400 Bad Request.
    +		     * 402 Payment Required.
    +		     * 403 Forbidden.
    +		     * 404 Not Found.
    +		     * 405 Method Not Allowed.
    +		     * 406 Not Acceptable.
    +		     * 409 Conflict.
    +		     * 410 Gone.
    +		     * 411 Length Required.
    +		     * 412 Precondition Failed.
    +		     * 413 Request Entity Too Large.
    +		     * 414 Request-URI Too Long.
    +		     * 415 Unsupported Media Type.
    +		     * 416 List Response (for content negotiation).
    +		     * > 416 is unknown.
    +		     * Show the status message, and display the returned text
    +		     * if we are not doing a traversal.  - FM
    +		     */
    +		    HTAlert(line_buffer);
    +		    if (traversal) {
    +			HTTP_NETCLOSE(s, handle);
    +			status = -1;
    +			goto clean_up;
    +		    }
    +		    if (!dump_output_immediately &&
    +			format_out == WWW_DOWNLOAD) {
    +			/*
    +			 * Convert a download request to a presentation request
    +			 * for interactive users.  - FM
    +			 */
    +			format_out = WWW_PRESENT;
    +		    }
    +		    break;
    +		}		/* case 4 switch */
    +		break;
    +
    +	    case 5:
    +		/*
    +		 * "I think YOU goofed!" (server error)
    +		 * 500 Internal Server Error
    +		 * 501 Not Implemented
    +		 * 502 Bad Gateway
    +		 * 503 Service Unavailable
    +		 * 504 Gateway Timeout
    +		 * 505 HTTP Version Not Supported
    +		 * > 505 is unknown.
    +		 * Should always include a message, which we always should
    +		 * display.  - FM
    +		 */
    +		HTAlert(line_buffer);
    +		if (traversal) {
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = -1;
    +		    goto clean_up;
    +		}
    +		if (!dump_output_immediately &&
    +		    format_out == WWW_DOWNLOAD) {
    +		    /*
    +		     * Convert a download request to a presentation request for
    +		     * interactive users.  - FM
    +		     */
    +		    format_out = WWW_PRESENT;
    +		}
    +		break;
    +
    +	    default:
    +		/*
    +		 * Bad or unknown server_status number.  Take a chance and hope
    +		 * there is something to display.  - FM
    +		 */
    +		HTAlert(gettext("Unknown status reply from server!"));
    +		HTAlert(line_buffer);
    +		if (traversal) {
    +		    HTTP_NETCLOSE(s, handle);
    +		    status = -1;
    +		    goto clean_up;
    +		}
    +		if (!dump_output_immediately &&
    +		    format_out == WWW_DOWNLOAD) {
    +		    /*
    +		     * Convert a download request to a presentation request for
    +		     * interactive users.  - FM
    +		     */
    +		    format_out = WWW_PRESENT;
    +		}
    +		break;
    +	    }			/* Switch on server_status/100 */
    +
    +	}			/* Full HTTP reply */
    +    }				/* scope of fields */
    +
    +    /*
    +     * The user may have pressed the 'z'ap key during the pause caused by one
    +     * of the HTAlerts above if the server reported an error, to skip loading
    +     * of the error response page.  Checking here before setting up the stream
    +     * stack and feeding it data avoids doing unnecessary work, it also can
    +     * avoid unnecessarily pushing a loaded document out of the cache to make
    +     * room for the unwanted error page.  - kw
    +     */
    +    if (HTCheckForInterrupt()) {
    +	HTTP_NETCLOSE(s, handle);
    +	if (doing_redirect) {
    +	    /*
    +	     * Impatient user.  - FM
    +	     */
    +	    CTRACE((tfp, "HTTP: Interrupted followup read.\n"));
    +	    _HTProgress(CONNECTION_INTERRUPTED);
    +	}
    +	status = HT_INTERRUPTED;
    +	goto clean_up;
    +    }
    +    /*
    +     * Set up the stream stack to handle the body of the message.
    +     */
    +    if (do_head || keep_mime_headers) {
    +	/*
    +	 * It was a HEAD request, or we want the headers and source.
    +	 */
    +	start_of_data = line_kept_clean;
    +#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
    +/* GIF file contains \0, so strlen does not return the data length */
    +	length = real_length_of_line;
    +#else
    +	length = rawlength;
    +#endif
    +	format_in = HTAtom_for(STR_PLAINTEXT);
    +
    +    } else if (doing_redirect) {
    +
    +	format_in = HTAtom_for("message/x-http-redirection");
    +	StrAllocCopy(anAnchor->content_type, HTAtom_name(format_in));
    +	if (traversal) {
    +	    format_out = WWW_DEBUG;
    +	    if (!sink)
    +		sink = HTErrorStream();
    +	} else if (!dump_output_immediately &&
    +		   format_out == WWW_DOWNLOAD) {
    +	    /*
    +	     * Convert a download request to a presentation request for
    +	     * interactive users.  - FM
    +	     */
    +	    format_out = WWW_PRESENT;
    +	}
    +    }
    +
    +    target = HTStreamStack(format_in,
    +			   format_out,
    +			   sink, anAnchor);
    +
    +    if (target == NULL) {
    +	char *buffer = NULL;
    +
    +	HTTP_NETCLOSE(s, handle);
    +	HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
    +		   HTAtom_name(format_in), HTAtom_name(format_out));
    +	_HTProgress(buffer);
    +	FREE(buffer);
    +	status = -1;
    +	goto clean_up;
    +    }
    +
    +    /*
    +     * Recycle the first chunk of data, in all cases.
    +     */
    +    (*target->isa->put_block) (target, start_of_data, length);
    +
    +    /*
    +     * Go pull the bulk of the data down.
    +     */
    +    rv = HTCopy(anAnchor, s, (void *) handle, target);
    +
    +    /*
    +     * If we get here with doing_redirect set, it means that we were looking
    +     * for a Location header.  We either have got it now in redirecting_url -
    +     * in that case the stream should not have loaded any data.  Or we didn't
    +     * get it, in that case the stream may have presented the message body
    +     * normally.  - kw
    +     */
    +
    +    if (rv == -1) {
    +	/*
    +	 * Intentional interrupt before data were received, not an error
    +	 */
    +	if (doing_redirect && traversal)
    +	    status = -1;
    +	else
    +	    status = HT_INTERRUPTED;
    +	HTTP_NETCLOSE(s, handle);
    +	goto clean_up;
    +    }
    +
    +    if (rv == -2) {
    +	/*
    +	 * Aw hell, a REAL error, maybe cuz it's a dumb HTTP0 server
    +	 */
    +	(*target->isa->_abort) (target, NULL);
    +	if (doing_redirect && redirecting_url) {
    +	    /*
    +	     * Got a location before the error occurred?  Then consider it an
    +	     * interrupt but proceed below as normal.  - kw
    +	     */
    +	    /* do nothing here */
    +	} else {
    +	    HTTP_NETCLOSE(s, handle);
    +	    if (!doing_redirect && !already_retrying && !do_post) {
    +		CTRACE((tfp, "HTTP: Trying again with HTTP0 request.\n"));
    +		/*
    +		 * May as well consider it an interrupt -- right?
    +		 */
    +		FREE(line_buffer);
    +		FREE(line_kept_clean);
    +		extensions = NO;
    +		already_retrying = TRUE;
    +		_HTProgress(RETRYING_AS_HTTP0);
    +		goto try_again;
    +	    } else {
    +		status = HT_NOT_LOADED;
    +		goto clean_up;
    +	    }
    +	}
    +    }
    +
    +    /*
    +     * Free if complete transmission (socket was closed before return).  Close
    +     * socket if partial transmission (was freed on abort).
    +     */
    +    if (rv != HT_INTERRUPTED && rv != -2) {
    +	(*target->isa->_free) (target);
    +    } else {
    +	HTTP_NETCLOSE(s, handle);
    +    }
    +
    +    if (doing_redirect) {
    +	if (redirecting_url) {
    +	    /*
    +	     * Set up for checking redirecting_url in LYGetFile.c for
    +	     * restrictions before we seek the document at that Location.  - FM
    +	     */
    +	    CTRACE((tfp, "HTTP: Picked up location '%s'\n",
    +		    redirecting_url));
    +	    if (rv == HT_INTERRUPTED) {
    +		/*
    +		 * Intentional interrupt after data were received, not an error
    +		 * (probably).  We take it as a user request to abandon the
    +		 * redirection chain.
    +		 *
    +		 * This could reasonably be changed (by just removing this
    +		 * block), it would make sense if there are redirecting
    +		 * resources that "hang" after sending the headers.  - kw
    +		 */
    +		FREE(redirecting_url);
    +		CTRACE((tfp, "HTTP: Interrupted followup read.\n"));
    +		status = HT_INTERRUPTED;
    +		goto clean_up;
    +	    }
    +	    HTProgress(line_buffer);
    +	    if (server_status == 305) {		/* Use Proxy */
    +		/*
    +		 * Make sure the proxy field ends with a slash.  - FM
    +		 */
    +		if (redirecting_url[strlen(redirecting_url) - 1]
    +		    != '/')
    +		    StrAllocCat(redirecting_url, "/");
    +		/*
    +		 * Append our URL.  - FM
    +		 */
    +		StrAllocCat(redirecting_url, anAnchor->address);
    +		CTRACE((tfp, "HTTP: Proxy URL is '%s'\n",
    +			redirecting_url));
    +	    }
    +	    if (!do_post ||
    +		server_status == 303 ||
    +		server_status == 302) {
    +		/*
    +		 * We don't have POST content (nor support PUT or DELETE), or
    +		 * the status is "See Other" or "General Redirection" and we
    +		 * can convert to GET, so go back and check out the new URL.  -
    +		 * FM
    +		 */
    +		status = HT_REDIRECTING;
    +		goto clean_up;
    +	    }
    +	    /*
    +	     * Make sure the user wants to redirect the POST content, or treat
    +	     * as GET - FM & DK
    +	     */
    +	    switch (HTConfirmPostRedirect(redirecting_url,
    +					  server_status)) {
    +		/*
    +		 * User failed to confirm.  Abort the fetch.
    +		 */
    +	    case 0:
    +		FREE(redirecting_url);
    +		status = HT_NO_DATA;
    +		goto clean_up;
    +
    +		/*
    +		 * User wants to treat as GET with no content.  Go back to
    +		 * check out the URL.
    +		 */
    +	    case 303:
    +		break;
    +
    +		/*
    +		 * Set the flag to retain the POST content and go back to check
    +		 * out the URL.  - FM
    +		 */
    +	    default:
    +		redirect_post_content = TRUE;
    +	    }
    +
    +	    /* Lou's old comment:  - FM */
    +	    /* OK, now we've got the redirection URL temporarily stored
    +	       in external variable redirecting_url, exported from HTMIME.c,
    +	       since there's no straightforward way to do this in the library
    +	       currently.  Do the right thing. */
    +
    +	    status = HT_REDIRECTING;
    +
    +	} else {
    +	    status = traversal ? -1 : HT_LOADED;
    +	}
    +
    +    } else {
    +	/*
    +	 * If any data were received, treat as a complete transmission
    +	 */
    +	status = HT_LOADED;
    +    }
    +
    +    /*
    +     * Clean up
    +     */
    +  clean_up:
    +    FREE(line_buffer);
    +    FREE(line_kept_clean);
    +
    +  done:
    +    /*
    +     * Clear out on exit, just in case.
    +     */
    +    reloading = FALSE;
    +#ifdef USE_SSL
    +    FREE(connect_host);
    +    if (handle) {
    +	SSL_free(handle);
    +	SSL_handle = handle = NULL;
    +    }
    +#endif /* USE_SSL */
    +    dump_server_status = server_status;
    +    return status;
    +}
    +
    +/*	Protocol descriptor
    +*/
    +#ifdef GLOBALDEF_IS_MACRO
    +#define _HTTP_C_GLOBALDEF_1_INIT { "http", HTLoadHTTP, 0}
    +GLOBALDEF(HTProtocol, HTTP, _HTTP_C_GLOBALDEF_1_INIT);
    +#define _HTTP_C_GLOBALDEF_2_INIT { "https", HTLoadHTTP, 0}
    +GLOBALDEF(HTProtocol, HTTPS, _HTTP_C_GLOBALDEF_2_INIT);
    +#else
    +GLOBALDEF HTProtocol HTTP =
    +{"http", HTLoadHTTP, 0};
    +GLOBALDEF HTProtocol HTTPS =
    +{"https", HTLoadHTTP, 0};
    +#endif /* GLOBALDEF_IS_MACRO */
    diff --git a/WWW/Library/Implementation/HTTP.h b/WWW/Library/Implementation/HTTP.h
    new file mode 100644
    index 0000000..ea0f835
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTP.h
    @@ -0,0 +1,47 @@
    +/*
    + * $LynxId: HTTP.h,v 1.12 2020/01/21 21:59:06 tom Exp $
    + *
    + * /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTTP.html
    + *                                HYPERTEXT TRANSFER PROTOCOL
    + */
    +#ifndef HTTP_H
    +#define HTTP_H
    +
    +#include <HTAccess.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#ifdef GLOBALREF_IS_MACRO
    +    extern GLOBALREF (HTProtocol, HTTP);
    +    extern GLOBALREF (HTProtocol, HTTPS);
    +
    +#else
    +    GLOBALREF HTProtocol HTTP;
    +    GLOBALREF HTProtocol HTTPS;
    +#endif				/* GLOBALREF_IS_MACRO */
    +
    +#define URL_GET_METHOD  1
    +#define URL_POST_METHOD 2
    +#define URL_MAIL_METHOD 3
    +
    +    /*
    +     * Special value for 'reloading' used to tell HTLoadDocument() that the
    +     * user asked for a reload, versus Lynx doing a reload for other reasons.
    +     */
    +#define REAL_RELOAD (TRUE + 1)
    +
    +    extern int ws_read_per_sec;
    +    extern BOOLEAN reloading;
    +    extern char *redirecting_url;
    +    extern BOOL permanent_redirection;
    +    extern BOOL redirect_post_content;
    +
    +#ifdef USE_SSL
    +    extern SSL *SSL_handle;
    +#endif
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTTP_H */
    diff --git a/WWW/Library/Implementation/HTTelnet.c b/WWW/Library/Implementation/HTTelnet.c
    new file mode 100644
    index 0000000..fb0098a
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTelnet.c
    @@ -0,0 +1,553 @@
    +/*
    + * $LynxId: HTTelnet.c,v 1.41 2013/11/28 11:15:19 tom Exp $
    + *
    + *		Telnet Access, Rlogin, etc			HTTelnet.c
    + *		==========================
    + *
    + * Authors
    + *	TBL	Tim Berners-Lee timbl@info.cern.ch
    + *	JFG	Jean-Francois Groff jgh@next.com
    + *	DD	Denis DeLaRoca (310) 825-4580  <CSP1DWD@mvs.oac.ucla.edu>
    + * History
    + *	 8 Jun 92 Telnet hopping prohibited as telnet is not secure (TBL)
    + *	26 Jun 92 When over DECnet, suppressed FTP, Gopher and News. (JFG)
    + *	 6 Oct 92 Moved HTClientHost and logfile into here. (TBL)
    + *	17 Dec 92 Tn3270 added, bug fix. (DD)
    + *	 2 Feb 93 Split from HTAccess.c.  Registration.(TBL)
    + */
    +
    +#include <HTUtils.h>
    +#include <LYUtils.h>
    +
    +/* Implements:
    +*/
    +#include <HTTelnet.h>
    +
    +#include <HTParse.h>
    +#include <HTAnchor.h>
    +#include <HTTP.h>
    +#include <HTFile.h>
    +
    +#include <HTTCP.h>
    +#include <HText.h>
    +
    +#include <HTAccess.h>
    +#include <HTAlert.h>
    +
    +#include <LYStrings.h>
    +#include <LYClean.h>
    +#include <LYLeaks.h>
    +
    +#ifdef __GNUC__
    +static void do_system(char *) GCC_UNUSED;
    +#endif
    +
    +static void do_system(char *command)
    +{
    +    if (non_empty(command)) {
    +	CTRACE((tfp, "HTTelnet: Command is: %s\n\n", command));
    +	LYSystem(command);
    +    }
    +    FREE(command);
    +}
    +
    +/*	Telnet or "rlogin" access
    + *	-------------------------
    + */
    +static int remote_session(char *acc_method, char *host)
    +{
    +    const char *program;
    +    char *user = host;
    +    char *password = NULL;
    +    char *cp;
    +    char *hostname;
    +    char *port;
    +    char *command = NULL;
    +    enum _login_protocol {
    +	telnet,
    +	rlogin,
    +	tn3270
    +    } login_protocol =
    +      strcmp(acc_method, "rlogin") == 0 ? rlogin :
    +      strcmp(acc_method, "tn3270") == 0 ? tn3270 : telnet;
    +
    +    /*
    +     * Modified to allow for odd chars in a username only if exists.
    +     * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
    +     */
    +    /* prevent telnet://hostname;rm -rf *  URL's (VERY BAD)
    +     *  *cp=0;        // terminate at any ;,<,>,`,|,",' or space or return
    +     * or tab to prevent security hole
    +     */
    +    for (cp = (StrChr(host, '@') ? StrChr(host, '@') : host); *cp != '\0';
    +	 cp++) {
    +	if (!isalnum(UCH(*cp)) && *cp != '_' && *cp != '-' &&
    +	    *cp != ':' && *cp != '.' && *cp != '@') {
    +	    *cp = '\0';
    +	    break;
    +	}
    +    }
    +
    +    hostname = StrChr(host, '@');
    +
    +    if (hostname) {
    +	*hostname++ = '\0';	/* Split */
    +    } else {
    +	hostname = host;
    +	user = NULL;		/* No user specified */
    +    }
    +
    +    port = StrChr(hostname, ':');
    +    if (port)
    +	*port++ = '\0';		/* Split */
    +
    +    if (*hostname == '\0') {
    +	CTRACE((tfp, "HTTelnet: No host specified!\n"));
    +	return HT_NO_DATA;
    +    } else if (!valid_hostname(hostname)) {
    +	char *prefix = NULL;
    +	char *line = NULL;
    +
    +	CTRACE((tfp, "HTTelnet: Invalid hostname %s!\n", host));
    +	HTSprintf0(&prefix,
    +		   gettext("remote %s session:"), acc_method);
    +	HTSprintf0(&line,
    +		   gettext("Invalid hostname %s"), host);
    +	HTAlwaysAlert(prefix, line);
    +	FREE(prefix);
    +	FREE(line);
    +	return HT_NO_DATA;
    +    }
    +
    +    if (user) {
    +	password = StrChr(user, ':');
    +	if (password) {
    +	    *password++ = '\0';
    +	}
    +    }
    +
    +    /* If the person is already telnetting etc, forbid hopping */
    +    /* This is a security precaution, for us and remote site */
    +
    +    if (HTSecure) {
    +
    +#ifdef TELNETHOPPER_MAIL
    +	HTSprintf0(&command,
    +		   "finger @%s | mail -s \"**telnethopper %s\" tbl@dxcern.cern.ch",
    +		   HTClientHost, HTClientHost);
    +	do_system(command);
    +#endif
    +	printf("\n\nSorry, but the service you have selected is one\n");
    +	printf("to which you have to log in.  If you were running www\n");
    +	printf("on your own computer, you would be automatically connected.\n");
    +	printf("For security reasons, this is not allowed when\n");
    +	printf("you log in to this information service remotely.\n\n");
    +
    +	printf("You can manually connect to this service using %s\n",
    +	       acc_method);
    +	printf("to host %s", hostname);
    +	if (user)
    +	    printf(", user name %s", user);
    +	if (password)
    +	    printf(", password %s", password);
    +	if (port)
    +	    printf(", port %s", port);
    +	printf(".\n\n");
    +	return HT_NO_DATA;
    +    }
    +
    +    /* Not all telnet servers get it even if user name is specified so we
    +     * always tell the guy what to log in as.
    +     */
    +    if (user && login_protocol != rlogin)
    +	printf("When you are connected, log in as:  %s\n", user);
    +    if (password && login_protocol != rlogin)
    +	printf("                  The password is:  %s\n", password);
    +    fflush(stdout);
    +
    +/*
    + *	NeXTSTEP is the implied version of the NeXT operating system.
    + *		You may need to define this yourself.
    + */
    +#if	!defined(TELNET_DONE) && (defined(NeXT) && defined(NeXTSTEP) && NeXTSTEP<=20100)
    +#define FMT_TELNET "%s%s%s %s %s"
    +
    +    if ((program = HTGetProgramPath(ppTELNET)) != NULL) {
    +	HTAddParam(&command, FMT_TELNET, 1, program);
    +	HTOptParam(&command, FMT_TELNET, 2, user ? " -l " : "");
    +	HTAddParam(&command, FMT_TELNET, 3, user);
    +	HTAddParam(&command, FMT_TELNET, 4, hostname);
    +	HTAddParam(&command, FMT_TELNET, 5, port);
    +	HTEndParam(&command, FMT_TELNET, 5);
    +    }
    +    do_system(command);
    +#define TELNET_DONE
    +#endif
    +
    +/* Most unix machines support username only with rlogin */
    +#if !defined(TELNET_DONE) && (defined(UNIX) || defined(DOSPATH) || defined(__CYGWIN__))
    +
    +#define FMT_RLOGIN "%s %s%s%s"
    +#define FMT_TN3270 "%s %s %s"
    +#define FMT_TELNET "%s %s %s"
    +
    +    switch (login_protocol) {
    +    case rlogin:
    +	if ((program = HTGetProgramPath(ppRLOGIN)) != NULL) {
    +	    HTAddParam(&command, FMT_RLOGIN, 1, program);
    +	    HTAddParam(&command, FMT_RLOGIN, 2, hostname);
    +	    HTOptParam(&command, FMT_RLOGIN, 3, user ? " -l " : "");
    +	    HTAddParam(&command, FMT_RLOGIN, 4, user);
    +	    HTEndParam(&command, FMT_RLOGIN, 4);
    +	}
    +	break;
    +
    +    case tn3270:
    +	if ((program = HTGetProgramPath(ppTN3270)) != NULL) {
    +	    HTAddParam(&command, FMT_TN3270, 1, program);
    +	    HTAddParam(&command, FMT_TN3270, 2, hostname);
    +	    HTAddParam(&command, FMT_TN3270, 3, port);
    +	    HTEndParam(&command, FMT_TN3270, 3);
    +	}
    +	break;
    +
    +    case telnet:
    +	if ((program = HTGetProgramPath(ppTELNET)) != NULL) {
    +	    HTAddParam(&command, FMT_TELNET, 1, program);
    +	    HTAddParam(&command, FMT_TELNET, 2, hostname);
    +	    HTAddParam(&command, FMT_TELNET, 3, port);
    +	    HTEndParam(&command, FMT_TELNET, 3);
    +	}
    +	break;
    +    }
    +
    +    LYSystem(command);
    +#define TELNET_DONE
    +#endif /* unix */
    +
    +/* VMS varieties */
    +#if !defined(TELNET_DONE) && (defined(MULTINET))
    +    if (login_protocol == rlogin) {
    +	HTSprintf0(&command, "RLOGIN%s%s%s%s%s %s",	/*lm 930713 */
    +		   user ? "/USERNAME=\"" : "",
    +		   NonNull(user),
    +		   user ? "\"" : "",
    +		   port ? "/PORT=" : "",
    +		   NonNull(port),
    +		   hostname);
    +
    +    } else if (login_protocol == tn3270) {
    +	HTSprintf0(&command, "TELNET/TN3270 %s%s %s",
    +		   port ? "/PORT=" : "",
    +		   NonNull(port),
    +		   hostname);
    +
    +    } else {			/* TELNET */
    +	HTSprintf0(&command, "TELNET %s%s %s",
    +		   port ? "/PORT=" : "",
    +		   NonNull(port),
    +		   hostname);
    +    }
    +
    +    do_system(command);
    +#define TELNET_DONE
    +#endif /* MULTINET */
    +
    +#if !defined(TELNET_DONE) && defined(WIN_TCP)
    +    if ((cp = getenv("WINTCP_COMMAND_STYLE")) != NULL &&
    +	0 == strncasecomp(cp, "VMS", 3)) {	/* VMS command syntax */
    +	if (login_protocol == rlogin) {
    +	    HTSprintf0(&command, "RLOGIN%s%s%s%s%s %s",		/*lm 930713 */
    +		       user ? "/USERNAME=\"" : "",
    +		       NonNull(user),
    +		       user ? "\"" : "",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +
    +	} else if (login_protocol == tn3270) {
    +	    HTSprintf0(&command, "TELNET/TN3270 %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +
    +	} else {		/* TELNET */
    +	    HTSprintf0(&command, "TELNET %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +	}
    +
    +    } else {			/* UNIX command syntax */
    +	if (login_protocol == rlogin) {
    +	    HTSprintf0(&command, "RLOGIN %s%s%s%s%s",
    +		       hostname,
    +		       user ? " -l " : "",
    +		       user ? "\"" : "",
    +		       NonNull(user),
    +		       user ? "\"" : "");
    +
    +	} else if (login_protocol == tn3270) {
    +	    HTSprintf0(&command, "TN3270 %s %s",
    +		       hostname,
    +		       NonNull(port));
    +
    +	} else {		/* TELNET */
    +	    HTSprintf0(&command, "TELNET %s %s",
    +		       hostname,
    +		       NonNull(port));
    +	}
    +    }
    +
    +    do_system(command);
    +#define TELNET_DONE
    +#endif /* WIN_TCP */
    +
    +#if !defined(TELNET_DONE) && defined(UCX)
    +    if (login_protocol == rlogin) {
    +	HTSprintf0(&command, "RLOGIN%s%s%s %s %s",
    +		   user ? "/USERNAME=\"" : "",
    +		   NonNull(user),
    +		   user ? "\"" : "",
    +		   hostname,
    +		   NonNull(port));
    +
    +    } else if (login_protocol == tn3270) {
    +	HTSprintf0(&command, "TN3270 %s %s",
    +		   hostname,
    +		   NonNull(port));
    +
    +    } else {			/* TELNET */
    +	HTSprintf0(&command, "TELNET %s %s",
    +		   hostname,
    +		   NonNull(port));
    +    }
    +
    +    do_system(command);
    +#define TELNET_DONE
    +#endif /* UCX */
    +
    +#if !defined(TELNET_DONE) && defined(CMU_TCP)
    +    if (login_protocol == telnet) {
    +	HTSprintf0(&command, "TELNET %s%s %s",
    +		   port ? "/PORT=" : "",
    +		   NonNull(port),
    +		   hostname);
    +	do_system(command);
    +    } else {
    +	printf("\nSorry, this browser was compiled without the %s access option.\n",
    +	       acc_method);
    +	printf("\nPress <return> to return to Lynx.");
    +	LYgetch();
    +	HadVMSInterrupt = FALSE;
    +    }
    +#define TELNET_DONE
    +#endif /* CMU_TCP */
    +
    +#if !defined(TELNET_DONE) && defined(SOCKETSHR_TCP)
    +    if (getenv("MULTINET_SOCKET_LIBRARY") != NULL) {
    +	if (login_protocol == rlogin) {
    +	    HTSprintf0(&command, "MULTINET RLOGIN%s%s%s%s %s",	/*lm 930713 */
    +		       user ? "/USERNAME=" : "",
    +		       NonNull(user),
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +
    +	} else if (login_protocol == tn3270) {
    +	    HTSprintf0(&command, "MULTINET TELNET/TN3270 %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +
    +	} else {		/* TELNET */
    +	    HTSprintf0(&command, "MULTINET TELNET %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +	}
    +
    +	do_system(command);
    +	return HT_NO_DATA;	/* Ok - it was done but no data */
    +    } else if ((cp = getenv("WINTCP_COMMAND_STYLE")) != NULL) {
    +	if (0 == strncasecomp(cp, "VMS", 3)) {	/* VMS command syntax */
    +	    if (login_protocol == rlogin) {
    +		HTSprintf0(&command, "RLOGIN%s%s%s%s %s",	/*lm 930713 */
    +			   user ? "/USERNAME=" : "",
    +			   NonNull(user),
    +			   port ? "/PORT=" : "",
    +			   NonNull(port),
    +			   hostname);
    +	    } else if (login_protocol == tn3270) {
    +		HTSprintf0(&command, "TELNET/TN3270 %s%s %s",
    +			   port ? "/PORT=" : "",
    +			   NonNull(port),
    +			   hostname);
    +	    } else {		/* TELNET */
    +		HTSprintf0(&command, "TELNET %s%s %s",
    +			   port ? "/PORT=" : "",
    +			   NonNull(port),
    +			   hostname);
    +	    }
    +	} else {		/* UNIX command syntax */
    +	    if (login_protocol == rlogin) {
    +		HTSprintf0(&command, "RLOGIN %s%s%s",
    +			   hostname,
    +			   user ? " -l " : "",
    +			   NonNull(user));
    +	    } else if (login_protocol == tn3270) {
    +		HTSprintf0(&command, "TN3270 %s %s",
    +			   hostname,
    +			   NonNull(port));
    +	    } else {		/* TELNET */
    +		HTSprintf0(&command, "TELNET %s %s",
    +			   hostname,
    +			   NonNull(port));
    +	    }
    +	}
    +
    +	do_system(command);
    +	return HT_NO_DATA;	/* Ok - it was done but no data */
    +    } else if (getenv("UCX$DEVICE") != NULL
    +	       || getenv("TCPIP$DEVICE") != NULL) {
    +	if (login_protocol == rlogin) {
    +	    HTSprintf0(&command, "RLOGIN%s%s %s %s",
    +		       user ? "/USERNAME=" : "",
    +		       NonNull(user),
    +		       hostname,
    +		       NonNull(port));
    +
    +	} else if (login_protocol == tn3270) {
    +	    HTSprintf0(&command, "TN3270 %s %s",
    +		       hostname,
    +		       NonNull(port));
    +
    +	} else {		/* TELNET */
    +	    HTSprintf0(&command, "TELNET %s %s",
    +		       hostname,
    +		       NonNull(port));
    +	}
    +
    +	do_system(command);
    +	return HT_NO_DATA;	/* Ok - it was done but no data */
    +    } else if (getenv("CMUTEK_ROOT") != NULL) {
    +	if (login_protocol == telnet) {
    +	    HTSprintf0(&command, "TELNET %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +	    do_system(command);
    +	} else {
    +	    printf("\nSorry, this browser was compiled without the %s access option.\n",
    +		   acc_method);
    +	    printf("\nPress <return> to return to Lynx.");
    +	    LYgetch();
    +	    HadVMSInterrupt = FALSE;
    +	}
    +    } else {
    +	if (login_protocol == telnet) {
    +	    HTSprintf0(&command, "TELNET %s%s %s",
    +		       port ? "/PORT=" : "",
    +		       NonNull(port),
    +		       hostname);
    +	    do_system(command);
    +	} else {
    +	    printf("\nSorry, this browser was compiled without the %s access option.\n",
    +		   acc_method);
    +	    printf("\nPress <return> to return to Lynx.");
    +	    LYgetch();
    +	    HadVMSInterrupt = FALSE;
    +	}
    +    }
    +#define TELNET_DONE
    +#endif /* SOCKETSHR_TCP */
    +
    +#if !defined(TELNET_DONE) && (defined(SIMPLE_TELNET) || defined(VM))
    +    if (login_protocol == telnet) {	/* telnet only */
    +	HTSprintf0(&command, "TELNET  %s",	/* @@ Bug: port ignored */
    +		   hostname);
    +	do_system(command);
    +	return HT_NO_DATA;	/* Ok - it was done but no data */
    +    }
    +#define TELNET_DONE
    +#endif
    +
    +#ifndef TELNET_DONE
    +    printf("\nSorry, this browser was compiled without the %s access option.\n",
    +	   acc_method);
    +    printf("\nTo access the information you must %s to %s", acc_method, hostname);
    +    if (port)
    +	printf(" (port %s)", port);
    +    if (user)
    +	printf("\nlogging in with username %s", user);
    +    printf(".\n");
    +    {
    +	printf("\nPress <return> to return to Lynx.");
    +	fflush(stdout);
    +	LYgetch();
    +#ifdef VMS
    +	HadVMSInterrupt = FALSE;
    +#endif /* VMS */
    +    }
    +#endif /* !TELNET_DONE */
    +    return HT_NO_DATA;
    +}
    +
    +/*	"Load a document" -- establishes a session
    + *	------------------------------------------
    + *
    + * On entry,
    + *	addr		must point to the fully qualified hypertext reference.
    + *
    + * On exit,
    + *	returns		<0	Error has occurred.
    + *			>=0	Value of file descriptor or socket to be used
    + *				 to read data.
    + *	*pFormat	Set to the format of the file, if known.
    + *			(See WWW.h)
    + *
    + */
    +static int HTLoadTelnet(const char *addr,
    +			HTParentAnchor *anchor GCC_UNUSED,
    +			HTFormat format_out GCC_UNUSED,
    +			HTStream *sink)		/* Ignored */
    +{
    +    char *acc_method;
    +    char *host;
    +    int status;
    +
    +    if (sink) {
    +	CTRACE((tfp,
    +		"HTTelnet: Can't output a live session -- must be interactive!\n"));
    +	return HT_NO_DATA;
    +    }
    +    acc_method = HTParse(addr, STR_FILE_URL, PARSE_ACCESS);
    +
    +    host = HTParse(addr, "", PARSE_HOST);
    +    if (!host || *host == '\0') {
    +	status = HT_NO_DATA;
    +	CTRACE((tfp, "HTTelnet: No host specified!\n"));
    +    } else {
    +	status = remote_session(acc_method, host);
    +    }
    +
    +    FREE(host);
    +    FREE(acc_method);
    +    return status;
    +}
    +
    +#ifdef GLOBALDEF_IS_MACRO
    +#define _HTTELNET_C_1_INIT { "telnet", HTLoadTelnet, NULL }
    +#define _HTTELNET_C_2_INIT { "rlogin", HTLoadTelnet, NULL }
    +#define _HTTELNET_C_3_INIT { "tn3270", HTLoadTelnet, NULL }
    +GLOBALDEF(HTProtocol, HTTelnet, _HTTELNET_C_1_INIT);
    +GLOBALDEF(HTProtocol, HTRlogin, _HTTELNET_C_2_INIT);
    +GLOBALDEF(HTProtocol, HTTn3270, _HTTELNET_C_3_INIT);
    +#else
    +GLOBALDEF HTProtocol HTTelnet =
    +{"telnet", HTLoadTelnet, NULL};
    +GLOBALDEF HTProtocol HTRlogin =
    +{"rlogin", HTLoadTelnet, NULL};
    +GLOBALDEF HTProtocol HTTn3270 =
    +{"tn3270", HTLoadTelnet, NULL};
    +#endif /* GLOBALDEF_IS_MACRO */
    diff --git a/WWW/Library/Implementation/HTTelnet.h b/WWW/Library/Implementation/HTTelnet.h
    new file mode 100644
    index 0000000..f2208d8
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTTelnet.h
    @@ -0,0 +1,28 @@
    +/*                 /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTTelnet.html
    +                            TELNET AND SIMILAR ACCESS METHODS
    +                                             
    + */
    +
    +#ifndef HTTELNET_H
    +#define HTTELNET_H
    +
    +#include <HTAccess.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#ifdef GLOBALREF_IS_MACRO
    +    extern GLOBALREF (HTProtocol, HTTelnet);
    +    extern GLOBALREF (HTProtocol, HTRlogin);
    +    extern GLOBALREF (HTProtocol, HTTn3270);
    +
    +#else
    +    GLOBALREF HTProtocol HTTelnet;
    +    GLOBALREF HTProtocol HTRlogin;
    +    GLOBALREF HTProtocol HTTn3270;
    +#endif				/* GLOBALREF_IS_MACRO */
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTTELNET_H */
    diff --git a/WWW/Library/Implementation/HTUU.c b/WWW/Library/Implementation/HTUU.c
    new file mode 100644
    index 0000000..4848cce
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTUU.c
    @@ -0,0 +1,210 @@
    +/*
    + * $LynxId: HTUU.c,v 1.11 2010/09/21 23:55:12 tom Exp $
    + *
    + * MODULE							HTUU.c
    + *			UUENCODE AND UUDECODE
    + *
    + * ACKNOWLEDGEMENT:
    + *	This code is taken from rpem distribution, and was originally
    + *	written by Mark Riordan.
    + *
    + * AUTHORS:
    + *	MR	Mark Riordan	riordanmr@clvax1.cl.msu.edu
    + *	AL	Ari Luotonen	luotonen@dxcern.cern.ch
    + *
    + * HISTORY:
    + *	Added as part of the WWW library and edited to conform
    + *	with the WWW project coding standards by:	AL  5 Aug 1993
    + *	Originally written by:				MR 12 Aug 1990
    + *	Original header text:
    + * -------------------------------------------------------------
    + *  File containing routines to convert a buffer
    + *  of bytes to/from RFC 1113 printable encoding format.
    + *
    + *  This technique is similar to the familiar Unix uuencode
    + *  format in that it maps 6 binary bits to one ASCII
    + *  character (or more aptly, 3 binary bytes to 4 ASCII
    + *  characters).  However, RFC 1113 does not use the same
    + *  mapping to printable characters as uuencode.
    + *
    + *  Mark Riordan   12 August 1990 and 17 Feb 1991.
    + *  This code is hereby placed in the public domain.
    + * -------------------------------------------------------------
    + */
    +
    +#include <HTUtils.h>
    +#include <HTUU.h>
    +
    +#include <LYLeaks.h>
    +
    +static char six2pr[64] =
    +{
    +    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    +    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    +    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    +    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    +    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    +};
    +
    +static unsigned char pr2six[256];
    +
    +/*--- function HTUU_encode -----------------------------------------------
    + *
    + * Encode a single line of binary data to a standard format that
    + * uses only printing ASCII characters (but takes up 33% more bytes).
    + *
    + *    Entry    bufin    points to a buffer of bytes.  If nbytes is not
    + *                      a multiple of three, then the byte just beyond
    + *                      the last byte in the buffer must be 0.
    + *             nbytes   is the number of bytes in that buffer.
    + *                      This cannot be more than 48.
    + *             bufcoded points to an output buffer.  Be sure that this
    + *                      can hold at least 1 + (4*nbytes)/3 characters.
    + *
    + *    Exit     bufcoded contains the coded line.  The first 4*nbytes/3 bytes
    + *                      contain printing ASCII characters representing
    + *                      those binary bytes.  This may include one or
    + *                      two '=' characters used as padding at the end.
    + *                      The last byte is a zero byte.
    + *             Returns the number of ASCII characters in "bufcoded".
    + */
    +int HTUU_encode(unsigned char *bufin,
    +		size_t nbytes,
    +		char *bufcoded)
    +{
    +/* ENC is the basic 1 character encoding function to make a char printing */
    +#define ENC(c) six2pr[c]
    +
    +    register char *outptr = bufcoded;
    +    size_t i;
    +
    +    /* This doesn't seem to be needed (AL):   register unsigned char *inptr  = bufin; */
    +
    +    for (i = 0; i < nbytes; i += 3) {
    +	*(outptr++) = ENC(*bufin >> 2);		/* c1 */
    +	*(outptr++) = ENC(((*bufin << 4) & 060) | ((bufin[1] >> 4) & 017));	/*c2 */
    +	*(outptr++) = ENC(((bufin[1] << 2) & 074) | ((bufin[2] >> 6) & 03));	/*c3 */
    +	*(outptr++) = ENC(bufin[2] & 077);	/* c4 */
    +
    +	bufin += 3;
    +    }
    +
    +    /* If nbytes was not a multiple of 3, then we have encoded too
    +     * many characters.  Adjust appropriately.
    +     */
    +    if (i == nbytes + 1) {
    +	/* There were only 2 bytes in that last group */
    +	outptr[-1] = '=';
    +    } else if (i == nbytes + 2) {
    +	/* There was only 1 byte in that last group */
    +	outptr[-1] = '=';
    +	outptr[-2] = '=';
    +    }
    +    *outptr = '\0';
    +    return (int) (outptr - bufcoded);
    +}
    +
    +/*--- function HTUU_decode ------------------------------------------------
    + *
    + * Decode an ASCII-encoded buffer back to its original binary form.
    + *
    + *    Entry    bufcoded    points to a uuencoded string.  It is
    + *                         terminated by any character not in
    + *                         the printable character table six2pr, but
    + *                         leading whitespace is stripped.
    + *             bufplain    points to the output buffer; must be big
    + *                         enough to hold the decoded string (generally
    + *                         shorter than the encoded string) plus
    + *                         as many as two extra bytes used during
    + *                         the decoding process.
    + *             outbufsize  is the maximum number of bytes that
    + *                         can fit in bufplain.
    + *
    + *    Exit     Returns the number of binary bytes decoded.
    + *             bufplain    contains these bytes.
    + */
    +int HTUU_decode(char *bufcoded,
    +		unsigned char *bufplain,
    +		int outbufsize)
    +{
    +/* single character decode */
    +#define DEC(c) pr2six[(int)c]
    +#define MAXVAL 63
    +
    +    static int first = 1;
    +
    +    int nbytesdecoded, j;
    +    register char *bufin;
    +    register unsigned char *bufout = bufplain;
    +    register int nprbytes;
    +
    +    /* If this is the first call, initialize the mapping table.
    +     * This code should work even on non-ASCII machines.
    +     */
    +    if (first) {
    +	first = 0;
    +	for (j = 0; j < 256; j++)
    +	    pr2six[j] = MAXVAL + 1;
    +
    +	for (j = 0; j < 64; j++)
    +	    pr2six[UCH(six2pr[j])] = UCH(j);
    +#if 0
    +      /* *INDENT-OFF* */
    +      pr2six['A']= 0; pr2six['B']= 1; pr2six['C']= 2; pr2six['D']= 3;
    +      pr2six['E']= 4; pr2six['F']= 5; pr2six['G']= 6; pr2six['H']= 7;
    +      pr2six['I']= 8; pr2six['J']= 9; pr2six['K']=10; pr2six['L']=11;
    +      pr2six['M']=12; pr2six['N']=13; pr2six['O']=14; pr2six['P']=15;
    +      pr2six['Q']=16; pr2six['R']=17; pr2six['S']=18; pr2six['T']=19;
    +      pr2six['U']=20; pr2six['V']=21; pr2six['W']=22; pr2six['X']=23;
    +      pr2six['Y']=24; pr2six['Z']=25; pr2six['a']=26; pr2six['b']=27;
    +      pr2six['c']=28; pr2six['d']=29; pr2six['e']=30; pr2six['f']=31;
    +      pr2six['g']=32; pr2six['h']=33; pr2six['i']=34; pr2six['j']=35;
    +      pr2six['k']=36; pr2six['l']=37; pr2six['m']=38; pr2six['n']=39;
    +      pr2six['o']=40; pr2six['p']=41; pr2six['q']=42; pr2six['r']=43;
    +      pr2six['s']=44; pr2six['t']=45; pr2six['u']=46; pr2six['v']=47;
    +      pr2six['w']=48; pr2six['x']=49; pr2six['y']=50; pr2six['z']=51;
    +      pr2six['0']=52; pr2six['1']=53; pr2six['2']=54; pr2six['3']=55;
    +      pr2six['4']=56; pr2six['5']=57; pr2six['6']=58; pr2six['7']=59;
    +      pr2six['8']=60; pr2six['9']=61; pr2six['+']=62; pr2six['/']=63;
    +      /* *INDENT-ON* */
    +
    +#endif
    +    }
    +
    +    /* Strip leading whitespace. */
    +
    +    while (*bufcoded == ' ' || *bufcoded == '\t')
    +	bufcoded++;
    +
    +    /* Figure out how many characters are in the input buffer.
    +     * If this would decode into more bytes than would fit into
    +     * the output buffer, adjust the number of input bytes downwards.
    +     */
    +    bufin = bufcoded;
    +    while (pr2six[UCH(*(bufin++))] <= MAXVAL) ;
    +    nprbytes = (int) (bufin - bufcoded - 1);
    +    nbytesdecoded = ((nprbytes + 3) / 4) * 3;
    +    if (nbytesdecoded > outbufsize) {
    +	nprbytes = (outbufsize * 4) / 3;
    +    }
    +
    +    bufin = bufcoded;
    +
    +    while (nprbytes > 0) {
    +	*(bufout++) = UCH((DEC(bufin[0]) << 2) | (DEC(bufin[1]) >> 4));
    +	*(bufout++) = UCH((DEC(bufin[1]) << 4) | (DEC(bufin[2]) >> 2));
    +	*(bufout++) = UCH((DEC(bufin[2]) << 6) | (DEC(bufin[3])));
    +	bufin += 4;
    +	nprbytes -= 4;
    +    }
    +
    +    if (nprbytes & 03) {
    +	if (pr2six[(int) bufin[-2]] > MAXVAL) {
    +	    nbytesdecoded -= 2;
    +	} else {
    +	    nbytesdecoded -= 1;
    +	}
    +    }
    +
    +    return (nbytesdecoded);
    +}
    diff --git a/WWW/Library/Implementation/HTUU.h b/WWW/Library/Implementation/HTUU.h
    new file mode 100644
    index 0000000..93ba435
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTUU.h
    @@ -0,0 +1,36 @@
    +/*
    + * $LynxId: HTUU.h,v 1.11 2010/09/25 11:43:27 tom Exp $
    + *
    + *                              ENCODING TO PRINTABLE CHARACTERS
    +
    +   File module provides functions HTUU_encode() and HTUU_decode() which convert
    +   a buffer of bytes to/from RFC 1113 printable encoding format.  This
    +   technique is similar to the familiar Unix uuencode format in that it maps 6
    +   binary bits to one ASCII character (or more aptly, 3 binary bytes to 4 ASCII
    +   characters).  However, RFC 1113 does not use the same mapping to printable
    +   characters as uuencode.
    +
    + */
    +
    +#ifndef HTUU_H
    +#define HTUU_H
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern int HTUU_encode(unsigned char *bufin,
    +			   size_t nbytes,
    +			   char *bufcoded);
    +
    +    extern int HTUU_decode(char *bufcoded,
    +			   unsigned char *bufplain,
    +			   int outbufsize);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTUU_H */
    diff --git a/WWW/Library/Implementation/HTUtils.h b/WWW/Library/Implementation/HTUtils.h
    new file mode 100644
    index 0000000..d01d0dd
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTUtils.h
    @@ -0,0 +1,847 @@
    +/*
    + * $LynxId: HTUtils.h,v 1.134 2021/06/29 22:01:12 tom Exp $
    + *
    + * Utility macros for the W3 code library
    + * MACROS FOR GENERAL USE
    + * 
    + * See also:  the system dependent file "www_tcp.h", which is included here.
    + */
    +
    +#ifndef NO_LYNX_TRACE
    +#define DEBUG			/* Turns on trace; turn off for smaller binary */
    +#endif
    +
    +#ifndef HTUTILS_H
    +#define HTUTILS_H
    +
    +#ifdef HAVE_CONFIG_H
    +#include <lynx_cfg.h>		/* generated by autoconf 'configure' script */
    +
    +#ifdef HAVE_STDNORETURN_H
    +#include <stdnoreturn.h>
    +#undef GCC_NORETURN
    +#define GCC_NORETURN _Noreturn
    +#endif
    +
    +/* see AC_FUNC_ALLOCA macro */
    +#ifdef __GNUC__
    +# define alloca(size) __builtin_alloca(size)
    +#else
    +# ifdef _MSC_VER
    +#  include <malloc.h>
    +#  ifndef alloca
    +#   define alloca(size) _alloca(size)
    +#  endif
    +# else
    +#  if HAVE_ALLOCA_H
    +#   include <alloca.h>
    +#  else
    +#   ifdef _AIX
    +#pragma alloca
    +#   else
    +#    ifndef alloca		/* predefined by HP cc +Olibcalls */
    +char *alloca();
    +
    +#    endif
    +#   endif
    +#  endif
    +# endif
    +#endif
    +
    +#include <sys/types.h>
    +#include <stdio.h>
    +
    +#else /* HAVE_CONFIG_H */
    +
    +#ifdef DJGPP
    +#include <sys/config.h>		/* pseudo-autoconf values for DJGPP libc/headers */
    +#define HAVE_TRUNCATE 1
    +#define HAVE_ALLOCA 1
    +#include <limits.h>
    +#endif /* DJGPP */
    +
    +#include <sys/types.h>
    +#include <stdio.h>
    +
    +/* Explicit system-configure */
    +#ifdef VMS
    +#define NO_SIZECHANGE
    +
    +#if defined(VAXC) && !defined(__DECC)
    +#define NO_UNISTD_H		/* DEC C has unistd.h, but not VAX C */
    +#endif
    +
    +#define NO_KEYPAD
    +#define NO_UTMP
    +
    +#undef NO_FILIO_H
    +#define NO_FILIO_H
    +
    +#define NOUSERS
    +#define DISP_PARTIAL		/* experimental */
    +#endif
    +
    +#if defined(VMS) || defined(_WINDOWS)
    +#define HAVE_STDLIB_H 1
    +#endif
    +
    +/* Accommodate non-autoconf'd Makefile's (VMS, DJGPP, etc) */
    +
    +#ifndef NO_ARPA_INET_H
    +#define HAVE_ARPA_INET_H 1
    +#endif
    +
    +#ifndef NO_CBREAK
    +#define HAVE_CBREAK 1
    +#endif
    +
    +#ifndef NO_CUSERID
    +#define HAVE_CUSERID 1
    +#endif
    +
    +#ifndef NO_FILIO_H
    +#define HAVE_SYS_FILIO_H 1
    +#endif
    +
    +#ifndef NO_GETCWD
    +#define HAVE_GETCWD 1
    +#endif
    +
    +#ifndef USE_SLANG
    +#ifndef NO_KEYPAD
    +#define HAVE_KEYPAD 1
    +#endif
    +#ifndef NO_TTYTYPE
    +#define HAVE_TTYTYPE 1
    +#endif
    +#endif /* USE_SLANG */
    +
    +#ifndef NO_PUTENV
    +#define HAVE_PUTENV 1
    +#endif
    +
    +#ifndef NO_SIZECHANGE
    +#define HAVE_SIZECHANGE 1
    +#endif
    +
    +#ifndef NO_UNISTD_H
    +#undef  HAVE_UNISTD_H
    +#define HAVE_UNISTD_H 1
    +#endif
    +
    +#ifndef NO_UTMP
    +#define HAVE_UTMP 1
    +#endif
    +
    +#endif /* HAVE_CONFIG_H */
    +
    +#include <assert.h>
    +
    +/* suppress inadvertent use of gettext in makeuctb when cross-compiling */
    +#ifdef DONT_USE_GETTEXT
    +#undef HAVE_GETTEXT
    +#undef HAVE_LIBGETTEXT_H
    +#undef HAVE_LIBINTL_H
    +#endif
    +
    +#ifndef HAVE_ICONV
    +#undef USE_JAPANESEUTF8_SUPPORT
    +#endif
    +
    +#ifndef lynx_srand
    +#define lynx_srand srand
    +#endif
    +
    +#ifndef lynx_rand
    +#define lynx_rand rand
    +#endif
    +
    +#if '0' != 48
    +#define NOT_ASCII
    +#endif
    +
    +#if '0' == 240
    +#define EBCDIC
    +#endif
    +
    +#ifndef LY_MAXPATH
    +#define LY_MAXPATH 256
    +#endif
    +
    +#ifndef GCC_NORETURN
    +#define GCC_NORETURN		/* nothing */
    +#endif
    +
    +#ifndef GCC_UNUSED
    +#define GCC_UNUSED		/* nothing */
    +#endif
    +
    +#if defined(__GNUC__) && defined(_FORTIFY_SOURCE)
    +#define USE_IGNORE_RC
    +extern int ignore_unused;
    +
    +#define IGNORE_RC(func) ignore_unused = (int) func
    +#else
    +#define IGNORE_RC(func) (void) func
    +#endif /* gcc workarounds */
    +
    +#if defined(__CYGWIN32__) && ! defined(__CYGWIN__)
    +#define __CYGWIN__ 1
    +#endif
    +
    +#if defined(__CYGWIN__)		/* 1998/12/31 (Thu) 16:13:46 */
    +#ifdef USE_OPENSSL_INCL
    +#define NOCRYPT			/* workaround for openssl 1.0.1e bug */
    +#endif
    +#include <windows.h>		/* #include "windef.h" */
    +#define BOOLEAN_DEFINED
    +#undef HAVE_POPEN		/* FIXME: does this not work, or is it missing */
    +#undef small			/* see <w32api/rpcndr.h> */
    +#endif
    +
    +#if defined(__DARWIN_NO_LONG_LONG)
    +#undef HAVE_ATOLL
    +#endif
    +
    +#if defined(HAVE_ATOLL)
    +#define LYatoll(n) atoll(n)
    +#else
    +extern off_t LYatoll(const char *value);
    +#endif
    +
    +/* cygwin, mingw32, etc. */
    +#ifdef FILE_DOES_NOT_EXIST
    +#undef FILE_DOES_NOT_EXIST	/* see <w32api/winnt.h> */
    +#endif
    +
    +/*
    + * VS .NET 2003 includes winsock.h unconditionally from windows.h,
    + * so we do not want to include windows.h if we want winsock2.h
    + */
    +#if defined(_WINDOWS) && !defined(__CYGWIN__)
    +
    +#ifndef __GNUC__
    +#pragma warning (disable : 4100)	/* unreferenced formal parameter */
    +#pragma warning (disable : 4127)	/* conditional expression is constant */
    +#pragma warning (disable : 4201)	/* nameless struct/union */
    +#pragma warning (disable : 4214)	/* bit field types other than int */
    +#pragma warning (disable : 4310)	/* cast truncates constant value */
    +#pragma warning (disable : 4514)	/* unreferenced inline function has been removed */
    +#pragma warning (disable : 4996)	/* This function or variable may be unsafe. ... */
    +#endif
    +
    +#if defined(USE_WINSOCK2_H) && (_MSC_VER >= 1300) && (_MSC_VER < 1400)
    +#include <winsock2.h>		/* includes windows.h, in turn windef.h */
    +#else
    +#include <windows.h>		/* #include "windef.h" */
    +#endif
    +
    +#define BOOLEAN_DEFINED
    +
    +#if !_WIN_CC			/* 1999/09/29 (Wed) 22:00:53 */
    +#include <dos.h>
    +#endif
    +
    +#if defined(DECL_SLEEP) && defined(HAVE_CONFIG_H)
    +#  undef sleep
    +#  if defined(__MINGW32__)
    +#    define sleep(n) Sleep((n)*100)
    +#  else
    +extern void sleep(unsigned __seconds);
    +#  endif
    +#elif !defined(__MINGW32__)
    +#  undef sleep
    +extern void sleep(unsigned __seconds);
    +#endif
    +
    +#define popen _popen
    +#define pclose _pclose
    +
    +#if defined(_MSC_VER) && (_MSC_VER > 0)
    +typedef unsigned short mode_t;
    +#endif
    +
    +#endif /* _WINDOWS */
    +
    +#if defined(USE_DEFAULT_COLORS) && !defined(HAVE_USE_DEFAULT_COLORS)
    +    /* if we don't have use_default_colors() */
    +#  undef USE_DEFAULT_COLORS
    +#endif
    +
    +#ifndef USE_COLOR_STYLE
    +    /* it's useless for such setup */
    +#  define NO_EMPTY_HREFLESS_A
    +#endif
    +
    +#if  defined(__EMX__) || defined(WIN_EX) || defined(HAVE_POPEN)
    +#  define CAN_CUT_AND_PASTE
    +#endif
    +
    +#if defined(USE_SLANG) || (defined(USE_COLOR_STYLE) && defined(__EMX__))
    +#  define USE_BLINK
    +#endif
    +
    +#if defined(DOSPATH) || defined(__EMX__)
    +#  define USE_DOS_DRIVES	/* we allow things like "c:" in paths */
    +#endif
    +
    +#if defined(UNIX)
    +#  if (defined(__BEOS__) || defined(__CYGWIN__) || defined(__DJGPP__) || defined(__EMX__) || defined(__MINGW32__))
    +#    define SINGLE_USER_UNIX	/* well, at least they try */
    +#  else
    +#    define MULTI_USER_UNIX
    +#  endif
    +#endif
    +
    +/*
    +
    +  ERROR TYPE
    +
    +   This is passed back when streams are aborted. It might be nice to have some structure
    +   of error messages, numbers, and recursive pointers to reasons.  Currently this is a
    +   placeholder for something more sophisticated.
    +
    + */
    +typedef void *HTError;		/* Unused at present -- best definition? */
    +
    +/*
    +
    +Standard C library for malloc() etc
    +
    + */
    +#ifdef HAVE_STDLIB_H
    +#include <stdlib.h>
    +#endif
    +
    +#ifndef EXIT_SUCCESS
    +#define EXIT_SUCCESS 0
    +#endif
    +
    +#ifndef EXIT_FAILURE
    +#define EXIT_FAILURE 1
    +#endif
    +
    +#ifdef __EMX__
    +#include <unistd.h>		/* should be re-include protected under EMX */
    +#define getcwd _getcwd2
    +#define chdir _chdir2
    +#endif
    +
    +#ifdef vax
    +#ifdef unix
    +#define ultrix			/* Assume vax+unix=ultrix */
    +#endif /* unix */
    +#endif /* vax */
    +
    +#ifndef VMS
    +#ifndef ultrix
    +
    +#ifdef NeXT
    +#include <libc.h>		/* NeXT */
    +#endif /* NeXT */
    +
    +#else /* ultrix: */
    +
    +#include <malloc.h>
    +#include <memory.h>
    +
    +#endif /* !ultrix */
    +#else /* VMS: */
    +
    +#include <unixlib.h>
    +#if defined(VAXC) && !defined(__DECC)
    +#define malloc	VAXC$MALLOC_OPT
    +#define calloc	VAXC$CALLOC_OPT
    +#define free	VAXC$FREE_OPT
    +#define cfree	VAXC$CFREE_OPT
    +#define realloc	VAXC$REALLOC_OPT
    +#endif /* VAXC && !__DECC */
    +
    +#endif /* !VMS */
    +
    +#ifndef NULL
    +#define NULL ((void *)0)
    +#endif
    +
    +#define DeConst(p)   (void *)(intptr_t)(p)
    +
    +#define isEmpty(s)   ((s) == 0 || *(s) == 0)
    +#define non_empty(s) !isEmpty(s)
    +
    +#define NonNull(s) (((s) != 0) ? s : "")
    +#define NONNULL(s) (((s) != 0) ? s : "(null)")
    +
    +/* array/table size */
    +#define	TABLESIZE(v)	(sizeof(v)/sizeof(v[0]))
    +
    +#define	typecalloc(cast)		(cast *)calloc((size_t)1, sizeof(cast))
    +#define	typecallocn(cast,ntypes)	(cast *)calloc((size_t)(ntypes),sizeof(cast))
    +
    +#define typeRealloc(cast,ptr,ntypes)    (cast *)realloc(ptr, (size_t)(ntypes)*sizeof(cast))
    +
    +#define typeMalloc(cast)                (cast *)malloc(sizeof(cast))
    +#define typeMallocn(cast,ntypes)        (cast *)malloc((size_t)(ntypes)*sizeof(cast))
    +
    +/*
    +
    +OFTEN USED INTEGER MACROS
    +
    +  Min and Max functions
    +
    + */
    +#ifndef HTMIN
    +#define HTMIN(a,b) ((a) <= (b) ? (a) : (b))
    +#define HTMAX(a,b) ((a) >= (b) ? (a) : (b))
    +#endif
    +/*
    +
    +Booleans
    +
    + */
    +/* Note: GOOD and BAD are already defined (differently) on RS6000 aix */
    +/* #define GOOD(status) ((status)38;1)   VMS style status: test bit 0         */
    +/* #define BAD(status)  (!GOOD(status))  Bit 0 set if OK, otherwise clear   */
    +
    +#ifndef _WINDOWS
    +#ifndef BOOLEAN_DEFINED
    +typedef char BOOLEAN;		/* Logical value */
    +
    +#ifndef CURSES
    +#ifndef TRUE
    +#define TRUE    (BOOLEAN)1
    +#define FALSE   (BOOLEAN)0
    +#endif
    +#endif /*  CURSES  */
    +#endif /*  BOOLEAN_DEFINED */
    +#define BOOLEAN_DEFINED
    +#endif /* _WINDOWS */
    +
    +#if defined(_MSC_VER) && (_MSC_VER >= 1300)
    +/* it declares BOOL/BOOLEAN as BYTE/int */
    +#else
    +#ifndef BOOL
    +#define BOOL BOOLEAN
    +#endif
    +#endif
    +
    +#ifndef YES
    +#define YES (BOOLEAN)1
    +#define NO (BOOLEAN)0
    +#endif
    +
    +#define STRING1PTR const char *
    +#define STRING2PTR const char * const *
    +
    +extern BOOL LYOutOfMemory;	/* Declared in LYexit.c - FM */
    +
    +#define TCP_PORT 80		/* Allocated to http by Jon Postel/ISI 24-Jan-92 */
    +#define OLD_TCP_PORT 2784	/* Try the old one if no answer on 80 */
    +#define DNP_OBJ 80		/* This one doesn't look busy, but we must check */
    +			/* That one was for decnet */
    +
    +/*      Inline Function WHITE: Is character c white space? */
    +/*      For speed, include all control characters */
    +
    +#define WHITE(c) ((UCH(TOASCII(c))) <= 32)
    +
    +/*     Inline Function LYIsASCII: Is character c a traditional ASCII
    + *     character (i.e. <128) after converting from host character set.  */
    +
    +#define LYIsASCII(c) (TOASCII(UCH(c)) < 128)
    +
    +/*
    +
    +Success (>=0) and failure (<0) codes
    +
    +Some of the values are chosen to be HTTP-like, but status return values
    +are generally not the response status from any specific protocol.
    +
    + */
    +
    +#define HT_PARSER_OTHER_CONTENT  701	/* tells SGML to change content model */
    +#define HT_PARSER_REOPEN_ELT     700	/* tells SGML parser to keep tag open */
    +#define HT_REDIRECTING           399
    +#define HT_PARTIAL_CONTENT       206	/* Partial Content */
    +#define HT_LOADED                200	/* Instead of a socket */
    +
    +#define HT_OK                      0	/* Generic success */
    +
    +#define HT_ERROR                  -1	/* Generic failure */
    +#define HT_CANNOT_TRANSLATE       -4
    +#define HT_BAD_EOF               -12	/* Premature EOF */
    +#define HT_NO_CONNECTION         -99	/* ERR no connection available - */
    +#define HT_NO_DATA              -204	/* OK but no data was loaded - */
    +					/* possibly other app started or forked */
    +#define HT_NO_ACCESS            -401	/* Access not available */
    +#define HT_FORBIDDEN            -403	/* Access forbidden */
    +#define HT_NOT_ACCEPTABLE       -406	/* Not Acceptable */
    +#define HT_H_ERRNO_VALID        -800	/* see h_errno for resolver error */
    +#define HT_INTERNAL             -900	/* Weird -- should never happen. */
    +#define HT_INTERRUPTED        -29998
    +#define HT_NOT_LOADED         -29999
    +
    +#ifndef va_arg
    +#  include <stdarg.h>
    +#endif
    +
    +#define LYva_start(ap,format) va_start(ap,format)
    +
    +/*
    + * GCC can be told that some functions are like printf (and do type-checking on
    + * their parameters).
    + */
    +#ifndef GCC_PRINTFLIKE
    +#if defined(GCC_PRINTF) && !defined(printf) && !defined(HAVE_LIBUTF8_H)
    +#define GCC_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
    +#else
    +#define GCC_PRINTFLIKE(fmt,var)	/*nothing */
    +#endif
    +#endif
    +
    +#include <HTString.h>		/* String utilities */
    +
    +/*
    +
    +Out Of Memory checking for malloc() return:
    +
    + */
    +#ifndef __FILE__
    +#define __FILE__ ""
    +#define __LINE__ ""
    +#endif
    +
    +#include <LYexit.h>
    +
    +/*
    + * Upper- and Lowercase macros
    + *
    + * The problem here is that toupper(x) is not defined officially unless
    + * isupper(x) is.  These macros are CERTAINLY needed on #if defined(pyr) ||
    + * define(mips) or BDSI platforms.  For safefy, we make them mandatory.
    + *
    + * Note: Pyramid and Mips can't uppercase non-alpha.
    + */
    +#include <ctype.h>
    +#include <string.h>
    +
    +#ifndef TOLOWER
    +
    +#ifdef USE_ASCII_CTYPES
    +
    +#define TOLOWER(c) ascii_tolower(UCH(c))
    +#define TOUPPER(c) ascii_toupper(UCH(c))
    +#define ISUPPER(c) ascii_isupper(UCH(c))
    +
    +#else
    +
    +#define TOLOWER(c) (isupper(UCH(c)) ? tolower(UCH(c)) : UCH(c))
    +#define TOUPPER(c) (islower(UCH(c)) ? toupper(UCH(c)) : UCH(c))
    +#define ISUPPER(c) (isupper(UCH(c)))
    +
    +#endif
    +
    +#endif /* TOLOWER */
    +
    +#define FREE(x)    {if (x != 0) {free((char *)x); x = NULL;}}
    +
    +/*
    +
    +The local equivalents of CR and LF
    +
    +   We can check for these after net ascii text has been converted to the local
    +   representation.  Similarly, we include them in strings to be sent as net ascii after
    +   translation.
    +
    + */
    +#define LF   FROMASCII('\012')	/* ASCII line feed LOCAL EQUIVALENT */
    +#define CR   FROMASCII('\015')	/* Will be converted to ^M for transmission */
    +
    +/*
    + * Debug message control.
    + */
    +#ifdef NO_LYNX_TRACE
    +#define WWW_TraceFlag   0
    +#define WWW_TraceMask   0
    +#define LYTraceLogFP    0
    +#else
    +extern BOOLEAN WWW_TraceFlag;
    +extern int WWW_TraceMask;
    +#endif
    +
    +#define TRACE           (WWW_TraceFlag)
    +#define TRACE_bit(n)    (TRACE && (WWW_TraceMask & (1 << n)) != 0)
    +#define TRACE_SGML      (TRACE_bit(0))
    +#define TRACE_STYLE     (TRACE_bit(1))
    +#define TRACE_TRST      (TRACE_bit(2))
    +#define TRACE_CFG       (TRACE_bit(3))
    +#define TRACE_BSTRING   (TRACE_bit(4))
    +#define TRACE_COOKIES   (TRACE_bit(5))
    +#define TRACE_CHARSETS  (TRACE_bit(6))
    +#define TRACE_GRIDTEXT  (TRACE_bit(7))
    +#define TRACE_TIMING    (TRACE_bit(8))
    +#define TRACE_HTPARSE   (TRACE_bit(9))
    +
    +/*
    + * Get printing/scanning formats.
    + */
    +#if defined(HAVE_INTTYPES_H)
    +#include <inttypes.h>
    +#endif
    +
    +#define DigitsOf(type)  (int)((sizeof(type)*8)/3)
    +
    +/*
    + * Printing/scanning-formats for "off_t", as well as cast needed to fit.
    + */
    +#if defined(HAVE_LONG_LONG) && defined(HAVE_INTTYPES_H) && defined(SIZEOF_OFF_T)
    +#if (SIZEOF_OFF_T == 8) && defined(PRId64) && defined(SCNd64)
    +
    +#define PRI_off_t	PRId64
    +#define SCN_off_t	SCNd64
    +#define CAST_off_t(n)	(int64_t)(n)
    +
    +#elif (SIZEOF_OFF_T == 4) && defined(PRId32)
    +
    +#define PRI_off_t	PRId32
    +#define SCN_off_t	SCNd32
    +
    +#if (SIZEOF_INT == 4)
    +#define CAST_off_t(n)	(int)(n)
    +#elif (SIZEOF_LONG == 4)
    +#define CAST_off_t(n)	(long)(n)
    +#else
    +#define CAST_off_t(n)	(int32_t)(n)
    +#endif
    +
    +#endif
    +#endif
    +
    +#ifndef PRI_off_t
    +#define GUESS_PRI_off_t
    +#if (SIZEOF_OFF_T == SIZEOF_LONG)
    +#define PRI_off_t	"ld"
    +#define SCN_off_t	"ld"
    +#define CAST_off_t(n)	(long)(n)
    +#elif defined(HAVE_LONG_LONG)
    +#define PRI_off_t	"lld"
    +#define SCN_off_t	"lld"
    +#define CAST_off_t(n)	(long long)(n)
    +#else
    +#define PRI_off_t	"ld"
    +/* SCN_off_t requires workaround */
    +#define CAST_off_t(n)	(long)(n)
    +#endif
    +#endif
    +
    +/*
    + * MinGW-32 uses only 32-bit DLL, which limits printing.
    + */
    +#if defined(__MINGW32__)
    +#undef  PRI_off_t
    +#undef  CAST_off_t
    +#define PRI_off_t	"ld"
    +#define CAST_off_t(n)	(long)(n)
    +#endif
    +
    +/*
    + * Printing-format for "time_t", as well as cast needed to fit.
    + */
    +#if defined(HAVE_LONG_LONG) && defined(HAVE_INTTYPES_H) && defined(SIZEOF_TIME_T)
    +#if (SIZEOF_TIME_T == 8) && defined(PRId64)
    +
    +#define PRI_time_t	PRId64
    +#define SCN_time_t	SCNd64
    +#define CAST_time_t(n)	(int64_t)(n)
    +
    +#elif (SIZEOF_TIME_T == 4) && defined(PRId32)
    +
    +#define PRI_time_t	PRId32
    +#define SCN_time_t	SCNd32
    +
    +#if (SIZEOF_INT == 4)
    +#define CAST_time_t(n)	(int)(n)
    +#elif (SIZEOF_LONG == 4)
    +#define CAST_time_t(n)	(long)(n)
    +#else
    +#define CAST_time_t(n)	(int32_t)(n)
    +#endif
    +
    +#endif
    +#endif
    +
    +#ifndef PRI_time_t
    +#if defined(HAVE_LONG_LONG) && (SIZEOF_TIME_T > SIZEOF_LONG)
    +#define PRI_time_t	"lld"
    +#define SCN_time_t	"lld"
    +#define CAST_time_t(n)	(long long)(n)
    +#else
    +#define PRI_time_t	"ld"
    +#define SCN_time_t	"ld"
    +#define CAST_time_t(n)	(long)(n)
    +#endif
    +#endif
    +
    +/*
    + * Printing-format for "UCode_t".
    + */
    +#define PRI_UCode_t	"lX"
    +#define CAST_UCode_t(n)	(unsigned long)(n)
    +
    +/*
    + * Verbose-tracing.
    + */
    +#if defined(USE_VERTRACE) && !defined(LY_TRACELINE)
    +#define LY_TRACELINE __LINE__
    +#endif
    +
    +#if defined(LY_TRACELINE)
    +#define LY_SHOWWHERE fprintf( tfp, "%s: %d: ", __FILE__, LY_TRACELINE ),
    +#else
    +#define LY_SHOWWHERE		/* nothing */
    +#endif
    +
    +#define CTRACE(p)         ((void)((TRACE) && ( LY_SHOWWHERE fprintf p )))
    +#define CTRACE2(m,p)      ((void)((m)     && ( LY_SHOWWHERE fprintf p )))
    +#define tfp TraceFP()
    +#define CTRACE_SLEEP(secs) if (TRACE && LYTraceLogFP == 0) sleep((unsigned)secs)
    +#define CTRACE_FLUSH(fp)   if (TRACE) fflush(fp)
    +
    +#include <www_tcp.h>
    +
    +/*
    + * We force this include-ordering since socks.h contains redefinitions of
    + * functions that probably are prototyped via other includes.  The socks.h
    + * definitions have to be included everywhere, since they're making wrappers
    + * for the stdio functions as well as the network functions.
    + */
    +#if defined(USE_SOCKS5)
    +#define SOCKS4TO5		/* turn on the Rxxxx definitions used in Lynx */
    +#include <socks.h>
    +
    +/*
    + * The AIX- and SOCKS4-specific definitions in socks.h are inconsistent.
    + * Repair them so they're consistent (and usable).
    + */
    +#if defined(_AIX) && !defined(USE_SOCKS4_PREFIX)
    +#undef  Raccept
    +#define Raccept       accept
    +#undef  Rgetsockname
    +#define Rgetsockname  getsockname
    +#undef  Rgetpeername
    +#define Rgetpeername  getpeername
    +#endif
    +
    +/*
    + * Workaround for order-of-evaluation problem with gcc and socks5 headers
    + * which breaks the Rxxxx names by attaching the prefix twice:
    + */
    +#ifdef INCLUDE_PROTOTYPES
    +#undef  Raccept
    +#undef  Rbind
    +#undef  Rconnect
    +#undef  Rlisten
    +#undef  Rselect
    +#undef  Rgetpeername
    +#undef  Rgetsockname
    +#define Raccept       accept
    +#define Rbind         bind
    +#define Rconnect      connect
    +#define Rgetpeername  getpeername
    +#define Rgetsockname  getsockname
    +#define Rlisten       listen
    +#define Rselect       select
    +#endif
    +
    +#elif defined(SOCKS)
    +#include <socks.h>
    +#endif /* USE_SOCKS5 */
    +
    +#ifdef USE_SSL
    +
    +#define free_func free__func
    +
    +#ifdef USE_OPENSSL_INCL
    +#include <openssl/ssl.h>
    +#include <openssl/crypto.h>
    +#include <openssl/rand.h>
    +#include <openssl/err.h>
    +
    +#else
    +
    +#if defined(USE_GNUTLS_FUNCS)
    +#include <tidy_tls.h>
    +#define USE_GNUTLS_INCL 1	/* do this for the ".c" ifdef's */
    +#elif defined(USE_GNUTLS_INCL)
    +#include <gnutls/openssl.h>
    +/*
    + * GNUTLS's implementation of OpenSSL is very incomplete and rudimentary.
    + * For a start, let's make it compile (TD - 2003/4/13).
    + */
    +#ifndef SSL_VERIFY_PEER
    +#define SSL_VERIFY_PEER			0x01
    +#endif
    +#else
    +
    +#ifdef USE_NSS_COMPAT_INCL
    +#include <nss_compat_ossl/nss_compat_ossl.h>
    +
    +#else /* assume SSLeay */
    +#include <ssl.h>
    +#include <crypto.h>
    +#include <rand.h>
    +#include <err.h>
    +#endif
    +#endif
    +#endif /* USE_OPENSSL_INCL */
    +
    +#undef free_func
    +#endif /* USE_SSL */
    +
    +#ifdef HAVE_BSD_STDLIB_H
    +#include <bsd/stdlib.h>		/* prototype for arc4random.h */
    +#elif defined(HAVE_BSD_RANDOM_H)
    +#include <bsd/random.h>		/* prototype for arc4random.h */
    +#endif
    +
    +#ifdef HAVE_LIBDMALLOC
    +#include <dmalloc.h>		/* Gray Watson's library */
    +#define show_alloc() dmalloc_log_unfreed()
    +#endif
    +
    +#ifdef HAVE_LIBDBMALLOC
    +#include <dbmalloc.h>		/* Conor Cahill's library */
    +#define show_alloc() malloc_dump(fileno(stderr))
    +#endif
    +
    +#ifndef show_alloc
    +#define show_alloc()		/* nothing */
    +#endif
    +
    +#include <userdefs.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#ifndef TOLOWER
    +#ifdef USE_ASCII_CTYPES
    +    extern int ascii_toupper(int);
    +    extern int ascii_tolower(int);
    +    extern int ascii_isupper(int);
    +#endif
    +#endif
    +
    +    extern FILE *TraceFP(void);
    +
    +    extern char *HTSkipToAt(char *host, int *gen_delims);
    +    extern void strip_userid(char *host, int warn);
    +
    +#ifdef USE_SSL
    +    extern SSL *HTGetSSLHandle(void);
    +    extern void HTSSLInitPRNG(void);
    +    extern int HTGetSSLCharacter(void *handle);
    +#endif				/* USE_SSL */
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTUTILS_H */
    diff --git a/WWW/Library/Implementation/HTVMSUtils.c b/WWW/Library/Implementation/HTVMSUtils.c
    new file mode 100644
    index 0000000..38e583a
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMSUtils.c
    @@ -0,0 +1,1131 @@
    +/*
    + * $LynxId: HTVMSUtils.c,v 1.40 2020/01/21 21:58:29 tom Exp $
    + *
    + * MODULE							HTVMSUtil.c
    + *		VMS Utility Routines
    + *
    + * AUTHORS:
    + *	MD	Mark Donszelmann    duns@vxdeop.cern.ch
    + *
    + * HISTORY:
    + *	14 Nov 93  MD	Written
    + *
    + * BUGS:
    + *
    + *
    + */
    +
    +#include <HTUtils.h>
    +#ifdef VMS
    +#include <HTFormat.h>
    +#include <HTStream.h>
    +#include <UCDefs.h>
    +#include <UCMap.h>
    +#include <UCAux.h>
    +#include <HTFTP.h>
    +#include <HTTCP.h>
    +#include <HTVMSUtils.h>
    +#include <ssdef.h>
    +#include <jpidef.h>
    +#include <prvdef.h>
    +#include <acldef.h>
    +#include <chpdef.h>
    +#include <descrip.h>
    +#include <lib$routines.h>
    +#include <starlet.h>
    +#include <rmsdef.h>
    +
    +#include <LYGlobalDefs.h>
    +#include <LYUtils.h>
    +#include <LYLeaks.h>
    +#include <LYStrings.h>
    +
    +BOOL HTVMSFileVersions = FALSE;	/* Include version numbers in listing? */
    +
    +typedef struct {
    +    unsigned long BufferLength:16;
    +    unsigned long ItemCode:16;
    +    unsigned long BufferAddress:32;
    +    unsigned long ReturnLengthAddress:32;
    +} ItemStruct;
    +
    +/* PUBLIC							HTVMS_authSysPrv()
    + *		CHECKS IF THIS PROCESS IS AUTHORIZED TO ENABLE SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *	returns	YES if SYSPRV is authorized
    + */
    +BOOL HTVMS_authSysPrv(void)
    +{
    +    unsigned long Result;
    +    ItemStruct ItemList[2];
    +    unsigned long Length;
    +    unsigned long Buffer[2];
    +
    +    /* fill Item */
    +    ItemList[0].BufferLength = sizeof(Buffer);
    +    ItemList[0].BufferAddress = (unsigned long) Buffer;
    +    ItemList[0].ReturnLengthAddress = (unsigned long) &Length;
    +    ItemList[0].ItemCode = JPI$_AUTHPRIV;
    +
    +    /* terminate list */
    +    ItemList[1].ItemCode = 0;
    +    ItemList[1].BufferLength = 0;
    +
    +    /* call system */
    +    Result = sys$getjpiw(0, 0, 0, ItemList, 0, 0, 0);
    +
    +    if (Result != SS$_NORMAL)
    +	return (NO);
    +
    +    if (Buffer[0] & PRV$M_SYSPRV)
    +	return (YES);
    +
    +    return (NO);
    +}
    +
    +/* PUBLIC							HTVMS_enableSysPrv()
    + *		ENABLES SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *
    + */
    +void HTVMS_enableSysPrv(void)
    +{
    +    unsigned long Result;
    +    unsigned long Prv[2], PreviousPrv[2];
    +
    +    Prv[0] = PRV$M_SYSPRV;
    +    Prv[1] = 0;
    +    Result = sys$setprv(1, &Prv, 0, &PreviousPrv);
    +
    +    if (Result == SS$_NORMAL) {
    +	if (!(PreviousPrv[0] & PRV$M_SYSPRV)) {
    +	    CTRACE((tfp, "HTVMS_enableSysPrv: Enabled SYSPRV\n"));
    +	}
    +    }
    +}
    +
    +/* PUBLIC							HTVMS_disableSysPrv()
    + *		DISABLES SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *
    + */
    +void HTVMS_disableSysPrv(void)
    +{
    +    unsigned long Result;
    +    unsigned long Prv[2], PreviousPrv[2];
    +
    +    Prv[0] = PRV$M_SYSPRV;
    +    Prv[1] = 0;
    +    Result = sys$setprv(0, &Prv, 0, &PreviousPrv);
    +
    +    if (Result == SS$_NORMAL) {
    +	if (PreviousPrv[0] & PRV$M_SYSPRV) {
    +	    CTRACE((tfp, "HTVMS_disableSysPrv: Disabled SYSPRV\n"));
    +	}
    +    }
    +}
    +
    +/* PUBLIC							HTVMS_checkAccess()
    + *		CHECKS ACCESS TO FILE FOR CERTAIN USER
    + * ON ENTRY:
    + *	FileName	The file to be accessed
    + *	UserName	Name of the user to check access for.
    + *			User nobody, represented by "" is given NO for an answer
    + *	Method		Name of the method to be checked
    + *
    + * ON EXIT:
    + *	returns YES if access is allowed
    + *
    + */
    +BOOL HTVMS_checkAccess(const char *FileName,
    +		       const char *UserName,
    +		       const char *Method)
    +{
    +    unsigned long Result;
    +    ItemStruct ItemList[2];
    +    unsigned long Length;
    +    unsigned long Buffer;
    +    unsigned long ObjType;
    +
    +    char *VmsName;
    +
    +    struct dsc$descriptor_s FileNameDesc;
    +    struct dsc$descriptor_s UserNameDesc;
    +
    +    char *colon;
    +
    +    /* user nobody should access as from account under which server is running */
    +    if (0 == strcmp(UserName, ""))
    +	return (NO);
    +
    +    /* check Filename and convert */
    +    colon = StrChr(FileName, ':');
    +    if (colon)
    +	VmsName = HTVMS_name("", colon + 1);
    +    else
    +	VmsName = HTVMS_name("", FileName);
    +
    +    /* check for GET */
    +    if (0 == strcmp(Method, "GET")) {
    +	/* fill Item */
    +	ItemList[0].BufferLength = sizeof(Buffer);
    +	ItemList[0].BufferAddress = (unsigned long) &Buffer;
    +	ItemList[0].ReturnLengthAddress = (unsigned long) &Length;
    +	ItemList[0].ItemCode = CHP$_FLAGS;
    +
    +	/* terminate list */
    +	ItemList[1].ItemCode = 0;
    +	ItemList[1].BufferLength = 0;
    +
    +	/* fill input */
    +	ObjType = ACL$C_FILE;
    +	Buffer = CHP$M_READ;
    +	UserNameDesc.dsc$w_length = strlen(UserName);
    +	UserNameDesc.dsc$b_dtype = DSC$K_DTYPE_T;
    +	UserNameDesc.dsc$b_class = DSC$K_CLASS_S;
    +	UserNameDesc.dsc$a_pointer = (char *) UserName;
    +	FileNameDesc.dsc$w_length = strlen(VmsName);
    +	FileNameDesc.dsc$b_dtype = DSC$K_DTYPE_T;
    +	FileNameDesc.dsc$b_class = DSC$K_CLASS_S;
    +	FileNameDesc.dsc$a_pointer = VmsName;
    +
    +	/* call system */
    +	Result = sys$check_access(&ObjType, &FileNameDesc, &UserNameDesc, ItemList);
    +
    +	if (Result == SS$_NORMAL)
    +	    return (YES);
    +	else
    +	    return (NO);
    +    }
    +
    +    return (NO);
    +}
    +
    +/* PUBLIC							HTVMS_wwwName()
    + *		CONVERTS VMS Name into WWW Name
    + * ON ENTRY:
    + *	vmsname		VMS file specification (NO NODE)
    + *
    + * ON EXIT:
    + *	returns		www file specification
    + *
    + * EXAMPLES:
    + *	vmsname				wwwname
    + *	DISK$USER			disk$user
    + *	DISK$USER:			/disk$user/
    + *	DISK$USER:[DUNS]		/disk$user/duns
    + *	DISK$USER:[DUNS.ECHO]		/disk$user/duns/echo
    + *	[DUNS]				duns
    + *	[DUNS.ECHO]			duns/echo
    + *	[DUNS.ECHO.-.TRANS]		duns/echo/../trans
    + *	[DUNS.ECHO.--.TRANS]		duns/echo/../../trans
    + *	[.DUNS]				duns
    + *	[.DUNS.ECHO]			duns/echo
    + *	[.DUNS.ECHO]TEST.COM		duns/echo/test.com
    + *	TEST.COM			test.com
    + *
    + *
    + */
    +const char *HTVMS_wwwName(const char *vmsname)
    +{
    +    static char wwwname[LY_MAXPATH];
    +    const char *src;
    +    char *dst;
    +    int dir;
    +
    +    dst = wwwname;
    +    src = vmsname;
    +    dir = 0;
    +    if (StrChr(src, ':'))
    +	*(dst++) = '/';
    +    for (; *src != '\0'; src++) {
    +	switch (*src) {
    +	case ':':
    +	    *(dst++) = '/';
    +	    break;
    +	case '-':
    +	    if (dir) {
    +		if ((*(src - 1) == '[' ||
    +		     *(src - 1) == '.' ||
    +		     *(src - 1) == '-') &&
    +		    (*(src + 1) == '.' ||
    +		     *(src + 1) == '-')) {
    +		    *(dst++) = '/';
    +		    *(dst++) = '.';
    +		    *(dst++) = '.';
    +		} else
    +		    *(dst++) = '-';
    +	    } else {
    +		if (*(src - 1) == ']')
    +		    *(dst++) = '/';
    +		*(dst++) = '-';
    +	    }
    +	    break;
    +	case '.':
    +	    if (dir) {
    +		if (*(src - 1) != '[')
    +		    *(dst++) = '/';
    +	    } else {
    +		if (*(src - 1) == ']')
    +		    *(dst++) = '/';
    +		*(dst++) = '.';
    +	    }
    +	    break;
    +	case '[':
    +	    dir = 1;
    +	    break;
    +	case ']':
    +	    dir = 0;
    +	    break;
    +	default:
    +	    if (*(src - 1) == ']')
    +		*(dst++) = '/';
    +	    *(dst++) = *src;
    +	    break;
    +	}
    +    }
    +    *(dst++) = '\0';
    +    return (wwwname);
    +}
    +
    +/*
    + *	The code below is for directory browsing by VMS Curses clients.
    + *	It is based on the newer WWWLib's HTDirBrw.c. - Foteos Macrides
    + */
    +int HTStat(const char *filename,
    +	   struct stat *info)
    +{
    +    /*
    +       the following stuff does not work in VMS with a normal stat...
    +       -->   /disk$user/duns/www if www is a directory
    +       is statted like:  /disk$user/duns/www.dir
    +       after a normal stat has failed
    +       -->   /disk$user/duns     if duns is a toplevel directory
    +       is statted like:  /disk$user/000000/duns.dir
    +       -->   /disk$user since disk$user is a device
    +       is statted like:  /disk$user/000000/000000.dir
    +       -->   /
    +       searches all devices, no solution yet...
    +       -->   /vxcern!/disk$cr/wwwteam/login.com
    +       is not statted but granted with fake information...
    +     */
    +    int Result;
    +    int Len;
    +    char *Ptr, *Ptr2;
    +    static char *Name;
    +
    +    /* try normal stat... */
    +    Result = stat((char *) filename, info);
    +    if (Result == 0)
    +	return (Result);
    +
    +    /* make local copy */
    +    StrAllocCopy(Name, filename);
    +
    +    /* failed,so do device search in case root is requested */
    +    if (!strcmp(Name, "/")) {	/* root requested */
    +	return (-1);
    +    }
    +
    +    /* failed so this might be a directory, add '.dir' */
    +    Len = strlen(Name);
    +    if (Name[Len - 1] == '/')
    +	Name[Len - 1] = '\0';
    +
    +    /* fail in case of device */
    +    Ptr = StrChr(Name + 1, '/');
    +    if ((Ptr == NULL) && (Name[0] == '/')) {	/* device only... */
    +	StrAllocCat(Name, "/000000/000000");
    +    }
    +
    +    if (Ptr != NULL) {		/* correct filename in case of toplevel dir */
    +	Ptr2 = StrChr(Ptr + 1, '/');
    +	if ((Ptr2 == NULL) && (Name[0] == '/')) {
    +	    char End[256];
    +
    +	    LYStrNCpy(End, Ptr, sizeof(End) - 1);
    +	    *(Ptr + 1) = '\0';
    +	    StrAllocCat(Name, "000000");
    +	    StrAllocCat(Name, End);
    +	}
    +    }
    +
    +    /* try in case a file on toplevel directory or .DIR was already specified */
    +    Result = stat(Name, info);
    +    if (Result == 0)
    +	return (Result);
    +
    +    /* add .DIR and try again */
    +    StrAllocCat(Name, ".dir");
    +    Result = stat(Name, info);
    +    return (Result);
    +}
    +
    +#ifndef	_POSIX_SOURCE
    +#define	d_ino	d_fileno	/* compatibility */
    +#ifndef	NULL
    +#define	NULL	0
    +#endif
    +#endif /* !_POSIX_SOURCE */
    +
    +typedef struct __dirdesc {
    +    long context;		/* context descriptor for LIB$FIND_FILE calls */
    +    char dirname[255 + 1];	/* keeps the directory name, including *.* */
    +    struct dsc$descriptor_s dirname_desc;	/* descriptor of dirname */
    +} DIR;
    +
    +static DIR *HTVMSopendir(char *dirname);
    +static struct dirent *HTVMSreaddir(DIR *dirp);
    +static int HTVMSclosedir(DIR *dirp);
    +
    +/*** #include <sys_dirent.h> ***/
    +/*** "sys_dirent.h" ***/
    +struct dirent {
    +    unsigned long d_fileno;	/* file number of entry */
    +    unsigned short d_namlen;	/* length of string in d_name */
    +    char d_name[255 + 1];	/* name (up to MAXNAMLEN + 1) */
    +};
    +
    +#ifndef	_POSIX_SOURCE
    +/*
    + * It's unlikely to change, but make sure that sizeof d_name above is
    + * at least MAXNAMLEN + 1 (more may be added for padding).
    + */
    +#define	MAXNAMLEN	255
    +/*
    + * The macro DIRSIZ(dp) gives the minimum amount of space required to represent
    + * a directory entry.  For any directory entry dp->d_reclen >= DIRSIZ(dp).
    + * Specific filesystem types may use this macro to construct the value
    + * for d_reclen.
    + */
    +#undef	DIRSIZ
    +#define	DIRSIZ(dp) \
    +	(((sizeof(struct dirent) - (MAXNAMLEN+1) + ((dp)->d_namlen+1)) +3) & ~3)
    +
    +#endif /* !_POSIX_SOURCE */
    +
    +static DIR *HTVMSopendir(char *dirname)
    +{
    +    static DIR dir;
    +    char *closebracket;
    +    long status;
    +    struct dsc$descriptor_s entryname_desc;
    +    struct dsc$descriptor_s dirname_desc;
    +    static char *DirEntry;
    +    char Actual[256];
    +    char VMSentry[256];
    +    char UnixEntry[256];
    +    int index;
    +    char *dot;
    +
    +    /* check if directory exists */
    +    /* dirname can look like /disk$user/duns/www/test/multi    */
    +    /* or like               /disk$user/duns/www/test/multi/   */
    +    /* DirEntry should look like     disk$user:[duns.www.test]multi in both cases */
    +    /* dir.dirname should look like  disk$user:[duns.www.test.multi] */
    +    sprintf(UnixEntry, "%.*s", sizeof(UnixEntry) - 2, dirname);
    +    if (UnixEntry[strlen(UnixEntry) - 1] != '/')
    +	strcat(UnixEntry, "/");
    +
    +    StrAllocCopy(DirEntry, HTVMS_name("", UnixEntry));
    +    if (strlen(DirEntry) > sizeof(dir.dirname) - 1)
    +	return (NULL);
    +    strcpy(dir.dirname, DirEntry);
    +    index = strlen(DirEntry) - 1;
    +
    +    if (DirEntry[index] == ']')
    +	DirEntry[index] = '\0';
    +
    +    if ((dot = strrchr(DirEntry, '.')) == NULL) {	/* convert disk$user:[duns] into disk$user:[000000]duns.dir */
    +	char *openbr = strrchr(DirEntry, '[');
    +
    +	if (!openbr) {		/* convert disk$user: into disk$user:[000000]000000.dir */
    +	    if (strlen(dir.dirname) > sizeof(dir.dirname) - 10)
    +		return (NULL);
    +	    sprintf(dir.dirname, "%.*s[000000]", sizeof(dir.dirname) - 9, DirEntry);
    +	    StrAllocCat(DirEntry, "[000000]000000.dir");
    +	} else {
    +	    char End[256];
    +
    +	    strcpy(End, openbr + 1);
    +	    *(openbr + 1) = '\0';
    +	    StrAllocCat(DirEntry, "000000]");
    +	    StrAllocCat(DirEntry, End);
    +	    StrAllocCat(DirEntry, ".dir");
    +	}
    +    } else {
    +	*dot = ']';
    +	StrAllocCat(DirEntry, ".dir");
    +    }
    +    /* lib$find_file needs a fixed-size buffer */
    +    LYStrNCpy(Actual, DirEntry, sizeof(Actual) - 1);
    +
    +    dir.context = 0;
    +    dirname_desc.dsc$w_length = strlen(Actual);
    +    dirname_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    +    dirname_desc.dsc$b_class = DSC$K_CLASS_S;
    +    dirname_desc.dsc$a_pointer = (char *) &(Actual);
    +
    +    /* look for the directory */
    +    entryname_desc.dsc$w_length = 255;
    +    entryname_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    +    entryname_desc.dsc$b_class = DSC$K_CLASS_S;
    +    entryname_desc.dsc$a_pointer = VMSentry;
    +
    +    status = lib$find_file(&(dirname_desc),
    +			   &entryname_desc,
    +			   &(dir.context),
    +			   0, 0, 0, 0);
    +    if (!(status & 0x01)) {	/* directory not found */
    +	return (NULL);
    +    }
    +
    +    if (strlen(dir.dirname) > sizeof(dir.dirname) - 10)
    +	return (NULL);
    +    if (HTVMSFileVersions)
    +	strcat(dir.dirname, "*.*;*");
    +    else
    +	strcat(dir.dirname, "*.*");
    +    dir.context = 0;
    +    dir.dirname_desc.dsc$w_length = strlen(dir.dirname);
    +    dir.dirname_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    +    dir.dirname_desc.dsc$b_class = DSC$K_CLASS_S;
    +    dir.dirname_desc.dsc$a_pointer = (char *) &(dir.dirname);
    +    return (&dir);
    +}
    +
    +static struct dirent *HTVMSreaddir(DIR *dirp)
    +{
    +    static struct dirent entry;
    +    long status;
    +    struct dsc$descriptor_s entryname_desc;
    +    char *space, *slash;
    +    char VMSentry[256];
    +    const char *UnixEntry;
    +
    +    entryname_desc.dsc$w_length = 255;
    +    entryname_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    +    entryname_desc.dsc$b_class = DSC$K_CLASS_S;
    +    entryname_desc.dsc$a_pointer = VMSentry;
    +
    +    status = lib$find_file(&(dirp->dirname_desc),
    +			   &entryname_desc,
    +			   &(dirp->context),
    +			   0, 0, 0, 0);
    +    if (status == RMS$_NMF) {	/* no more files */
    +	return (NULL);
    +    } else {			/* ok */
    +	if (!(status & 0x01))
    +	    return (0);
    +	if (HTVMSFileVersions)
    +	    space = StrChr(VMSentry, ' ');
    +	else
    +	    space = StrChr(VMSentry, ';');
    +	if (space)
    +	    *space = '\0';
    +
    +	/* convert to unix style... */
    +	UnixEntry = HTVMS_wwwName(VMSentry);
    +	slash = strrchr(UnixEntry, '/') + 1;
    +	strcpy(entry.d_name, slash);
    +	entry.d_namlen = strlen(entry.d_name);
    +	entry.d_fileno = 1;
    +	return (&entry);
    +    }
    +}
    +
    +static int HTVMSclosedir(DIR *dirp)
    +{
    +    long status;
    +
    +    status = lib$find_file_end(&(dirp->context));
    +    if (!(status & 0x01))
    +	exit_immediately(status);
    +    dirp->context = 0;
    +    return (0);
    +}
    +
    +#include <HTAnchor.h>
    +#include <HTParse.h>
    +#include <HTBTree.h>
    +#include <HTFile.h>		/* For HTFileFormat() */
    +#include <HTAlert.h>
    +/*
    + *  Hypertext object building machinery.
    + */
    +#include <HTML.h>
    +#define PUTC(c) (*targetClass.put_character)(target, c)
    +#define PUTS(s) (*targetClass.put_string)(target, s)
    +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
    +#define END(e) (*targetClass.end_element)(target, e, 0)
    +#define FREE_TARGET (*targetClass._free)(target)
    +#define ABORT_TARGET (*targetClass._free)(target)
    +struct _HTStructured {
    +    const HTStructuredClass *isa;
    +    /* ... */
    +};
    +
    +#define STRUCT_DIRENT struct dirent
    +
    +static char *months[12] =
    +{
    +    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    +    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    +};
    +
    +typedef struct _VMSEntryInfo {
    +    char *filename;
    +    char *type;
    +    char *date;
    +    unsigned int size;
    +    BOOLEAN display;		/* show this entry? */
    +} VMSEntryInfo;
    +
    +static void free_VMSEntryInfo_contents(VMSEntryInfo * entry_info)
    +{
    +    if (entry_info) {
    +	FREE(entry_info->filename);
    +	FREE(entry_info->type);
    +	FREE(entry_info->date);
    +    }
    +    /* don't free the struct */
    +}
    +
    +int compare_VMSEntryInfo_structs(VMSEntryInfo * entry1, VMSEntryInfo * entry2)
    +{
    +    int i, status;
    +    char date1[16], date2[16], time1[8], time2[8], month[4];
    +
    +    switch (HTfileSortMethod) {
    +    case FILE_BY_SIZE:
    +	/* both equal or both 0 */
    +	if (entry1->size == entry2->size)
    +	    return (strcasecomp(entry1->filename,
    +				entry2->filename));
    +	else if (entry1->size > entry2->size)
    +	    return (1);
    +	else
    +	    return (-1);
    +    case FILE_BY_TYPE:
    +	if (entry1->type && entry2->type) {
    +	    status = strcasecomp(entry1->type, entry2->type);
    +	    if (status)
    +		return (status);
    +	    /* else fall to filename comparison */
    +	}
    +	return (strcasecomp(entry1->filename,
    +			    entry2->filename));
    +    case FILE_BY_DATE:
    +	if (entry1->date && entry2->date) {
    +	    /*
    +	     * Make sure we have the correct length. - FM
    +	     */
    +	    if (strlen(entry1->date) != 12 ||
    +		strlen(entry2->date) != 12) {
    +		return (strcasecomp(entry1->filename,
    +				    entry2->filename));
    +	    }
    +	    /*
    +	     * Set up for sorting in reverse
    +	     * chronological order. - FM
    +	     */
    +	    if (entry1->date[7] != ' ') {
    +		strcpy(date1, "9999");
    +		strcpy(time1, (char *) &entry1->date[7]);
    +	    } else {
    +		strcpy(date1, (char *) &entry1->date[8]);
    +		strcpy(time1, "00:00");
    +	    }
    +	    LYStrNCpy(month, entry1->date, 3);
    +	    for (i = 0; i < 12; i++) {
    +		if (!strcasecomp(month, months[i])) {
    +		    break;
    +		}
    +	    }
    +	    i++;
    +	    sprintf(month, "%02d", i);
    +	    strcat(date1, month);
    +	    StrNCat(date1, (char *) &entry1->date[4], 2);
    +	    date1[8] = '\0';
    +	    if (date1[6] == ' ') {
    +		date1[6] = '0';
    +	    }
    +	    strcat(date1, time1);
    +	    if (entry2->date[7] != ' ') {
    +		strcpy(date2, "9999");
    +		strcpy(time2, (char *) &entry2->date[7]);
    +	    } else {
    +		strcpy(date2, (char *) &entry2->date[8]);
    +		strcpy(time2, "00:00");
    +	    }
    +	    LYStrNCpy(month, entry2->date, 3);
    +	    for (i = 0; i < 12; i++) {
    +		if (!strcasecomp(month, months[i])) {
    +		    break;
    +		}
    +	    }
    +	    i++;
    +	    sprintf(month, "%02d", i);
    +	    strcat(date2, month);
    +	    StrNCat(date2, (char *) &entry2->date[4], 2);
    +	    date2[8] = '\0';
    +	    if (date2[6] == ' ') {
    +		date2[6] = '0';
    +	    }
    +	    strcat(date2, time2);
    +	    /*
    +	     * Do the comparison. - FM
    +	     */
    +	    status = strcasecomp(date2, date1);
    +	    if (status)
    +		return (status);
    +	    /* else fall to filename comparison */
    +	}
    +	return (strcasecomp(entry1->filename,
    +			    entry2->filename));
    +    case FILE_BY_NAME:
    +    default:
    +	return (strcmp(entry1->filename,
    +		       entry2->filename));
    +    }
    +}
    +
    +/*							HTVMSBrowseDir()
    + *
    + *	This function generates a directory listing as an HTML-object
    + *	for local file URL's.  It assumes the first two elements of
    + *	of the path are a device followed by a directory:
    + *
    + *		file://localhost/device/directory[/[foo]]
    + *
    + *	Will not accept 000000 as a directory name.
    + *	Will offer links to parent through the top directory, unless
    + *	a terminal slash was included in the calling URL.
    + *
    + *	Returns HT_LOADED on success, HTLoadError() messages on error.
    + *
    + *	Developed for Lynx by Foteos Macrides (macrides@sci.wfeb.edu).
    + */
    +int HTVMSBrowseDir(const char *address,
    +		   HTParentAnchor *anchor,
    +		   HTFormat format_out,
    +		   HTStream *sink)
    +{
    +    HTStructured *target;
    +    HTStructuredClass targetClass;
    +    char *pathname = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
    +    char *tail = NULL;
    +    char *title = NULL;
    +    char *header = NULL;
    +    char *parent = NULL;
    +    char *relative = NULL;
    +    char *cp, *cp1;
    +    int pathend, len;
    +    DIR *dp;
    +    struct stat file_info;
    +    time_t NowTime;
    +    static char ThisYear[8];
    +    VMSEntryInfo *entry_info = 0;
    +    char string_buffer[64];
    +
    +    HTUnEscape(pathname);
    +    CTRACE((tfp, "HTVMSBrowseDir: Browsing `%s\'\n", pathname));
    +
    +    /*
    +     * Require at least two elements (presumably a device and directory) and
    +     * disallow the device root (000000 directory).  Symbolic paths (e.g.,
    +     * sys$help) should have been translated and expanded (e.g., to
    +     * /sys$sysroot/syshlp) before calling this routine.
    +     */
    +    if (((*pathname != '/') ||
    +	 (cp = StrChr(pathname + 1, '/')) == NULL ||
    +	 *(cp + 1) == '\0' ||
    +	 0 == StrNCmp((cp + 1), "000000", 6)) ||
    +	(dp = HTVMSopendir(pathname)) == NULL) {
    +	FREE(pathname);
    +	return HTLoadError(sink, 403, COULD_NOT_ACCESS_DIR);
    +    }
    +
    +    /*
    +     * Set up the output stream.
    +     */
    +    _HTProgress(BUILDING_DIR_LIST);
    +    if (UCLYhndl_HTFile_for_unspec >= 0) {
    +	HTAnchor_setUCInfoStage(anchor,
    +				UCLYhndl_HTFile_for_unspec,
    +				UCT_STAGE_PARSER,
    +				UCT_SETBY_DEFAULT);
    +    }
    +    target = HTML_new(anchor, format_out, sink);
    +    targetClass = *(target->isa);
    +
    +    /*
    +     * Set up the offset string of the anchor reference, and strings for the
    +     * title and header.
    +     */
    +    cp = strrchr(pathname, '/');	/* find lastslash */
    +    StrAllocCopy(tail, (cp + 1));	/* take slash off the beginning */
    +    if (*tail != '\0') {
    +	StrAllocCopy(title, tail);
    +	*cp = '\0';
    +	if ((cp1 = strrchr(pathname, '/')) != NULL &&
    +	    cp1 != pathname &&
    +	    StrNCmp((cp1 + 1), "000000", 6))
    +	    StrAllocCopy(parent, (cp1 + 1));
    +	*cp = '/';
    +    } else {
    +	pathname[strlen(pathname) - 1] = '\0';
    +	cp = strrchr(pathname, '/');
    +	StrAllocCopy(title, (cp + 1));
    +	pathname[strlen(pathname)] = '/';
    +    }
    +    StrAllocCopy(header, pathname);
    +
    +    /*
    +     * Initialize path name for HTStat().
    +     */
    +    pathend = strlen(pathname);
    +    if (*(pathname + pathend - 1) != '/') {
    +	StrAllocCat(pathname, "/");
    +	pathend++;
    +    }
    +
    +    /*
    +     * Output the title and header.
    +     */
    +    START(HTML_HTML);
    +    PUTC('\n');
    +    START(HTML_HEAD);
    +    PUTC('\n');
    +    HTUnEscape(title);
    +    START(HTML_TITLE);
    +    PUTS(title);
    +    PUTS(" directory");
    +    END(HTML_TITLE);
    +    PUTC('\n');
    +    FREE(title);
    +    END(HTML_HEAD);
    +    PUTC('\n');
    +    START(HTML_BODY);
    +    PUTC('\n');
    +    HTUnEscape(header);
    +    START(HTML_H1);
    +    PUTS(header);
    +    END(HTML_H1);
    +    PUTC('\n');
    +    if (HTDirReadme == HT_DIR_README_TOP) {
    +	FILE *fp;
    +
    +	if (header[strlen(header) - 1] != '/')
    +	    StrAllocCat(header, "/");
    +	StrAllocCat(header, HT_DIR_README_FILE);
    +	if ((fp = fopen(header, "r")) != NULL) {
    +	    START(HTML_PRE);
    +	    for (;;) {
    +		char c = fgetc(fp);
    +
    +		if (c == (char) EOF)
    +		    break;
    +#ifdef NOTDEFINED
    +		switch (c) {
    +		case '&':
    +		case '<':
    +		case '>':
    +		    PUTC('&');
    +		    PUTC('#');
    +		    PUTC((char) (c / 10));
    +		    PUTC((char) (c % 10));
    +		    PUTC(';');
    +		    break;
    +		default:
    +		    PUTC(c);
    +		}
    +#else
    +		PUTC(c);
    +#endif /* NOTDEFINED */
    +	    }
    +	    END(HTML_PRE);
    +	    fclose(fp);
    +	}
    +    }
    +    FREE(header);
    +    if (parent) {
    +	HTSprintf0(&relative, "%s/..", tail);
    +	HTStartAnchor(target, "", relative);
    +	PUTS("Up to ");
    +	HTUnEscape(parent);
    +	PUTS(parent);
    +	END(HTML_A);
    +	START(HTML_P);
    +	PUTC('\n');
    +	FREE(relative);
    +	FREE(parent);
    +    }
    +
    +    /*
    +     * Set up the date comparison.
    +     */
    +    NowTime = time(NULL);
    +    strcpy(ThisYear, (char *) ctime(&NowTime) + 20);
    +    ThisYear[4] = '\0';
    +
    +    /*
    +     * Now, generate the Btree and put it out to the output stream.
    +     */
    +    {
    +	char dottest = 2;	/* To avoid two strcmp() each time */
    +	STRUCT_DIRENT *dirbuf;
    +	HTBTree *bt;
    +
    +	/* Set up sort key and initialize BTree */
    +	bt = HTBTree_new((HTComparer) compare_VMSEntryInfo_structs);
    +
    +	/* Build tree */
    +	while ((dirbuf = HTVMSreaddir(dp))) {
    +	    HTAtom *encoding = NULL;
    +	    HTFormat format;
    +
    +	    /* Skip if not used */
    +	    if (!dirbuf->d_ino) {
    +		continue;
    +	    }
    +
    +	    /* Current and parent directories are never shown in list */
    +	    if (dottest && (!strcmp(dirbuf->d_name, ".") ||
    +			    !strcmp(dirbuf->d_name, ".."))) {
    +		dottest--;
    +		continue;
    +	    }
    +
    +	    /* Don't show the selective enabling file
    +	     * unless version numbers are included */
    +	    if (!strcasecomp(dirbuf->d_name, HT_DIR_ENABLE_FILE)) {
    +		continue;
    +	    }
    +
    +	    /* Skip files beginning with a dot? */
    +	    if ((no_dotfiles || !show_dotfiles) && *dirbuf->d_name == '.') {
    +		continue;
    +	    }
    +
    +	    /* OK, make an lstat() and get a key ready. */
    +	    *(pathname + pathend) = '\0';
    +	    StrAllocCat(pathname, dirbuf->d_name);
    +	    if (HTStat(pathname, &file_info)) {
    +		/* for VMS the failure here means the file is not readable...
    +		   we however continue to browse through the directory... */
    +		continue;
    +	    }
    +	    entry_info = (VMSEntryInfo *) malloc(sizeof(VMSEntryInfo));
    +	    if (entry_info == NULL)
    +		outofmem(__FILE__, "HTVMSBrowseDir");
    +	    entry_info->type = 0;
    +	    entry_info->size = 0;
    +	    entry_info->date = 0;
    +	    entry_info->filename = 0;
    +	    entry_info->display = TRUE;
    +
    +	    /* Get the type */
    +	    format = HTFileFormat(dirbuf->d_name, &encoding,
    +				  (const char **) &cp);
    +	    if (!cp) {
    +		if (!StrNCmp(HTAtom_name(format), "application", 11)) {
    +		    cp = HTAtom_name(format) + 12;
    +		    if (!StrNCmp(cp, "x-", 2))
    +			cp += 2;
    +		} else
    +		    cp = HTAtom_name(format);
    +	    }
    +	    StrAllocCopy(entry_info->type, cp);
    +
    +	    StrAllocCopy(entry_info->filename, dirbuf->d_name);
    +	    if (S_ISDIR(file_info.st_mode)) {
    +		/* strip .DIR part... */
    +		char *dot;
    +
    +		dot = strstr(entry_info->filename, ".DIR");
    +		if (dot)
    +		    *dot = '\0';
    +		LYLowerCase(entry_info->filename);
    +		StrAllocCopy(entry_info->type, "Directory");
    +	    } else {
    +		if ((cp = strstr(entry_info->filename, "READ")) == NULL) {
    +		    cp = entry_info->filename;
    +		} else {
    +		    cp += 4;
    +		    if (!StrNCmp(cp, "ME", 2)) {
    +			cp += 2;
    +			while (cp && *cp && *cp != '.') {
    +			    cp++;
    +			}
    +		    } else if (!StrNCmp(cp, ".ME", 3)) {
    +			cp = (entry_info->filename +
    +			      strlen(entry_info->filename));
    +		    } else {
    +			cp = entry_info->filename;
    +		    }
    +		}
    +		LYLowerCase(cp);
    +		if (((len = strlen(entry_info->filename)) > 2) &&
    +		    entry_info->filename[len - 1] == 'z') {
    +		    if (entry_info->filename[len - 2] == '.' ||
    +			entry_info->filename[len - 2] == '_')
    +			entry_info->filename[len - 1] = 'Z';
    +		}
    +	    }
    +
    +	    /* Get the date */
    +	    {
    +		char *t = (char *) ctime((const time_t *) &file_info.st_ctime);
    +
    +		*(t + 24) = '\0';
    +
    +		StrAllocCopy(entry_info->date, (t + 4));
    +		*((entry_info->date) + 7) = '\0';
    +		if ((atoi((t + 19))) < atoi(ThisYear))
    +		    StrAllocCat(entry_info->date, (t + 19));
    +		else {
    +		    StrAllocCat(entry_info->date, (t + 11));
    +		    *((entry_info->date) + 12) = '\0';
    +		}
    +	    }
    +
    +	    /* Get the size */
    +	    if (!S_ISDIR(file_info.st_mode))
    +		entry_info->size = (unsigned int) file_info.st_size;
    +	    else
    +		entry_info->size = 0;
    +
    +	    /* Now, update the BTree etc. */
    +	    if (entry_info->display) {
    +		CTRACE((tfp, "Adding file to BTree: %s\n",
    +			entry_info->filename));
    +		HTBTree_add(bt, entry_info);
    +	    }
    +
    +	}			/* End while HTVMSreaddir() */
    +
    +	FREE(pathname);
    +	HTVMSclosedir(dp);
    +
    +	START(HTML_PRE);
    +	/*
    +	 * Run through the BTree printing out in order
    +	 */
    +	{
    +	    HTBTElement *ele;
    +	    int i;
    +
    +	    for (ele = HTBTree_next(bt, NULL);
    +		 ele != NULL;
    +		 ele = HTBTree_next(bt, ele)) {
    +		entry_info = (VMSEntryInfo *) HTBTree_object(ele);
    +
    +		/* Output the date */
    +		if (entry_info->date) {
    +		    PUTS(entry_info->date);
    +		    PUTS("  ");
    +		} else
    +		    PUTS("     * ");
    +
    +		/* Output the type */
    +		if (entry_info->type) {
    +		    for (i = 0; entry_info->type[i] != '\0' && i < 15; i++)
    +			PUTC(entry_info->type[i]);
    +		    for (; i < 17; i++)
    +			PUTC(' ');
    +
    +		}
    +
    +		/* Output the link for the name */
    +		HTDirEntry(target, tail, entry_info->filename);
    +		PUTS(entry_info->filename);
    +		END(HTML_A);
    +
    +		/* Output the size */
    +		if (entry_info->size) {
    +		    if (entry_info->size < 1024)
    +			sprintf(string_buffer, "  %d bytes",
    +				entry_info->size);
    +		    else
    +			sprintf(string_buffer, "  %dKb",
    +				entry_info->size / 1024);
    +		    PUTS(string_buffer);
    +		}
    +
    +		PUTC('\n');	/* end of this entry */
    +
    +		free_VMSEntryInfo_contents(entry_info);
    +	    }
    +	}
    +
    +	HTBTreeAndObject_free(bt);
    +
    +    }				/* End of both BTree loops */
    +
    +    /*
    +     * Complete the output stream.
    +     */
    +    END(HTML_PRE);
    +    PUTC('\n');
    +    END(HTML_BODY);
    +    PUTC('\n');
    +    END(HTML_HTML);
    +    PUTC('\n');
    +    FREE(tail);
    +    FREE_TARGET;
    +
    +    return HT_LOADED;
    +
    +}				/* End of directory reading section */
    +
    +/*
    + * Remove all versions of the given file.  We assume there are no permissions
    + * problems, since we do this mainly for removing temporary files.
    + */
    +int HTVMS_remove(char *filename)
    +{
    +    int code = remove(filename);	/* return the first status code */
    +
    +    while (remove(filename) == 0) ;
    +    return code;
    +}
    +
    +/*
    + * Remove all older versions of the given file.  We may fail to remove some
    + * version due to permissions -- the loop stops either at that point, or when
    + * we run out of older versions to remove.
    + */
    +void HTVMS_purge(char *filename)
    +{
    +    char *older_file = 0;
    +    char *oldest_file = 0;
    +    struct stat sb;
    +
    +    StrAllocCopy(older_file, filename);
    +    StrAllocCat(older_file, ";-1");
    +
    +    while (remove(older_file) == 0) ;
    +    /*
    +     * If we do not have any more older versions, it is safe to rename the
    +     * current file to version #1.
    +     */
    +    if (stat(older_file, &sb) != 0) {
    +	StrAllocCopy(oldest_file, filename);
    +	StrAllocCat(oldest_file, ";1");
    +	rename(older_file, oldest_file);
    +	FREE(oldest_file);
    +    }
    +
    +    FREE(older_file);
    +}
    +#endif /* VMS */
    diff --git a/WWW/Library/Implementation/HTVMSUtils.h b/WWW/Library/Implementation/HTVMSUtils.h
    new file mode 100644
    index 0000000..d7efe8c
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMSUtils.h
    @@ -0,0 +1,101 @@
    +/*             VMS specific routines
    +
    + */
    +
    +#ifndef HTVMSUTIL_H
    +#define HTVMSUTIL_H
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#include <HTAnchor.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern BOOL HTVMSFileVersions;	/* Include version numbers in listing? */
    +
    +/* PUBLIC							HTVMS_authSysPrv()
    + *		CHECKS IF THIS PROCESS IS AUTHORIZED TO ENABLE SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *	returns	YES if SYSPRV is authorized
    + */
    +    extern BOOL HTVMS_authSysPrv(void);
    +
    +/* PUBLIC							HTVMS_enableSysPrv()
    + *		ENABLES SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *
    + */
    +    extern void HTVMS_enableSysPrv(void);
    +
    +/* PUBLIC							HTVMS_disableSysPrv()
    + *		DISABLES SYSPRV
    + * ON ENTRY:
    + *	No arguments.
    + *
    + * ON EXIT:
    + *
    + */
    +    extern void HTVMS_disableSysPrv(void);
    +
    +/* PUBLIC							HTVMS_checkAccess()
    + *		CHECKS ACCESS TO FILE FOR CERTAIN USER
    + * ON ENTRY:
    + *	FileName	The file to be accessed
    + *	UserName	Name of the user to check access for
    + *
    + * ON EXIT:
    + *	returns YES if access is allowed
    + *
    + */
    +    extern BOOL HTVMS_checkAccess(const char *FileName,
    +				  const char *UserName,
    +				  const char *Method);
    +
    +/* PUBLIC							HTVMS_wwwName()
    + *		CONVERTS VMS Name into WWW Name
    + * ON ENTRY:
    + *	vmsname		VMS file specification (NO NODE)
    + *
    + * ON EXIT:
    + *	returns		www file specification
    + *
    + * EXAMPLES:
    + *	vmsname				wwwname
    + *	DISK$USER			disk$user
    + *	DISK$USER:			/disk$user/
    + *	DISK$USER:[DUNS]		/disk$user/duns
    + *	DISK$USER:[DUNS.ECHO]		/disk$user/duns/echo
    + *	[DUNS]				duns
    + *	[DUNS.ECHO]			duns/echo
    + *	[DUNS.ECHO.-.TRANS]		duns/echo/../trans
    + *	[DUNS.ECHO.--.TRANS]		duns/echo/../../trans
    + *	[.DUNS]				duns
    + *	[.DUNS.ECHO]			duns/echo
    + *	[.DUNS.ECHO]TEST.COM		duns/echo/test.com
    + *	TEST.COM			test.com
    + *
    + *
    + */
    +    const extern char *HTVMS_wwwName(const char *vmsname);
    +
    +    extern int HTVMSBrowseDir(const char *address,
    +			      HTParentAnchor *anchor,
    +			      HTFormat format_out,
    +			      HTStream *sink);
    +
    +    extern int HTVMS_remove(char *filename);
    +    extern void HTVMS_purge(char *filename);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* not HTVMSUTIL_H */
    diff --git a/WWW/Library/Implementation/HTVMS_WaisProt.c b/WWW/Library/Implementation/HTVMS_WaisProt.c
    new file mode 100644
    index 0000000..687c9df
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMS_WaisProt.c
    @@ -0,0 +1,2469 @@
    +/*
    + * $LynxId: HTVMS_WaisProt.c,v 1.10 2020/01/21 22:10:03 tom Exp $
    + *
    + *							  HTVMS_WAISProt.c
    + *
    + *	Adaptation for Lynx by F.Macrides (macrides@sci.wfeb.edu)
    + *
    + *	31-May-1994 FM	Initial version.
    + *
    + *----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from WProt.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    +
    + * 3.26.90	Harry Morris, morris@think.com
    + * 3.30.90  Harry Morris
    + * -	removed chunk code from WAISSearchAPDU,
    + * -	added makeWAISQueryType1Query() and readWAISType1Query() which replace
    + * makeWAISQueryTerms() and makeWAISQueryDocs().
    + * 4.11.90  HWM - generalized conditional includes (see c-dialect.h)
    + * - renamed makeWAISType1Query() to makeWAISTextQuery()
    + * renamed readWAISType1Query() to readWAISTextQuery()
    + * 5.29.90  TS - fixed bug in makeWAISQueryDocs
    + * added CSTFreeWAISFoo functions
    + */
    +
    +#define _C_WAIS_protocol_
    +
    +/*	This file implements the Z39.50 extensions required for WAIS
    +*/
    +
    +#include <HTUtils.h>
    +#include <HTVMS_WaisUI.h>
    +#include <HTVMS_WaisProt.h>
    +
    +#include <LYLeaks.h>
    +
    +/* very rough estimates of the size of an object */
    +#define DefWAISInitResponseSize		(size_t)200
    +#define DefWAISSearchSize			(size_t)3000
    +#define DefWAISSearchResponseSize	(size_t)6000
    +#define DefWAISPresentSize			(size_t)1000
    +#define DefWAISPresentResponseSize	(size_t)6000
    +#define DefWAISDocHeaderSize		(size_t)500
    +#define DefWAISShortHeaderSize		(size_t)200
    +#define DefWAISLongHeaderSize		(size_t)800
    +#define DefWAISDocTextSize			(size_t)6000
    +#define DefWAISDocHeadlineSize		(size_t)500
    +#define DefWAISDocCodeSize			(size_t)500
    +
    +#define RESERVE_SPACE_FOR_WAIS_HEADER(len)	\
    +     if (*len > 0)				\
    +	*len -= header_len;
    +
    +#define S_MALLOC(type) (type*)s_malloc(sizeof(type))
    +#define S_MALLOC2(type) (type*)s_malloc(sizeof(type) * 2)
    +
    +#define S_REALLOC2(type, ptr, num) (type*)s_realloc((char*)ptr, (sizeof(type) * (num + 2)))
    +
    +/*----------------------------------------------------------------------*/
    +
    +static unsigned long userInfoTagSize(data_tag tag,
    +				     unsigned long length)
    +/* return the number of bytes required to write the user info tag and
    +   length
    + */
    +{
    +    unsigned long size;
    +
    +    /* calculate bytes required to represent tag.  max tag is 16K */
    +    size = writtenCompressedIntSize(tag);
    +    size += writtenCompressedIntSize(length);
    +
    +    return (size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static char *writeUserInfoHeader(data_tag tag,
    +				 long infoSize,
    +				 long estHeaderSize,
    +				 char *buffer,
    +				 long *len)
    +/* write the tag and size, making sure the info fits.  return the true end
    +   of the info (after adjustment) note that the argument infoSize includes
    +   estHeaderSize.  Note that the argument len is the number of bytes remaining
    +   in the buffer.  Since we write the tag and size at the beginning of the
    +   buffer (in space that we reserved) we don't want to pass len the calls which
    +   do that writing.
    + */
    +{
    +    long dummyLen = 100;	/* plenty of space for a tag and size */
    +    char *buf = buffer;
    +    long realSize = infoSize - estHeaderSize;
    +    long realHeaderSize = userInfoTagSize(tag, realSize);
    +
    +    if (buffer == NULL || *len == 0)
    +	return (NULL);
    +
    +    /* write the tag */
    +    buf = writeTag(tag, buf, &dummyLen);
    +
    +    /* see if the if the header size was correct. if not,
    +       we have to shift the info to fit the real header size */
    +    if (estHeaderSize != realHeaderSize) {	/* make sure there is enough space */
    +	CHECK_FOR_SPACE_LEFT(realHeaderSize - estHeaderSize, len);
    +	memmove(buffer + realHeaderSize, buffer + estHeaderSize, (size_t) (realSize));
    +    }
    +
    +    /* write the size */
    +    writeCompressedInteger(realSize, buf, &dummyLen);
    +
    +    /* return the true end of buffer */
    +    return (buffer + realHeaderSize + realSize);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static char *readUserInfoHeader(data_tag *tag,
    +				unsigned long *num,
    +				char *buffer)
    +/* read the tag and size */
    +{
    +    char *buf = buffer;
    +
    +    buf = readTag(tag, buf);
    +    buf = readCompressedInteger(num, buf);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISInitResponse *makeWAISInitResponse(long chunkCode,
    +				       long chunkIDLen,
    +				       char *chunkMarker,
    +				       char *highlightMarker,
    +				       char *deHighlightMarker,
    +				       char *newLineChars)
    +/* create a WAIS init response object */
    +{
    +    WAISInitResponse *init = S_MALLOC(WAISInitResponse);
    +
    +    init->ChunkCode = chunkCode;	/* note: none are copied! */
    +    init->ChunkIDLength = chunkIDLen;
    +    init->ChunkMarker = chunkMarker;
    +    init->HighlightMarker = highlightMarker;
    +    init->DeHighlightMarker = deHighlightMarker;
    +    init->NewlineCharacters = newLineChars;
    +
    +    return (init);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISInitResponse(WAISInitResponse *init)
    +/* free an object made with makeWAISInitResponse */
    +{
    +    s_free(init->ChunkMarker);
    +    s_free(init->HighlightMarker);
    +    s_free(init->DeHighlightMarker);
    +    s_free(init->NewlineCharacters);
    +    s_free(init);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeInitResponseInfo(InitResponseAPDU *init,
    +			    char *buffer,
    +			    long *len)
    +/* write an init response object */
    +{
    +    unsigned long header_len = userInfoTagSize(DT_UserInformationLength,
    +					       DefWAISInitResponseSize);
    +    char *buf = buffer + header_len;
    +    WAISInitResponse *info = (WAISInitResponse *) init->UserInformationField;
    +    unsigned long size;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeNum(info->ChunkCode, DT_ChunkCode, buf, len);
    +    buf = writeNum(info->ChunkIDLength, DT_ChunkIDLength, buf, len);
    +    buf = writeString(info->ChunkMarker, DT_ChunkMarker, buf, len);
    +    buf = writeString(info->HighlightMarker, DT_HighlightMarker, buf, len);
    +    buf = writeString(info->DeHighlightMarker, DT_DeHighlightMarker, buf, len);
    +    buf = writeString(info->NewlineCharacters, DT_NewlineCharacters, buf, len);
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_UserInformationLength,
    +			      size,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readInitResponseInfo(void **info,
    +			   char *buffer)
    +/* read an init response object */
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    long chunkCode, chunkIDLen;
    +    data_tag tag1;
    +    char *chunkMarker = NULL;
    +    char *highlightMarker = NULL;
    +    char *deHighlightMarker = NULL;
    +    char *newLineChars = NULL;
    +
    +    chunkCode = chunkIDLen = UNUSED;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_ChunkCode:
    +	    buf = readNum(&chunkCode, buf);
    +	    break;
    +	case DT_ChunkIDLength:
    +	    buf = readNum(&chunkIDLen, buf);
    +	    break;
    +	case DT_ChunkMarker:
    +	    buf = readString(&chunkMarker, buf);
    +	    break;
    +	case DT_HighlightMarker:
    +	    buf = readString(&highlightMarker, buf);
    +	    break;
    +	case DT_DeHighlightMarker:
    +	    buf = readString(&deHighlightMarker, buf);
    +	    break;
    +	case DT_NewlineCharacters:
    +	    buf = readString(&newLineChars, buf);
    +	    break;
    +	default:
    +	    s_free(highlightMarker);
    +	    s_free(deHighlightMarker);
    +	    s_free(newLineChars);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *info = (void *) makeWAISInitResponse(chunkCode, chunkIDLen, chunkMarker,
    +					  highlightMarker, deHighlightMarker,
    +					  newLineChars);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISSearch *makeWAISSearch(char *seedWords,
    +			   DocObj **docs,
    +			   char **textList,
    +			   long dateFactor,
    +			   char *beginDateRange,
    +			   char *endDateRange,
    +			   long maxDocsRetrieved)
    +
    +/* create a type 3 query object */
    +{
    +    WAISSearch *query = S_MALLOC(WAISSearch);
    +
    +    query->SeedWords = seedWords;	/* not copied! */
    +    query->Docs = docs;		/* not copied! */
    +    query->TextList = textList;	/* not copied! */
    +    query->DateFactor = dateFactor;
    +    query->BeginDateRange = beginDateRange;
    +    query->EndDateRange = endDateRange;
    +    query->MaxDocumentsRetrieved = maxDocsRetrieved;
    +
    +    return (query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISSearch(WAISSearch *query)
    +
    +/* destroy an object made with makeWAISSearch() */
    +{
    +    void *ptr = NULL;
    +    long i;
    +
    +    s_free(query->SeedWords);
    +
    +    if (query->Docs != NULL)
    +	for (i = 0, ptr = (void *) query->Docs[i];
    +	     ptr != NULL;
    +	     ptr = (void *) query->Docs[++i])
    +	    freeDocObj((DocObj *) ptr);
    +    s_free(query->Docs);
    +
    +    if (query->TextList != NULL)	/* XXX revisit when textlist is fully defined */
    +	for (i = 0, ptr = (void *) query->TextList[i];
    +	     ptr != NULL;
    +	     ptr = (void *) query->TextList[++i])
    +	    s_free(ptr);
    +    s_free(query->TextList);
    +
    +    s_free(query->BeginDateRange);
    +    s_free(query->EndDateRange);
    +    s_free(query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +DocObj *makeDocObjUsingWholeDocument(any *docID,
    +				     char *type)
    +
    +/* construct a document object using byte chunks - only for use by
    +   servers */
    +{
    +    DocObj *doc = S_MALLOC(DocObj);
    +
    +    doc->DocumentID = docID;	/* not copied! */
    +    doc->Type = type;		/* not copied! */
    +    doc->ChunkCode = CT_document;
    +    return (doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +DocObj *makeDocObjUsingLines(any *docID,
    +			     char *type,
    +			     long start,
    +			     long end)
    +
    +/* construct a document object using line chunks - only for use by
    +   servers */
    +{
    +    DocObj *doc = S_MALLOC(DocObj);
    +
    +    doc->ChunkCode = CT_line;
    +    doc->DocumentID = docID;	/* not copied */
    +    doc->Type = type;		/* not copied! */
    +    doc->ChunkStart.Pos = start;
    +    doc->ChunkEnd.Pos = end;
    +    return (doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +DocObj *makeDocObjUsingBytes(any *docID,
    +			     char *type,
    +			     long start,
    +			     long end)
    +
    +/* construct a document object using byte chunks - only for use by
    +   servers */
    +{
    +    DocObj *doc = S_MALLOC(DocObj);
    +
    +    doc->ChunkCode = CT_byte;
    +    doc->DocumentID = docID;	/* not copied */
    +    doc->Type = type;		/* not copied! */
    +    doc->ChunkStart.Pos = start;
    +    doc->ChunkEnd.Pos = end;
    +    return (doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +DocObj *makeDocObjUsingParagraphs(any *docID,
    +				  char *type,
    +				  any *start,
    +				  any *end)
    +
    +/* construct a document object using byte chunks - only for use by
    +   servers */
    +{
    +    DocObj *doc = S_MALLOC(DocObj);
    +
    +    doc->ChunkCode = CT_paragraph;
    +    doc->DocumentID = docID;	/* not copied */
    +    doc->Type = type;
    +    doc->ChunkStart.ID = start;
    +    doc->ChunkEnd.ID = end;
    +    return (doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeDocObj(DocObj *doc)
    +
    +/* free a docObj */
    +{
    +    freeAny(doc->DocumentID);
    +    s_free(doc->Type);
    +    if (doc->ChunkCode == CT_paragraph) {
    +	freeAny(doc->ChunkStart.ID);
    +	freeAny(doc->ChunkEnd.ID);
    +    }
    +    s_free(doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static char *writeDocObj(DocObj *doc,
    +			 char *buffer,
    +			 long *len)
    +
    +/* write as little as we can about the doc obj */
    +{
    +    char *buf = buffer;
    +
    +    /* we always have to write the id, but its tag depends on if it's a chunk */
    +    if (doc->ChunkCode == CT_document)
    +	buf = writeAny(doc->DocumentID, DT_DocumentID, buf, len);
    +    else
    +	buf = writeAny(doc->DocumentID, DT_DocumentIDChunk, buf, len);
    +
    +    if (doc->Type != NULL)
    +	buf = writeString(doc->Type, DT_TYPE, buf, len);
    +
    +    switch (doc->ChunkCode) {
    +    case CT_document:
    +	/* do nothing - there is no chunk data */
    +	break;
    +    case CT_byte:
    +    case CT_line:
    +	buf = writeNum(doc->ChunkCode, DT_ChunkCode, buf, len);
    +	buf = writeNum(doc->ChunkStart.Pos, DT_ChunkStartID, buf, len);
    +	buf = writeNum(doc->ChunkEnd.Pos, DT_ChunkEndID, buf, len);
    +	break;
    +    case CT_paragraph:
    +	buf = writeNum(doc->ChunkCode, DT_ChunkCode, buf, len);
    +	buf = writeAny(doc->ChunkStart.ID, DT_ChunkStartID, buf, len);
    +	buf = writeAny(doc->ChunkEnd.ID, DT_ChunkEndID, buf, len);
    +	break;
    +    default:
    +	panic("Implementation error: unknown chuck type %ld",
    +	      doc->ChunkCode);
    +	break;
    +    }
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static char *readDocObj(DocObj **doc,
    +			char *buffer)
    +
    +/* read whatever we have about the new document */
    +{
    +    char *buf = buffer;
    +    data_tag tag;
    +
    +    *doc = S_MALLOC(DocObj);
    +
    +    tag = peekTag(buf);
    +    buf = readAny(&((*doc)->DocumentID), buf);
    +
    +    if (tag == DT_DocumentID) {
    +	(*doc)->ChunkCode = CT_document;
    +	tag = peekTag(buf);
    +	if (tag == DT_TYPE)	/* XXX depends on DT_TYPE != what comes next */
    +	    buf = readString(&((*doc)->Type), buf);
    +	/* ChunkStart and ChunkEnd are undefined */
    +    } else if (tag == DT_DocumentIDChunk) {
    +	boolean readParagraphs = false;		/* for cleanup */
    +
    +	tag = peekTag(buf);
    +	if (tag == DT_TYPE)	/* XXX depends on DT_TYPE != CT_FOO */
    +	    buf = readString(&((*doc)->Type), buf);
    +	buf = readNum(&((*doc)->ChunkCode), buf);
    +	switch ((*doc)->ChunkCode) {
    +	case CT_byte:
    +	case CT_line:
    +	    buf = readNum(&((*doc)->ChunkStart.Pos), buf);
    +	    buf = readNum(&((*doc)->ChunkEnd.Pos), buf);
    +	    break;
    +	case CT_paragraph:
    +	    buf = readAny(&((*doc)->ChunkStart.ID), buf);
    +	    buf = readAny(&((*doc)->ChunkEnd.ID), buf);
    +	    break;
    +	default:
    +	    freeAny((*doc)->DocumentID);
    +	    if (readParagraphs) {
    +		freeAny((*doc)->ChunkStart.ID);
    +		freeAny((*doc)->ChunkEnd.ID);
    +	    }
    +	    s_free(doc);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    } else {
    +	freeAny((*doc)->DocumentID);
    +	s_free(*doc);
    +	REPORT_READ_ERROR(buf);
    +    }
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeSearchInfo(SearchAPDU *query,
    +		      char *buffer,
    +		      long *len)
    +
    +/* write out a WAIS query (type 1 or 3) */
    +{
    +    if (strcmp(query->QueryType, QT_TextRetrievalQuery) == 0) {
    +	return (writeAny((any *) query->Query, DT_Query, buffer, len));
    +    } else {
    +	unsigned long header_len = userInfoTagSize(DT_UserInformationLength,
    +						   DefWAISSearchSize);
    +	char *buf = buffer + header_len;
    +	WAISSearch *info = (WAISSearch *) query->Query;
    +	unsigned long size;
    +	long i;
    +
    +	RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +	buf = writeString(info->SeedWords, DT_SeedWords, buf, len);
    +
    +	if (info->Docs != NULL) {
    +	    for (i = 0; info->Docs[i] != NULL; i++) {
    +		buf = writeDocObj(info->Docs[i], buf, len);
    +	    }
    +	}
    +
    +	/* XXX text list */
    +
    +	buf = writeNum(info->DateFactor,
    +		       DT_DateFactor,
    +		       buf,
    +		       len);
    +	buf = writeString(info->BeginDateRange,
    +			  DT_BeginDateRange,
    +			  buf,
    +			  len);
    +	buf = writeString(info->EndDateRange,
    +			  DT_EndDateRange,
    +			  buf,
    +			  len);
    +	buf = writeNum(info->MaxDocumentsRetrieved,
    +		       DT_MaxDocumentsRetrieved,
    +		       buf,
    +		       len);
    +
    +	/* now write the header and size */
    +	size = buf - buffer;
    +	buf = writeUserInfoHeader(DT_UserInformationLength,
    +				  size,
    +				  header_len,
    +				  buffer,
    +				  len);
    +
    +	return (buf);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readSearchInfo(void **info,
    +		     char *buffer)
    +
    +/* read a WAIS query (type 1 or 3) */
    +{
    +    data_tag type = peekTag(buffer);
    +
    +    if (type == DT_Query)	/* this is a type 1 query */
    +    {
    +	char *buf = buffer;
    +	any *query = NULL;
    +
    +	buf = readAny(&query, buf);
    +	*info = (void *) query;
    +	return (buf);
    +    } else {			/* a type 3 query */
    +	char *buf = buffer;
    +	unsigned long size;
    +	unsigned long headerSize;
    +	data_tag tag1;
    +	char *seedWords = NULL;
    +	char *beginDateRange = NULL;
    +	char *endDateRange = NULL;
    +	long dateFactor, maxDocsRetrieved;
    +	char **textList = NULL;
    +	DocObj **docIDs = NULL;
    +	DocObj *doc = NULL;
    +	long docs = 0;
    +	long i;
    +	void *ptr = NULL;
    +
    +	dateFactor = maxDocsRetrieved = UNUSED;
    +
    +	buf = readUserInfoHeader(&tag1, &size, buf);
    +	headerSize = buf - buffer;
    +
    +	while (buf < (buffer + size + headerSize)) {
    +	    data_tag tag = peekTag(buf);
    +
    +	    switch (tag) {
    +	    case DT_SeedWords:
    +		buf = readString(&seedWords, buf);
    +		break;
    +	    case DT_DocumentID:
    +	    case DT_DocumentIDChunk:
    +		if (docIDs == NULL)	/* create a new doc list */
    +		{
    +		    docIDs = S_MALLOC2(DocObj *);
    +		} else {	/* grow the doc list */
    +		    docIDs = S_REALLOC2(DocObj *, docIDs, docs);
    +		}
    +		buf = readDocObj(&doc, buf);
    +		if (buf == NULL) {
    +		    s_free(seedWords);
    +		    s_free(beginDateRange);
    +		    s_free(endDateRange);
    +		    if (docIDs != NULL)
    +			for (i = 0, ptr = (void *) docIDs[i];
    +			     ptr != NULL;
    +			     ptr = (void *) docIDs[++i])
    +			    freeDocObj((DocObj *) ptr);
    +		    s_free(docIDs);
    +		    /* XXX should also free textlist when it is fully defined */
    +		}
    +		RETURN_ON_NULL(buf);
    +		docIDs[docs++] = doc;	/* put it in the list */
    +		docIDs[docs] = NULL;
    +		break;
    +	    case DT_TextList:
    +		/* XXX */
    +		break;
    +	    case DT_DateFactor:
    +		buf = readNum(&dateFactor, buf);
    +		break;
    +	    case DT_BeginDateRange:
    +		buf = readString(&beginDateRange, buf);
    +		break;
    +	    case DT_EndDateRange:
    +		buf = readString(&endDateRange, buf);
    +		break;
    +	    case DT_MaxDocumentsRetrieved:
    +		buf = readNum(&maxDocsRetrieved, buf);
    +		break;
    +	    default:
    +		s_free(seedWords);
    +		s_free(beginDateRange);
    +		s_free(endDateRange);
    +		if (docIDs != NULL)
    +		    for (i = 0, ptr = (void *) docIDs[i];
    +			 ptr != NULL;
    +			 ptr = (void *) docIDs[++i])
    +			freeDocObj((DocObj *) ptr);
    +		s_free(docIDs);
    +		/* XXX should also free textlist when it is fully defined */
    +		REPORT_READ_ERROR(buf);
    +		break;
    +	    }
    +	}
    +
    +	*info = (void *) makeWAISSearch(seedWords, docIDs, textList,
    +					dateFactor, beginDateRange, endDateRange,
    +					maxDocsRetrieved);
    +	return (buf);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentHeader *makeWAISDocumentHeader(any *docID,
    +					   long versionNumber,
    +					   long score,
    +					   long bestMatch,
    +					   long docLen,
    +					   long lines,
    +					   char **types,
    +					   char *source,
    +					   char *date,
    +					   char *headline,
    +					   char *originCity)
    +
    +/* construct a standard document header, note that no fields are copied!
    +   if the application needs to save these fields, it should copy them,
    +   or set the field in this object to NULL before freeing it.
    + */
    +{
    +    WAISDocumentHeader *header = S_MALLOC(WAISDocumentHeader);
    +
    +    header->DocumentID = docID;
    +    header->VersionNumber = versionNumber;
    +    header->Score = score;
    +    header->BestMatch = bestMatch;
    +    header->DocumentLength = docLen;
    +    header->Lines = lines;
    +    header->Types = types;
    +    header->Source = source;
    +    header->Date = date;
    +    header->Headline = headline;
    +    header->OriginCity = originCity;
    +
    +    return (header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentHeader(WAISDocumentHeader *header)
    +{
    +    freeAny(header->DocumentID);
    +    doList((void **) header->Types, fs_free);	/* can't use the macro here ! */
    +    s_free(header->Types);
    +    s_free(header->Source);
    +    s_free(header->Date);
    +    s_free(header->Headline);
    +    s_free(header->OriginCity);
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentHeader(WAISDocumentHeader *header, char *buffer,
    +			      long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentHeaderGroup,
    +					       DefWAISDocHeaderSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size1;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(header->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(header->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeNum(header->Score, DT_Score, buf, len);
    +    buf = writeNum(header->BestMatch, DT_BestMatch, buf, len);
    +    buf = writeNum(header->DocumentLength, DT_DocumentLength, buf, len);
    +    buf = writeNum(header->Lines, DT_Lines, buf, len);
    +    if (header->Types != NULL) {
    +	long size;
    +	char *ptr = NULL;
    +	long i;
    +
    +	buf = writeTag(DT_TYPE_BLOCK, buf, len);
    +	for (i = 0, size = 0, ptr = header->Types[i];
    +	     ptr != NULL;
    +	     ptr = header->Types[++i]) {
    +	    long typeSize = strlen(ptr);
    +
    +	    size += writtenTagSize(DT_TYPE);
    +	    size += writtenCompressedIntSize(typeSize);
    +	    size += typeSize;
    +	}
    +	buf = writeCompressedInteger((unsigned long) size, buf, len);
    +	for (i = 0, ptr = header->Types[i]; ptr != NULL; ptr = header->Types[++i])
    +	    buf = writeString(ptr, DT_TYPE, buf, len);
    +    }
    +    buf = writeString(header->Source, DT_Source, buf, len);
    +    buf = writeString(header->Date, DT_Date, buf, len);
    +    buf = writeString(header->Headline, DT_Headline, buf, len);
    +    buf = writeString(header->OriginCity, DT_OriginCity, buf, len);
    +
    +    /* now write the header and size */
    +    size1 = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentHeaderGroup,
    +			      size1,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentHeader(WAISDocumentHeader **header, char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size1;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID = NULL;
    +    long versionNumber, score, bestMatch, docLength, lines;
    +    char **types = NULL;
    +    char *source = NULL;
    +    char *date = NULL;
    +    char *headline = NULL;
    +    char *originCity = NULL;
    +
    +    versionNumber = score = bestMatch = docLength = lines = UNUSED;
    +
    +    buf = readUserInfoHeader(&tag1, &size1, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size1 + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_Score:
    +	    buf = readNum(&score, buf);
    +	    break;
    +	case DT_BestMatch:
    +	    buf = readNum(&bestMatch, buf);
    +	    break;
    +	case DT_DocumentLength:
    +	    buf = readNum(&docLength, buf);
    +	    break;
    +	case DT_Lines:
    +	    buf = readNum(&lines, buf);
    +	    break;
    +	case DT_TYPE_BLOCK:
    +	    {
    +		unsigned long size = -1;
    +		long numTypes = 0;
    +
    +		buf = readTag(&tag, buf);
    +		buf = readCompressedInteger(&size, buf);
    +		while (size > 0) {
    +		    char *type = NULL;
    +		    char *originalBuf = buf;
    +
    +		    buf = readString(&type, buf);
    +		    types = S_REALLOC2(char *, types, numTypes);
    +
    +		    types[numTypes++] = type;
    +		    types[numTypes] = NULL;
    +		    size -= (buf - originalBuf);
    +		}
    +	    }
    +	    /* FALLTHRU */
    +	case DT_Source:
    +	    buf = readString(&source, buf);
    +	    break;
    +	case DT_Date:
    +	    buf = readString(&date, buf);
    +	    break;
    +	case DT_Headline:
    +	    buf = readString(&headline, buf);
    +	    break;
    +	case DT_OriginCity:
    +	    buf = readString(&originCity, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    s_free(source);
    +	    s_free(date);
    +	    s_free(headline);
    +	    s_free(originCity);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *header = makeWAISDocumentHeader(docID, versionNumber, score, bestMatch,
    +				     docLength, lines, types, source, date, headline,
    +				     originCity);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentShortHeader *makeWAISDocumentShortHeader(any *docID,
    +						     long versionNumber,
    +						     long score,
    +						     long bestMatch,
    +						     long docLen,
    +						     long lines)
    +/* construct a short document header, note that no fields are copied!
    +   if the application needs to save these fields, it should copy them,
    +   or set the field in this object to NULL before freeing it.
    + */
    +{
    +    WAISDocumentShortHeader *header = S_MALLOC(WAISDocumentShortHeader);
    +
    +    header->DocumentID = docID;
    +    header->VersionNumber = versionNumber;
    +    header->Score = score;
    +    header->BestMatch = bestMatch;
    +    header->DocumentLength = docLen;
    +    header->Lines = lines;
    +
    +    return (header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentShortHeader(WAISDocumentShortHeader *header)
    +{
    +    freeAny(header->DocumentID);
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentShortHeader(WAISDocumentShortHeader *header, char *buffer,
    +				   long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentShortHeaderGroup,
    +					       DefWAISShortHeaderSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(header->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(header->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeNum(header->Score, DT_Score, buf, len);
    +    buf = writeNum(header->BestMatch, DT_BestMatch, buf, len);
    +    buf = writeNum(header->DocumentLength, DT_DocumentLength, buf, len);
    +    buf = writeNum(header->Lines, DT_Lines, buf, len);
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentShortHeaderGroup,
    +			      size,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentShortHeader(WAISDocumentShortHeader **header, char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID = NULL;
    +    long versionNumber, score, bestMatch, docLength, lines;
    +
    +    versionNumber = score = bestMatch = docLength = lines = UNUSED;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_Score:
    +	    buf = readNum(&score, buf);
    +	    break;
    +	case DT_BestMatch:
    +	    buf = readNum(&bestMatch, buf);
    +	    break;
    +	case DT_DocumentLength:
    +	    buf = readNum(&docLength, buf);
    +	    break;
    +	case DT_Lines:
    +	    buf = readNum(&lines, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *header = makeWAISDocumentShortHeader(docID, versionNumber, score, bestMatch,
    +					  docLength, lines);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentLongHeader *makeWAISDocumentLongHeader(any *docID,
    +						   long versionNumber,
    +						   long score,
    +						   long bestMatch,
    +						   long docLen,
    +						   long lines,
    +						   char **types,
    +						   char *source,
    +						   char *date,
    +						   char *headline,
    +						   char *originCity,
    +						   char *stockCodes,
    +						   char *companyCodes,
    +						   char *industryCodes)
    +/* construct a long document header, note that no fields are copied!
    +   if the application needs to save these fields, it should copy them,
    +   or set the field in this object to NULL before freeing it.
    + */
    +{
    +    WAISDocumentLongHeader *header = S_MALLOC(WAISDocumentLongHeader);
    +
    +    header->DocumentID = docID;
    +    header->VersionNumber = versionNumber;
    +    header->Score = score;
    +    header->BestMatch = bestMatch;
    +    header->DocumentLength = docLen;
    +    header->Lines = lines;
    +    header->Types = types;
    +    header->Source = source;
    +    header->Date = date;
    +    header->Headline = headline;
    +    header->OriginCity = originCity;
    +    header->StockCodes = stockCodes;
    +    header->CompanyCodes = companyCodes;
    +    header->IndustryCodes = industryCodes;
    +
    +    return (header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentLongHeader(WAISDocumentLongHeader *header)
    +{
    +    freeAny(header->DocumentID);
    +    doList((void **) header->Types, fs_free);	/* can't use the macro here! */
    +    s_free(header->Source);
    +    s_free(header->Date);
    +    s_free(header->Headline);
    +    s_free(header->OriginCity);
    +    s_free(header->StockCodes);
    +    s_free(header->CompanyCodes);
    +    s_free(header->IndustryCodes);
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentLongHeader(WAISDocumentLongHeader *header, char *buffer,
    +				  long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentLongHeaderGroup,
    +					       DefWAISLongHeaderSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size1;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(header->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(header->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeNum(header->Score, DT_Score, buf, len);
    +    buf = writeNum(header->BestMatch, DT_BestMatch, buf, len);
    +    buf = writeNum(header->DocumentLength, DT_DocumentLength, buf, len);
    +    buf = writeNum(header->Lines, DT_Lines, buf, len);
    +    if (header->Types != NULL) {
    +	long size;
    +	char *ptr = NULL;
    +	long i;
    +
    +	buf = writeTag(DT_TYPE_BLOCK, buf, len);
    +	for (i = 0, size = 0, ptr = header->Types[i];
    +	     ptr != NULL;
    +	     ptr = header->Types[++i]) {
    +	    long typeSize = strlen(ptr);
    +
    +	    size += writtenTagSize(DT_TYPE);
    +	    size += writtenCompressedIntSize(typeSize);
    +	    size += typeSize;
    +	}
    +	buf = writeCompressedInteger((unsigned long) size, buf, len);
    +	for (i = 0, ptr = header->Types[i]; ptr != NULL; ptr = header->Types[++i])
    +	    buf = writeString(ptr, DT_TYPE, buf, len);
    +    }
    +    buf = writeString(header->Source, DT_Source, buf, len);
    +    buf = writeString(header->Date, DT_Date, buf, len);
    +    buf = writeString(header->Headline, DT_Headline, buf, len);
    +    buf = writeString(header->OriginCity, DT_OriginCity, buf, len);
    +    buf = writeString(header->StockCodes, DT_StockCodes, buf, len);
    +    buf = writeString(header->CompanyCodes, DT_CompanyCodes, buf, len);
    +    buf = writeString(header->IndustryCodes, DT_IndustryCodes, buf, len);
    +
    +    /* now write the header and size */
    +    size1 = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentLongHeaderGroup,
    +			      size1,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentLongHeader(WAISDocumentLongHeader **header, char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size1;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID;
    +    long versionNumber, score, bestMatch, docLength, lines;
    +    char **types;
    +    char *source, *date, *headline, *originCity, *stockCodes, *companyCodes, *industryCodes;
    +
    +    docID = NULL;
    +    versionNumber =
    +	score =
    +	bestMatch =
    +	docLength =
    +	lines = UNUSED;
    +    types = NULL;
    +    source =
    +	date =
    +	headline =
    +	originCity =
    +	stockCodes =
    +	companyCodes =
    +	industryCodes = NULL;
    +
    +    buf = readUserInfoHeader(&tag1, &size1, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size1 + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_Score:
    +	    buf = readNum(&score, buf);
    +	    break;
    +	case DT_BestMatch:
    +	    buf = readNum(&bestMatch, buf);
    +	    break;
    +	case DT_DocumentLength:
    +	    buf = readNum(&docLength, buf);
    +	    break;
    +	case DT_Lines:
    +	    buf = readNum(&lines, buf);
    +	    break;
    +	case DT_TYPE_BLOCK:
    +	    {
    +		unsigned long size = -1;
    +		long numTypes = 0;
    +
    +		buf = readTag(&tag, buf);
    +		readCompressedInteger(&size, buf);
    +		while (size > 0) {
    +		    char *type = NULL;
    +		    char *originalBuf = buf;
    +
    +		    buf = readString(&type, buf);
    +		    types = S_REALLOC2(char *, types, numTypes);
    +
    +		    types[numTypes++] = type;
    +		    types[numTypes] = NULL;
    +		    size -= (buf - originalBuf);
    +		}
    +	    }
    +	    /* FALLTHRU */
    +	case DT_Source:
    +	    buf = readString(&source, buf);
    +	    break;
    +	case DT_Date:
    +	    buf = readString(&date, buf);
    +	    break;
    +	case DT_Headline:
    +	    buf = readString(&headline, buf);
    +	    break;
    +	case DT_OriginCity:
    +	    buf = readString(&originCity, buf);
    +	    break;
    +	case DT_StockCodes:
    +	    buf = readString(&stockCodes, buf);
    +	    break;
    +	case DT_CompanyCodes:
    +	    buf = readString(&companyCodes, buf);
    +	    break;
    +	case DT_IndustryCodes:
    +	    buf = readString(&industryCodes, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    s_free(source);
    +	    s_free(date);
    +	    s_free(headline);
    +	    s_free(originCity);
    +	    s_free(stockCodes);
    +	    s_free(companyCodes);
    +	    s_free(industryCodes);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *header = makeWAISDocumentLongHeader(docID,
    +					 versionNumber,
    +					 score,
    +					 bestMatch,
    +					 docLength,
    +					 lines,
    +					 types,
    +					 source,
    +					 date,
    +					 headline,
    +					 originCity,
    +					 stockCodes,
    +					 companyCodes,
    +					 industryCodes);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISSearchResponse *
    +  makeWAISSearchResponse(
    +			    char *seedWordsUsed,
    +			    WAISDocumentHeader **docHeaders,
    +			    WAISDocumentShortHeader **shortHeaders,
    +			    WAISDocumentLongHeader **longHeaders,
    +			    WAISDocumentText **text,
    +			    WAISDocumentHeadlines **headlines,
    +			    WAISDocumentCodes **codes,
    +			    diagnosticRecord ** diagnostics)
    +{
    +    WAISSearchResponse *response = S_MALLOC(WAISSearchResponse);
    +
    +    response->SeedWordsUsed = seedWordsUsed;
    +    response->DocHeaders = docHeaders;
    +    response->ShortHeaders = shortHeaders;
    +    response->LongHeaders = longHeaders;
    +    response->Text = text;
    +    response->Headlines = headlines;
    +    response->Codes = codes;
    +    response->Diagnostics = diagnostics;
    +
    +    return (response);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISSearchResponse(WAISSearchResponse * response)
    +{
    +    void *ptr = NULL;
    +    long i;
    +
    +    s_free(response->SeedWordsUsed);
    +
    +    if (response->DocHeaders != NULL)
    +	for (i = 0, ptr = (void *) response->DocHeaders[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->DocHeaders[++i])
    +	    freeWAISDocumentHeader((WAISDocumentHeader *) ptr);
    +    s_free(response->DocHeaders);
    +
    +    if (response->ShortHeaders != NULL)
    +	for (i = 0, ptr = (void *) response->ShortHeaders[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->ShortHeaders[++i])
    +	    freeWAISDocumentShortHeader((WAISDocumentShortHeader *) ptr);
    +    s_free(response->ShortHeaders);
    +
    +    if (response->LongHeaders != NULL)
    +	for (i = 0, ptr = (void *) response->LongHeaders[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->LongHeaders[++i])
    +	    freeWAISDocumentLongHeader((WAISDocumentLongHeader *) ptr);
    +    s_free(response->LongHeaders);
    +
    +    if (response->Text != NULL)
    +	for (i = 0, ptr = (void *) response->Text[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->Text[++i])
    +	    freeWAISDocumentText((WAISDocumentText *) ptr);
    +    s_free(response->Text);
    +
    +    if (response->Headlines != NULL)
    +	for (i = 0, ptr = (void *) response->Headlines[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->Headlines[++i])
    +	    freeWAISDocumentHeadlines((WAISDocumentHeadlines *) ptr);
    +    s_free(response->Headlines);
    +
    +    if (response->Codes != NULL)
    +	for (i = 0, ptr = (void *) response->Codes[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->Codes[++i])
    +	    freeWAISDocumentCodes((WAISDocumentCodes *) ptr);
    +    s_free(response->Codes);
    +
    +    if (response->Diagnostics != NULL)
    +	for (i = 0, ptr = (void *) response->Diagnostics[i];
    +	     ptr != NULL;
    +	     ptr = (void *) response->Diagnostics[++i])
    +	    freeDiag((diagnosticRecord *) ptr);
    +    s_free(response->Diagnostics);
    +
    +    s_free(response);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeSearchResponseInfo(SearchResponseAPDU *query,
    +			      char *buffer,
    +			      long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_UserInformationLength,
    +					       DefWAISSearchResponseSize);
    +    char *buf = buffer + header_len;
    +    WAISSearchResponse *info = (WAISSearchResponse *) query->DatabaseDiagnosticRecords;
    +    unsigned long size;
    +    void *header = NULL;
    +    long i;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeString(info->SeedWordsUsed, DT_SeedWordsUsed, buf, len);
    +
    +    /* write out all the headers */
    +    if (info->DocHeaders != NULL) {
    +	for (i = 0, header = (void *) info->DocHeaders[i];
    +	     header != NULL;
    +	     header = (void *) info->DocHeaders[++i])
    +	    buf = writeWAISDocumentHeader((WAISDocumentHeader *) header, buf, len);
    +    }
    +
    +    if (info->ShortHeaders != NULL) {
    +	for (i = 0, header = (void *) info->ShortHeaders[i];
    +	     header != NULL;
    +	     header = (void *) info->ShortHeaders[++i])
    +	    buf = writeWAISDocumentShortHeader((WAISDocumentShortHeader *) header,
    +					       buf,
    +					       len);
    +    }
    +
    +    if (info->LongHeaders != NULL) {
    +	for (i = 0, header = (void *) info->LongHeaders[i];
    +	     header != NULL;
    +	     header = (void *) info->LongHeaders[++i])
    +	    buf = writeWAISDocumentLongHeader((WAISDocumentLongHeader *) header,
    +					      buf,
    +					      len);
    +    }
    +
    +    if (info->Text != NULL) {
    +	for (i = 0, header = (void *) info->Text[i];
    +	     header != NULL;
    +	     header = (void *) info->Text[++i])
    +	    buf = writeWAISDocumentText((WAISDocumentText *) header, buf, len);
    +    }
    +
    +    if (info->Headlines != NULL) {
    +	for (i = 0, header = (void *) info->Headlines[i];
    +	     header != NULL;
    +	     header = (void *) info->Headlines[++i])
    +	    buf = writeWAISDocumentHeadlines((WAISDocumentHeadlines *) header,
    +					     buf,
    +					     len);
    +    }
    +
    +    if (info->Codes != NULL) {
    +	for (i = 0, header = (void *) info->Codes[i];
    +	     header != NULL;
    +	     header = (void *) info->Codes[++i])
    +	    buf = writeWAISDocumentCodes((WAISDocumentCodes *) header, buf, len);
    +    }
    +
    +    if (info->Diagnostics != NULL) {
    +	for (i = 0, header = (void *) info->Diagnostics[i];
    +	     header != NULL;
    +	     header = (void *) info->Diagnostics[++i])
    +	    buf = writeDiag((diagnosticRecord *) header, buf, len);
    +    }
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_UserInformationLength,
    +			      size,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static void cleanUpWaisSearchResponse(char *buf,
    +				      char *seedWordsUsed,
    +				      WAISDocumentHeader **docHeaders,
    +				      WAISDocumentShortHeader **shortHeaders,
    +				      WAISDocumentLongHeader **longHeaders,
    +				      WAISDocumentText **text,
    +				      WAISDocumentHeadlines **headlines,
    +				      WAISDocumentCodes **codes,
    +				      diagnosticRecord ** diags)
    +/* if buf is NULL, we have just gotten a read error, and need to clean up
    +   any state we have built.  If not, then everything is going fine, and
    +   we should just hang loose
    + */
    +{
    +    void *ptr = NULL;
    +    long i;
    +
    +    if (buf == NULL) {
    +	s_free(seedWordsUsed);
    +	if (docHeaders != NULL)
    +	    for (i = 0, ptr = (void *) docHeaders[i]; ptr != NULL;
    +		 ptr = (void *) docHeaders[++i])
    +		freeWAISDocumentHeader((WAISDocumentHeader *) ptr);
    +	s_free(docHeaders);
    +	if (shortHeaders != NULL)
    +	    for (i = 0, ptr = (void *) shortHeaders[i]; ptr != NULL;
    +		 ptr = (void *) shortHeaders[++i])
    +		freeWAISDocumentShortHeader((WAISDocumentShortHeader *) ptr);
    +	s_free(shortHeaders);
    +	if (longHeaders != NULL)
    +	    for (i = 0, ptr = (void *) longHeaders[i]; ptr != NULL;
    +		 ptr = (void *) longHeaders[++i])
    +		freeWAISDocumentLongHeader((WAISDocumentLongHeader *) ptr);
    +	s_free(longHeaders);
    +	if (text != NULL)
    +	    for (i = 0, ptr = (void *) text[i];
    +		 ptr != NULL;
    +		 ptr = (void *) text[++i])
    +		freeWAISDocumentText((WAISDocumentText *) ptr);
    +	s_free(text);
    +	if (headlines != NULL)
    +	    for (i = 0, ptr = (void *) headlines[i]; ptr != NULL;
    +		 ptr = (void *) headlines[++i])
    +		freeWAISDocumentHeadlines((WAISDocumentHeadlines *) ptr);
    +	s_free(headlines);
    +	if (codes != NULL)
    +	    for (i = 0, ptr = (void *) codes[i]; ptr != NULL;
    +		 ptr = (void *) codes[++i])
    +		freeWAISDocumentCodes((WAISDocumentCodes *) ptr);
    +	s_free(codes);
    +	if (diags != NULL)
    +	    for (i = 0, ptr = (void *) diags[i]; ptr != NULL;
    +		 ptr = (void *) diags[++i])
    +		freeDiag((diagnosticRecord *) ptr);
    +	s_free(diags);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readSearchResponseInfo(void **info,
    +			     char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    void *header = NULL;
    +    WAISDocumentHeader **docHeaders = NULL;
    +    WAISDocumentShortHeader **shortHeaders = NULL;
    +    WAISDocumentLongHeader **longHeaders = NULL;
    +    WAISDocumentText **text = NULL;
    +    WAISDocumentHeadlines **headlines = NULL;
    +    WAISDocumentCodes **codes = NULL;
    +    long numDocHeaders, numLongHeaders, numShortHeaders, numText, numHeadlines;
    +    long numCodes;
    +    char *seedWordsUsed = NULL;
    +    diagnosticRecord **diags = NULL;
    +    diagnosticRecord *diag = NULL;
    +    long numDiags = 0;
    +
    +    numDocHeaders =
    +	numLongHeaders =
    +	numShortHeaders =
    +	numText =
    +	numHeadlines =
    +	numCodes = 0;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_SeedWordsUsed:
    +	    buf = readString(&seedWordsUsed, buf);
    +	    break;
    +	case DT_DatabaseDiagnosticRecords:
    +	    if (diags == NULL)	/* create a new diag list */
    +	    {
    +		diags = S_MALLOC2(diagnosticRecord *);
    +	    } else {		/* grow the diag list */
    +		diags = S_REALLOC2(diagnosticRecord *, diags, numDiags);
    +	    }
    +	    buf = readDiag(&diag, buf);
    +	    diags[numDiags++] = diag;	/* put it in the list */
    +	    diags[numDiags] = NULL;
    +	    break;
    +	case DT_DocumentHeaderGroup:
    +	    if (docHeaders == NULL)	/* create a new header list */
    +	    {
    +		docHeaders = S_MALLOC2(WAISDocumentHeader *);
    +	    } else {		/* grow the doc list */
    +		docHeaders = S_REALLOC2(WAISDocumentHeader *, docHeaders, numDocHeaders);
    +	    }
    +	    buf = readWAISDocumentHeader((WAISDocumentHeader **) &header, buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    docHeaders[numDocHeaders++] =
    +		(WAISDocumentHeader *) header;	/* put it in the list */
    +	    docHeaders[numDocHeaders] = NULL;
    +	    break;
    +	case DT_DocumentShortHeaderGroup:
    +	    if (shortHeaders == NULL)	/* create a new header list */
    +	    {
    +		shortHeaders = S_MALLOC2(WAISDocumentShortHeader *);
    +	    } else {		/* grow the doc list */
    +		shortHeaders = S_REALLOC2(WAISDocumentShortHeader *,
    +					  shortHeaders,
    +					  numShortHeaders);
    +	    }
    +	    buf = readWAISDocumentShortHeader((WAISDocumentShortHeader **) &header,
    +					      buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    shortHeaders[numShortHeaders++] =
    +		(WAISDocumentShortHeader *) header;	/* put it in the list */
    +	    shortHeaders[numShortHeaders] = NULL;
    +	    break;
    +	case DT_DocumentLongHeaderGroup:
    +	    if (longHeaders == NULL)	/* create a new header list */
    +	    {
    +		longHeaders = S_MALLOC2(WAISDocumentLongHeader *);
    +	    } else {		/* grow the doc list */
    +		longHeaders = S_REALLOC2(WAISDocumentLongHeader *,
    +					 longHeaders,
    +					 numLongHeaders);
    +	    }
    +	    buf = readWAISDocumentLongHeader((WAISDocumentLongHeader **) &header,
    +					     buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    longHeaders[numLongHeaders++] =
    +		(WAISDocumentLongHeader *) header;	/* put it in the list */
    +	    longHeaders[numLongHeaders] = NULL;
    +	    break;
    +	case DT_DocumentTextGroup:
    +	    if (text == NULL)	/* create a new list */
    +	    {
    +		text = S_MALLOC2(WAISDocumentText *);
    +	    } else {		/* grow the list */
    +		text = S_REALLOC2(WAISDocumentText *, text, numText);
    +	    }
    +	    buf = readWAISDocumentText((WAISDocumentText **) &header, buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    text[numText++] =
    +		(WAISDocumentText *) header;	/* put it in the list */
    +	    text[numText] = NULL;
    +	    break;
    +	case DT_DocumentHeadlineGroup:
    +	    if (headlines == NULL)	/* create a new list */
    +	    {
    +		headlines = S_MALLOC2(WAISDocumentHeadlines *);
    +	    } else {		/* grow the list */
    +		headlines = S_REALLOC2(WAISDocumentHeadlines *, headlines, numHeadlines);
    +	    }
    +	    buf = readWAISDocumentHeadlines((WAISDocumentHeadlines **) &header,
    +					    buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    headlines[numHeadlines++] =
    +		(WAISDocumentHeadlines *) header;	/* put it in the list */
    +	    headlines[numHeadlines] = NULL;
    +	    break;
    +	case DT_DocumentCodeGroup:
    +	    if (codes == NULL)	/* create a new list */
    +	    {
    +		codes = S_MALLOC2(WAISDocumentCodes *);
    +	    } else {		/* grow the list */
    +		codes = S_REALLOC2(WAISDocumentCodes *, codes, numCodes);
    +	    }
    +	    buf = readWAISDocumentCodes((WAISDocumentCodes **) &header, buf);
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    RETURN_ON_NULL(buf);
    +	    codes[numCodes++] =
    +		(WAISDocumentCodes *) header;	/* put it in the list */
    +	    codes[numCodes] = NULL;
    +	    break;
    +	default:
    +	    cleanUpWaisSearchResponse(buf,
    +				      seedWordsUsed,
    +				      docHeaders,
    +				      shortHeaders,
    +				      longHeaders,
    +				      text,
    +				      headlines,
    +				      codes,
    +				      diags);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *info = (void *) makeWAISSearchResponse(seedWordsUsed,
    +					    docHeaders,
    +					    shortHeaders,
    +					    longHeaders,
    +					    text,
    +					    headlines,
    +					    codes,
    +					    diags);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentText *makeWAISDocumentText(any *docID,
    +				       long versionNumber,
    +				       any *documentText)
    +{
    +    WAISDocumentText *docText = S_MALLOC(WAISDocumentText);
    +
    +    docText->DocumentID = docID;
    +    docText->VersionNumber = versionNumber;
    +    docText->DocumentText = documentText;
    +
    +    return (docText);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentText(WAISDocumentText *docText)
    +{
    +    freeAny(docText->DocumentID);
    +    freeAny(docText->DocumentText);
    +    s_free(docText);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentText(WAISDocumentText *docText, char *buffer,
    +			    long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentTextGroup,
    +					       DefWAISDocTextSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(docText->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(docText->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeAny(docText->DocumentText, DT_DocumentText, buf, len);
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentTextGroup, size, header_len, buffer, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentText(WAISDocumentText **docText, char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID, *documentText;
    +    long versionNumber;
    +
    +    docID = documentText = NULL;
    +    versionNumber = UNUSED;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_DocumentText:
    +	    buf = readAny(&documentText, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    freeAny(documentText);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *docText = makeWAISDocumentText(docID, versionNumber, documentText);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentHeadlines *makeWAISDocumentHeadlines(any *docID,
    +						 long versionNumber,
    +						 char *source,
    +						 char *date,
    +						 char *headline,
    +						 char *originCity)
    +{
    +    WAISDocumentHeadlines *docHeadline = S_MALLOC(WAISDocumentHeadlines);
    +
    +    docHeadline->DocumentID = docID;
    +    docHeadline->VersionNumber = versionNumber;
    +    docHeadline->Source = source;
    +    docHeadline->Date = date;
    +    docHeadline->Headline = headline;
    +    docHeadline->OriginCity = originCity;
    +
    +    return (docHeadline);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentHeadlines(WAISDocumentHeadlines *docHeadline)
    +{
    +    freeAny(docHeadline->DocumentID);
    +    s_free(docHeadline->Source);
    +    s_free(docHeadline->Date);
    +    s_free(docHeadline->Headline);
    +    s_free(docHeadline->OriginCity);
    +    s_free(docHeadline);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentHeadlines(WAISDocumentHeadlines *docHeadline, char *buffer,
    +				 long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentHeadlineGroup,
    +					       DefWAISDocHeadlineSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(docHeadline->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(docHeadline->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeString(docHeadline->Source, DT_Source, buf, len);
    +    buf = writeString(docHeadline->Date, DT_Date, buf, len);
    +    buf = writeString(docHeadline->Headline, DT_Headline, buf, len);
    +    buf = writeString(docHeadline->OriginCity, DT_OriginCity, buf, len);
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentHeadlineGroup,
    +			      size,
    +			      header_len,
    +			      buffer,
    +			      len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentHeadlines(WAISDocumentHeadlines **docHeadline, char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID;
    +    long versionNumber;
    +    char *source, *date, *headline, *originCity;
    +
    +    docID = NULL;
    +    versionNumber = UNUSED;
    +    source = date = headline = originCity = NULL;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_Source:
    +	    buf = readString(&source, buf);
    +	    break;
    +	case DT_Date:
    +	    buf = readString(&date, buf);
    +	    break;
    +	case DT_Headline:
    +	    buf = readString(&headline, buf);
    +	    break;
    +	case DT_OriginCity:
    +	    buf = readString(&originCity, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    s_free(source);
    +	    s_free(date);
    +	    s_free(headline);
    +	    s_free(originCity);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *docHeadline = makeWAISDocumentHeadlines(docID, versionNumber, source, date,
    +					     headline, originCity);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +WAISDocumentCodes *makeWAISDocumentCodes(any *docID,
    +					 long versionNumber,
    +					 char *stockCodes,
    +					 char *companyCodes,
    +					 char *industryCodes)
    +{
    +    WAISDocumentCodes *docCodes = S_MALLOC(WAISDocumentCodes);
    +
    +    docCodes->DocumentID = docID;
    +    docCodes->VersionNumber = versionNumber;
    +    docCodes->StockCodes = stockCodes;
    +    docCodes->CompanyCodes = companyCodes;
    +    docCodes->IndustryCodes = industryCodes;
    +
    +    return (docCodes);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeWAISDocumentCodes(WAISDocumentCodes *docCodes)
    +{
    +    freeAny(docCodes->DocumentID);
    +    s_free(docCodes->StockCodes);
    +    s_free(docCodes->CompanyCodes);
    +    s_free(docCodes->IndustryCodes);
    +    s_free(docCodes);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeWAISDocumentCodes(WAISDocumentCodes *docCodes,
    +			     char *buffer,
    +			     long *len)
    +{
    +    unsigned long header_len = userInfoTagSize(DT_DocumentCodeGroup,
    +					       DefWAISDocCodeSize);
    +    char *buf = buffer + header_len;
    +    unsigned long size;
    +
    +    RESERVE_SPACE_FOR_WAIS_HEADER(len);
    +
    +    buf = writeAny(docCodes->DocumentID, DT_DocumentID, buf, len);
    +    buf = writeNum(docCodes->VersionNumber, DT_VersionNumber, buf, len);
    +    buf = writeString(docCodes->StockCodes, DT_StockCodes, buf, len);
    +    buf = writeString(docCodes->CompanyCodes, DT_CompanyCodes, buf, len);
    +    buf = writeString(docCodes->IndustryCodes, DT_IndustryCodes, buf, len);
    +
    +    /* now write the header and size */
    +    size = buf - buffer;
    +    buf = writeUserInfoHeader(DT_DocumentCodeGroup, size, header_len, buffer, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readWAISDocumentCodes(WAISDocumentCodes **docCodes,
    +			    char *buffer)
    +{
    +    char *buf = buffer;
    +    unsigned long size;
    +    unsigned long headerSize;
    +    data_tag tag1;
    +    any *docID;
    +    long versionNumber;
    +    char *stockCodes, *companyCodes, *industryCodes;
    +
    +    docID = NULL;
    +    versionNumber = UNUSED;
    +    stockCodes = companyCodes = industryCodes = NULL;
    +
    +    buf = readUserInfoHeader(&tag1, &size, buf);
    +    headerSize = buf - buffer;
    +
    +    while (buf < (buffer + size + headerSize)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_DocumentID:
    +	    buf = readAny(&docID, buf);
    +	    break;
    +	case DT_VersionNumber:
    +	    buf = readNum(&versionNumber, buf);
    +	    break;
    +	case DT_StockCodes:
    +	    buf = readString(&stockCodes, buf);
    +	    break;
    +	case DT_CompanyCodes:
    +	    buf = readString(&companyCodes, buf);
    +	    break;
    +	case DT_IndustryCodes:
    +	    buf = readString(&industryCodes, buf);
    +	    break;
    +	default:
    +	    freeAny(docID);
    +	    s_free(stockCodes);
    +	    s_free(companyCodes);
    +	    s_free(industryCodes);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    *docCodes = makeWAISDocumentCodes(docID, versionNumber, stockCodes,
    +				      companyCodes, industryCodes);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writePresentInfo(PresentAPDU * present GCC_UNUSED, char *buffer,
    +		       long *len GCC_UNUSED)
    +{
    +    /* The WAIS protocol doesn't use present info */
    +    return (buffer);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readPresentInfo(void **info,
    +		      char *buffer)
    +{
    +    /* The WAIS protocol doesn't use present info */
    +    *info = NULL;
    +    return (buffer);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writePresentResponseInfo(PresentResponseAPDU * response GCC_UNUSED, char *buffer,
    +			       long *len GCC_UNUSED)
    +{
    +    /* The WAIS protocol doesn't use presentResponse info */
    +    return (buffer);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readPresentResponseInfo(void **info,
    +			      char *buffer)
    +{
    +    /* The WAIS protocol doesn't use presentResponse info */
    +    *info = NULL;
    +    return (buffer);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* support for type 1 queries */
    +
    +/* new use values (for the chunk types) */
    +#define	BYTE		"wb"
    +#define	LINE		"wl"
    +#define	PARAGRAPH	"wp"
    +#define DATA_TYPE	"wt"
    +
    +/* WAIS supports the following semantics for type 1 queries:
    +
    +     1.  retrieve the header/codes from a document:
    +
    +	    System_Control_Number = docID
    +	    Data Type = type (optional)
    +	    And
    +
    +     2.  retrieve a fragment of the text of a document:
    +
    +	    System_Control_Number = docID
    +	    Data Type = type (optional)
    +	    And
    +		Chunk >= start
    +		And
    +		Chunk < end
    +		And
    +
    +		Information from multiple documents may be requested by using
    +		groups of the above joined by:
    +
    +	    OR
    +
    +		( XXX does an OR come after every group but the first, or do they
    +	      all come at the end? )
    +
    +	( XXX return type could be in the element set)
    +*/
    +
    +static query_term **makeWAISQueryTerms(DocObj **docs)
    +/* given a null terminated list of docObjs, construct the appropriate
    +   query of the form given above
    + */
    +{
    +    query_term **terms = NULL;
    +    long numTerms = 0;
    +    DocObj *doc = NULL;
    +    long i;
    +
    +    if (docs == NULL)
    +	return ((query_term **) NULL);
    +
    +    terms = (query_term **) s_malloc((size_t) (sizeof(query_term *) * 1));
    +
    +    terms[numTerms] = NULL;
    +
    +    /* loop through the docs making terms for them all */
    +    for (i = 0, doc = docs[i]; doc != NULL; doc = docs[++i]) {
    +	any *type = NULL;
    +
    +	if (doc->Type != NULL)
    +	    type = stringToAny(doc->Type);
    +
    +	if (doc->ChunkCode == CT_document)	/* a whole document */
    +	{
    +	    terms = S_REALLOC2(query_term *, terms, numTerms + 2);
    +
    +	    terms[numTerms++] = makeAttributeTerm(SYSTEM_CONTROL_NUMBER,
    +						  EQUAL, IGNORE, IGNORE,
    +						  IGNORE, IGNORE, doc->DocumentID);
    +	    if (type != NULL) {
    +		terms[numTerms++] = makeAttributeTerm(DATA_TYPE, EQUAL,
    +						      IGNORE, IGNORE, IGNORE,
    +						      IGNORE, type);
    +		terms[numTerms++] = makeOperatorTerm(AND);
    +	    }
    +	    terms[numTerms] = NULL;
    +	} else {		/* a document fragment */
    +	    char chunk_att[ATTRIBUTE_SIZE];
    +	    any *startChunk = NULL;
    +	    any *endChunk = NULL;
    +
    +	    terms = S_REALLOC2(query_term *, terms, numTerms + 6);
    +
    +	    switch (doc->ChunkCode) {
    +	    case CT_byte:
    +	    case CT_line:
    +		{
    +		    char start[20], end[20];
    +
    +		    (doc->ChunkCode == CT_byte) ?
    +			StrNCpy(chunk_att, BYTE, ATTRIBUTE_SIZE) :
    +			StrNCpy(chunk_att, LINE, ATTRIBUTE_SIZE);
    +		    sprintf(start, "%ld", doc->ChunkStart.Pos);
    +		    startChunk = stringToAny(start);
    +		    sprintf(end, "%ld", doc->ChunkEnd.Pos);
    +		    endChunk = stringToAny(end);
    +		}
    +		break;
    +	    case CT_paragraph:
    +		StrNCpy(chunk_att, PARAGRAPH, ATTRIBUTE_SIZE);
    +		startChunk = doc->ChunkStart.ID;
    +		endChunk = doc->ChunkEnd.ID;
    +		break;
    +	    default:
    +		/* error */
    +		break;
    +	    }
    +
    +	    terms[numTerms++] = makeAttributeTerm(SYSTEM_CONTROL_NUMBER,
    +						  EQUAL, IGNORE, IGNORE,
    +						  IGNORE,
    +						  IGNORE, doc->DocumentID);
    +	    if (type != NULL) {
    +		terms[numTerms++] = makeAttributeTerm(DATA_TYPE, EQUAL, IGNORE,
    +						      IGNORE, IGNORE, IGNORE,
    +						      type);
    +		terms[numTerms++] = makeOperatorTerm(AND);
    +	    }
    +	    terms[numTerms++] = makeAttributeTerm(chunk_att,
    +						  GREATER_THAN_OR_EQUAL,
    +						  IGNORE, IGNORE, IGNORE,
    +						  IGNORE,
    +						  startChunk);
    +	    terms[numTerms++] = makeOperatorTerm(AND);
    +	    terms[numTerms++] = makeAttributeTerm(chunk_att, LESS_THAN,
    +						  IGNORE, IGNORE, IGNORE,
    +						  IGNORE,
    +						  endChunk);
    +	    terms[numTerms++] = makeOperatorTerm(AND);
    +	    terms[numTerms] = NULL;
    +
    +	    if (doc->ChunkCode == CT_byte || doc->ChunkCode == CT_line) {
    +		freeAny(startChunk);
    +		freeAny(endChunk);
    +	    }
    +	}
    +
    +	freeAny(type);
    +
    +	if (i != 0)		/* multiple independent queries, need a disjunction */
    +	{
    +	    terms = S_REALLOC2(query_term *, terms, numTerms);
    +
    +	    terms[numTerms++] = makeOperatorTerm(OR);
    +	    terms[numTerms] = NULL;
    +	}
    +    }
    +
    +    return (terms);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static DocObj **makeWAISQueryDocs(query_term **terms)
    +/* given a list of terms in the form given above, convert them to
    +   DocObjs.
    + */
    +{
    +    query_term *docTerm = NULL;
    +    query_term *fragmentTerm = NULL;
    +    DocObj **docs = NULL;
    +    DocObj *doc = NULL;
    +    long docNum, termNum;
    +
    +    docNum = termNum = 0;
    +
    +    docs = S_MALLOC(DocObj *);
    +
    +    docs[docNum] = NULL;
    +
    +    /* translate the terms into DocObjs */
    +    while (true) {
    +	query_term *typeTerm = NULL;
    +	char *type = NULL;
    +	long startTermOffset;
    +
    +	docTerm = terms[termNum];
    +
    +	if (docTerm == NULL)
    +	    break;		/* we're done converting */
    +
    +	typeTerm = terms[termNum + 1];	/* get the lead Term if it exists */
    +
    +	if (strcmp(typeTerm->Use, DATA_TYPE) == 0)	/* we do have a type */
    +	{
    +	    startTermOffset = 3;
    +	    type = anyToString(typeTerm->Term);
    +	} else {		/* no type */
    +	    startTermOffset = 1;
    +	    typeTerm = NULL;
    +	    type = NULL;
    +	}
    +
    +	/* grow the doc list */
    +	docs = S_REALLOC2(DocObj *, docs, docNum);
    +
    +	/* figure out what kind of docObj to build - and build it */
    +	fragmentTerm = terms[termNum + startTermOffset];
    +	if (fragmentTerm != NULL && fragmentTerm->TermType == TT_Attribute) {	/* build a document fragment */
    +	    query_term *startTerm = fragmentTerm;
    +	    query_term *endTerm = terms[termNum + startTermOffset + 2];
    +
    +	    if (strcmp(startTerm->Use, BYTE) == 0) {	/* a byte chunk */
    +		doc = makeDocObjUsingBytes(duplicateAny(docTerm->Term),
    +					   type,
    +					   anyToLong(startTerm->Term),
    +					   anyToLong(endTerm->Term));
    +		log_write("byte");
    +	    } else if (strcmp(startTerm->Use, LINE) == 0) {	/* a line chunk */
    +		doc = makeDocObjUsingLines(duplicateAny(docTerm->Term),
    +					   type,
    +					   anyToLong(startTerm->Term),
    +					   anyToLong(endTerm->Term));
    +		log_write("line");
    +	    } else {
    +		log_write("chunk");	/* a paragraph chunk */
    +		doc = makeDocObjUsingParagraphs(duplicateAny(docTerm->Term),
    +						type,
    +						duplicateAny(startTerm->Term),
    +						duplicateAny(endTerm->Term));
    +	    }
    +	    termNum += (startTermOffset + 4);	/* point to next term */
    +	} else {		/* build a full document */
    +	    doc = makeDocObjUsingWholeDocument(duplicateAny(docTerm->Term),
    +					       type);
    +	    log_write("whole doc");
    +	    termNum += startTermOffset;		/* point to next term */
    +	}
    +
    +	docs[docNum++] = doc;	/* insert the new document */
    +
    +	docs[docNum] = NULL;	/* keep the doc list terminated */
    +
    +	if (terms[termNum] != NULL)
    +	    termNum++;		/* skip the OR operator it necessary */
    +	else
    +	    break;		/* we are done */
    +    }
    +
    +    return (docs);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +any *makeWAISTextQuery(DocObj **docs)
    +/* given a list of DocObjs, return an any whose contents is the corresponding
    +   type 1 query
    + */
    +{
    +    any *buf = NULL;
    +    query_term **terms = NULL;
    +
    +    terms = makeWAISQueryTerms(docs);
    +    buf = writeQuery(terms);
    +
    +    doList((void **) terms, freeTerm);
    +    s_free(terms);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +DocObj **readWAISTextQuery(any *buf)
    +/* given an any whose contents are type 1 queries of the WAIS sort,
    +   construct a list of the corresponding DocObjs
    + */
    +{
    +    query_term **terms = NULL;
    +    DocObj **docs = NULL;
    +
    +    terms = readQuery(buf);
    +    docs = makeWAISQueryDocs(terms);
    +
    +    doList((void **) terms, freeTerm);
    +    s_free(terms);
    +
    +    return (docs);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +/* Customized free WAIS object routines:                                */
    +/*                                                                      */
    +/*   This set of procedures is for applications to free a WAIS object   */
    +/*   which was made with makeWAISFOO.                                   */
    +/*   Each procedure frees only the memory that was allocated in its     */
    +/*   associated makeWAISFOO routine, thus it's not necessary for the    */
    +/*   caller to assign nulls to the pointer fields of the WAIS object.  */
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISInitResponse(WAISInitResponse *init)
    +/* free an object made with makeWAISInitResponse */
    +{
    +    s_free(init);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISSearch(WAISSearch *query)
    +/* destroy an object made with makeWAISSearch() */
    +{
    +    s_free(query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeDocObj(DocObj *doc)
    +/* free a docObj */
    +{
    +    s_free(doc);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocumentHeader(WAISDocumentHeader *header)
    +{
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocumentShortHeader(WAISDocumentShortHeader *header)
    +{
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocumentLongHeader(WAISDocumentLongHeader *header)
    +{
    +    s_free(header);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISSearchResponse(WAISSearchResponse * response)
    +{
    +    s_free(response);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocumentText(WAISDocumentText *docText)
    +{
    +    s_free(docText);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocHeadlines(WAISDocumentHeadlines *docHeadline)
    +{
    +    s_free(docHeadline);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISDocumentCodes(WAISDocumentCodes *docCodes)
    +{
    +    s_free(docCodes);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void CSTFreeWAISTextQuery(any *query)
    +{
    +    freeAny(query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from WMessage.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + * 3.26.90
    + */
    +
    +/* This file is for reading and writing the wais packet header.
    + * Morris@think.com
    + */
    +
    +/* to do:
    + *  add check sum
    + *  what do you do when checksum is wrong?
    + */
    +
    +/*---------------------------------------------------------------------*/
    +
    +void readWAISPacketHeader(char *msgBuffer,
    +			  WAISMessage * header_struct)
    +{
    +    /* msgBuffer is a string containing at least HEADER_LENGTH bytes. */
    +
    +    memmove(header_struct->msg_len, msgBuffer, (size_t) 10);
    +    header_struct->msg_type = char_downcase((unsigned long) msgBuffer[10]);
    +    header_struct->hdr_vers = char_downcase((unsigned long) msgBuffer[11]);
    +    memmove(header_struct->server, (void *) (msgBuffer + 12), (size_t) 10);
    +    header_struct->compression = char_downcase((unsigned long) msgBuffer[22]);
    +    header_struct->encoding = char_downcase((unsigned long) msgBuffer[23]);
    +    header_struct->msg_checksum = char_downcase((unsigned long) msgBuffer[24]);
    +}
    +
    +/*---------------------------------------------------------------------*/
    +
    +/* this modifies the header argument.  See wais-message.h for the different
    + * options for the arguments.
    + */
    +
    +void writeWAISPacketHeader(char *header,
    +			   long dataLen,
    +			   long type,
    +			   char *server,
    +			   long compression,
    +			   long encoding,
    +			   long version)
    +/* Puts together the new wais before-the-z39-packet header. */
    +{
    +    char lengthBuf[11];
    +    char serverBuf[11];
    +
    +    long serverLen = strlen(server);
    +
    +    if (serverLen > 10)
    +	serverLen = 10;
    +
    +    sprintf(lengthBuf, "%010ld", dataLen);
    +    StrNCpy(header, lengthBuf, 10);
    +
    +    header[10] = type & 0xFF;
    +    header[11] = version & 0xFF;
    +
    +    StrNCpy(serverBuf, server, serverLen);
    +    StrNCpy((char *) (header + 12), serverBuf, serverLen);
    +
    +    header[22] = compression & 0xFF;
    +    header[23] = encoding & 0xFF;
    +    header[24] = '0';		/* checkSum(header + HEADER_LENGTH,dataLen);   XXX the result must be ascii */
    +}
    +
    +/*---------------------------------------------------------------------*/
    diff --git a/WWW/Library/Implementation/HTVMS_WaisProt.h b/WWW/Library/Implementation/HTVMS_WaisProt.h
    new file mode 100644
    index 0000000..cea2a32
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMS_WaisProt.h
    @@ -0,0 +1,425 @@
    +/*							HTVMS_WAISProt.h
    + *
    + *	Adaptation for Lynx by F.Macrides (macrides@sci.wfeb.edu)
    + *
    + *	31-May-1994 FM	Initial version.
    + *
    + *----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from WProt.h -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + *  3.26.90	Harry Morris, morris@think.com
    + *  3.30.90  Harry Morris
    + *			-	removed chunk code from WAISSearchAPDU,
    + *			-	added makeWAISQueryType1Query() and readWAISType1Query() which
    + *				replace makeWAISQueryTerms() and makeWAISQueryDocs().
    + *  4.11.90  HWM - added definitions of wais element set names
    + *  4.14.90  HWM - changed symbol for relevance feedback query from QT_3 to
    + *				  QT_RelevanceFeedbackQuery added QT_TextRetrievalQuery as a
    + *				  synonym for QT_BooleanQuery
    + *				- renamed makeWAISType1Query() to makeWAISTextQuery()
    + *				  renamed readWAISType1Query() to readWAISTextQuery()
    + *  5.29.90  TS - added CSTFreeWAISFoo functions
    + */
    +
    +#ifndef _H_WAIS_protocol_
    +#define _H_WAIS_protocol_
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#include <HTVMS_WaisUI.h>
    +
    +/*----------------------------------------------------------------------*/
    +/* Data types / constants */
    +
    +/* date factor constants */
    +#define	DF_INDEPENDENT		1
    +#define DF_LATER		2
    +#define DF_EARLIER		3
    +#define DF_SPECIFIED_RANGE	4
    +
    +/* chunk types */
    +#define CT_document		0
    +#define CT_byte			1
    +#define CT_line			2
    +#define CT_paragraph	3
    +
    +/* relevance feedback query */
    +#define QT_RelevanceFeedbackQuery	"3"
    +#define QT_TextRetrievalQuery		QT_BooleanQuery
    +
    +/* new data tags */
    +#define DT_UserInformationLength	(data_tag)99
    +#define	DT_ChunkCode			(data_tag)100
    +#define	DT_ChunkIDLength		(data_tag)101
    +#define	DT_ChunkMarker			(data_tag)102
    +#define	DT_HighlightMarker		(data_tag)103
    +#define	DT_DeHighlightMarker		(data_tag)104
    +#define	DT_NewlineCharacters		(data_tag)105
    +#define	DT_SeedWords			(data_tag)106
    +#define	DT_DocumentIDChunk		(data_tag)107
    +#define	DT_ChunkStartID			(data_tag)108
    +#define	DT_ChunkEndID			(data_tag)109
    +#define	DT_TextList			(data_tag)110
    +#define	DT_DateFactor			(data_tag)111
    +#define	DT_BeginDateRange		(data_tag)112
    +#define	DT_EndDateRange			(data_tag)113
    +#define	DT_MaxDocumentsRetrieved	(data_tag)114
    +#define	DT_SeedWordsUsed		(data_tag)115
    +#define	DT_DocumentID			(data_tag)116
    +#define	DT_VersionNumber		(data_tag)117
    +#define	DT_Score			(data_tag)118
    +#define	DT_BestMatch			(data_tag)119
    +#define	DT_DocumentLength		(data_tag)120
    +#define	DT_Source			(data_tag)121
    +#define	DT_Date				(data_tag)122
    +#define	DT_Headline			(data_tag)123
    +#define	DT_OriginCity			(data_tag)124
    +#define	DT_PresentStartByte		(data_tag)125
    +#define	DT_TextLength			(data_tag)126
    +#define	DT_DocumentText			(data_tag)127
    +#define	DT_StockCodes			(data_tag)128
    +#define	DT_CompanyCodes			(data_tag)129
    +#define	DT_IndustryCodes		(data_tag)130
    +
    +/* added by harry */
    +#define DT_DocumentHeaderGroup		(data_tag)150
    +#define DT_DocumentShortHeaderGroup	(data_tag)151
    +#define DT_DocumentLongHeaderGroup	(data_tag)152
    +#define DT_DocumentTextGroup		(data_tag)153
    +#define DT_DocumentHeadlineGroup	(data_tag)154
    +#define DT_DocumentCodeGroup		(data_tag)155
    +#define DT_Lines			(data_tag)131
    +#define	DT_TYPE_BLOCK			(data_tag)132
    +#define DT_TYPE				(data_tag)133
    +
    +/* wais element sets */
    +#define ES_DocumentHeader		"Document Header"
    +#define ES_DocumentShortHeader		"Document Short Header"
    +#define ES_DocumentLongHeader		"Document Long Header"
    +#define ES_DocumentText			"Document Text"
    +#define ES_DocumentHeadline		"Document Headline"
    +#define ES_DocumentCodes		"Document Codes"
    +
    +typedef struct DocObj {		/* specifies a section of a document */
    +    any *DocumentID;
    +    char *Type;
    +    long ChunkCode;
    +    union {
    +	long Pos;
    +	any *ID;
    +    } ChunkStart;
    +    union {
    +	long Pos;
    +	any *ID;
    +    } ChunkEnd;
    +} DocObj;
    +
    +/*----------------------------------------------------------------------*/
    +/* WAIS APDU extensions */
    +
    +typedef struct WAISInitResponse {
    +    long ChunkCode;
    +    long ChunkIDLength;
    +    char *ChunkMarker;
    +    char *HighlightMarker;
    +    char *DeHighlightMarker;
    +    char *NewlineCharacters;
    +    /* XXX  need to add UpdateFrequency and Update Time */
    +} WAISInitResponse;
    +
    +typedef struct WAISSearch {
    +    char *SeedWords;
    +    DocObj **Docs;
    +    char **TextList;
    +    long DateFactor;
    +    char *BeginDateRange;
    +    char *EndDateRange;
    +    long MaxDocumentsRetrieved;
    +} WAISSearch;
    +
    +typedef struct WAISDocumentHeader {
    +    any *DocumentID;
    +    long VersionNumber;
    +    long Score;
    +    long BestMatch;
    +    long DocumentLength;
    +    long Lines;
    +    char **Types;
    +    char *Source;
    +    char *Date;
    +    char *Headline;
    +    char *OriginCity;
    +} WAISDocumentHeader;
    +
    +typedef struct WAISDocumentShortHeader {
    +    any *DocumentID;
    +    long VersionNumber;
    +    long Score;
    +    long BestMatch;
    +    long DocumentLength;
    +    long Lines;
    +} WAISDocumentShortHeader;
    +
    +typedef struct WAISDocumentLongHeader {
    +    any *DocumentID;
    +    long VersionNumber;
    +    long Score;
    +    long BestMatch;
    +    long DocumentLength;
    +    long Lines;
    +    char **Types;
    +    char *Source;
    +    char *Date;
    +    char *Headline;
    +    char *OriginCity;
    +    char *StockCodes;
    +    char *CompanyCodes;
    +    char *IndustryCodes;
    +} WAISDocumentLongHeader;
    +
    +typedef struct WAISDocumentText {
    +    any *DocumentID;
    +    long VersionNumber;
    +    any *DocumentText;
    +} WAISDocumentText;
    +
    +typedef struct WAISDocumentHeadlines {
    +    any *DocumentID;
    +    long VersionNumber;
    +    char *Source;
    +    char *Date;
    +    char *Headline;
    +    char *OriginCity;
    +} WAISDocumentHeadlines;
    +
    +typedef struct WAISDocumentCodes {
    +    any *DocumentID;
    +    long VersionNumber;
    +    char *StockCodes;
    +    char *CompanyCodes;
    +    char *IndustryCodes;
    +} WAISDocumentCodes;
    +
    +typedef struct WAISSearchResponse {
    +    char *SeedWordsUsed;
    +    WAISDocumentHeader **DocHeaders;
    +    WAISDocumentShortHeader **ShortHeaders;
    +    WAISDocumentLongHeader **LongHeaders;
    +    WAISDocumentText **Text;
    +    WAISDocumentHeadlines **Headlines;
    +    WAISDocumentCodes **Codes;
    +    diagnosticRecord **Diagnostics;
    +} WAISSearchResponse;
    +
    +/*----------------------------------------------------------------------*/
    +/* Functions */
    +
    +char *generate_search_apdu(char *buff,	/* buffer to hold the apdu */
    +			   long *buff_len,	/* number of bytes written to the buffer */
    +			   char *seed_words,	/* string of the seed words */
    +			   char *database_name,
    +			   DocObj **docobjs,
    +			   long maxDocsRetrieved);
    +
    +DocObj *makeDocObjUsingWholeDocument(any *aDocID, char *type);
    +DocObj *makeDocObjUsingBytes(any *aDocID, char *type, long start, long end);
    +DocObj *makeDocObjUsingLines(any *aDocID, char *type, long start, long end);
    +DocObj *makeDocObjUsingParagraphs(any *aDocID, char *type, any *start, any *end);
    +void freeDocObj(DocObj *doc);
    +
    +WAISInitResponse *makeWAISInitResponse(long chunkCode, long chunkIDLen,
    +				       char *chunkMarker, char *highlightMarker,
    +				       char *deHighlightMarker, char *newLineChars);
    +void freeWAISInitResponse(WAISInitResponse *init);
    +
    +WAISSearch *makeWAISSearch(char *seedWords,
    +			   DocObj **docs,
    +			   char **textList,
    +			   long dateFactor,
    +			   char *beginDateRange,
    +			   char *endDateRange,
    +			   long maxDocsRetrieved);
    +void freeWAISSearch(WAISSearch *query);
    +
    +WAISDocumentHeader *makeWAISDocumentHeader(any *aDocID,
    +					   long versionNumber,
    +					   long score,
    +					   long bestMatch,
    +					   long docLen,
    +					   long lines,
    +					   char **types,
    +					   char *source,
    +					   char *date,
    +					   char *headline,
    +					   char *originCity);
    +void freeWAISDocumentHeader(WAISDocumentHeader *header);
    +char *writeWAISDocumentHeader(WAISDocumentHeader *header, char *buffer, long *len);
    +char *readWAISDocumentHeader(WAISDocumentHeader **header, char *buffer);
    +
    +WAISDocumentShortHeader *makeWAISDocumentShortHeader(any *aDocID,
    +						     long versionNumber,
    +						     long score,
    +						     long bestMatch,
    +						     long docLen,
    +						     long lines);
    +void freeWAISDocumentShortHeader(WAISDocumentShortHeader *header);
    +char *writeWAISDocumentShortHeader(WAISDocumentShortHeader *header,
    +				   char *buffer, long *len);
    +char *readWAISDocumentShortHeader(WAISDocumentShortHeader **header, char *buffer);
    +
    +WAISDocumentLongHeader *makeWAISDocumentLongHeader(any *aDocID,
    +						   long versionNumber,
    +						   long score,
    +						   long bestMatch,
    +						   long docLen,
    +						   long lines,
    +						   char **types,
    +						   char *source,
    +						   char *date, char *headline,
    +						   char *originCity,
    +						   char *stockCodes,
    +						   char *companyCodes,
    +						   char *industryCodes);
    +void freeWAISDocumentLongHeader(WAISDocumentLongHeader *header);
    +char *writeWAISDocumentLongHeader(WAISDocumentLongHeader *header,
    +				  char *buffer,
    +				  long *len);
    +char *readWAISDocumentLongHeader(WAISDocumentLongHeader **header, char *buffer);
    +
    +WAISSearchResponse *makeWAISSearchResponse(char *seedWordsUsed,
    +					   WAISDocumentHeader **docHeaders,
    +					   WAISDocumentShortHeader **shortHeaders,
    +					   WAISDocumentLongHeader **longHeaders,
    +					   WAISDocumentText **text, WAISDocumentHeadlines **headlines,
    +					   WAISDocumentCodes **codes,
    +					   diagnosticRecord ** diagnostics);
    +void freeWAISSearchResponse(WAISSearchResponse * response);
    +
    +WAISDocumentText *makeWAISDocumentText(any *aDocID, long versionNumber,
    +				       any *documentText);
    +void freeWAISDocumentText(WAISDocumentText *docText);
    +char *writeWAISDocumentText(WAISDocumentText *docText, char *buffer, long *len);
    +char *readWAISDocumentText(WAISDocumentText **docText, char *buffer);
    +
    +WAISDocumentHeadlines *makeWAISDocumentHeadlines(any *aDocID,
    +						 long versionNumber,
    +						 char *source,
    +						 char *date,
    +						 char *headline,
    +						 char *originCity);
    +void freeWAISDocumentHeadlines(WAISDocumentHeadlines *docHeadline);
    +char *writeWAISDocumentHeadlines(WAISDocumentHeadlines *docHeadline,
    +				 char *buffer,
    +				 long *len);
    +char *readWAISDocumentHeadlines(WAISDocumentHeadlines **docHeadline, char *buffer);
    +
    +WAISDocumentCodes *makeWAISDocumentCodes(any *aDocID,
    +					 long versionNumber,
    +					 char *stockCodes,
    +					 char *companyCodes,
    +					 char *industryCodes);
    +void freeWAISDocumentCodes(WAISDocumentCodes *docCodes);
    +char *writeWAISDocumentCodes(WAISDocumentCodes *docCodes, char *buffer, long *len);
    +char *readWAISDocumentCodes(WAISDocumentCodes **docCodes, char *buffer);
    +
    +any *makeWAISTextQuery(DocObj **docs);
    +DocObj **readWAISTextQuery(any *terms);
    +
    +void CSTFreeWAISInitResponse(WAISInitResponse *init);
    +void CSTFreeWAISSearch(WAISSearch *query);
    +void CSTFreeDocObj(DocObj *doc);
    +void CSTFreeWAISDocumentHeader(WAISDocumentHeader *header);
    +void CSTFreeWAISDocumentShortHeader(WAISDocumentShortHeader *header);
    +void CSTFreeWAISDocumentLongHeader(WAISDocumentLongHeader *header);
    +void CSTFreeWAISSearchResponse(WAISSearchResponse * response);
    +void CSTFreeWAISDocumentText(WAISDocumentText *docText);
    +void CSTFreeWAISDocHeadlines(WAISDocumentHeadlines *docHeadline);
    +void CSTFreeWAISDocumentCodes(WAISDocumentCodes *docCodes);
    +void CSTFreeWAISTextQuery(any *query);
    +
    +/*----------------------------------------------------------------------*/
    +
    +#endif /* ndef _H_WAIS_protocol_ */
    +
    +/*
    + *	Routines originally from WMessage.h -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + * 3.26.90
    + */
    +
    +/* wais-message.h
    + *
    + * This is the header outside of WAIS Z39.50 messages.  The header will be
    + * printable ascii, so as to be transportable.  This header will precede each
    + * Z39.50 APDU, or zero-length message if it is an ACK or NACK.  Be sure to
    + * change hdr_vers current value if you change the structure of the header.
    + *
    + * The characters in the header are case insensitive so that the systems from
    + * the past that only handle one case can at least read the header.
    + *
    + * 7.5.90 HWM - added constants
    + * 7/5/90 brewster added function prototypes and comments
    + * 11/30/90 HWM - went to version 2 (inits and typed retrieval)
    + */
    +
    +#ifndef WMESSAGE_H
    +#define WMESSAGE_H
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#include <HTVMS_WaisUI.h>
    +
    +typedef struct wais_header {
    +    char msg_len[10];		/* length in bytes of following message */
    +    char msg_type;		/* type of message: 'z'=Z39.50 APDU,
    +				   'a'=ACK, 'n'=NACK */
    +    char hdr_vers;		/* version of this header, currently = '2' */
    +    char server[10];		/* name or address of server */
    +    char compression;		/* <sp>=no compression, 'u'=unix compress */
    +    char encoding;		/* <sp>=no encoding, 'h'=hexize,
    +				   'u'=uuencode */
    +    char msg_checksum;		/* XOR of every byte of message */
    +} WAISMessage;
    +
    +#define HEADER_LENGTH	25	/* number of bytes needed to write a
    +				   wais-header (not sizeof(wais_header)) */
    +
    +#define HEADER_VERSION	(long)'2'
    +
    +/* message type */
    +#define Z3950			'z'
    +#define ACK			'a'
    +#define	NAK			'n'
    +
    +/* compression */
    +#define NO_COMPRESSION		' '
    +#define UNIX_COMPRESSION	'u'
    +
    +/* encoding */
    +#define NO_ENCODING		' '
    +#define HEX_ENCODING		'h'	/* Swartz 4/3 encoding */
    +#define IBM_HEXCODING		'i'	/* same as h but uses characters acceptable for IBM mainframes */
    +#define UUENCODE		'u'
    +
    +void readWAISPacketHeader(char *msgBuffer, WAISMessage * header_struct);
    +long getWAISPacketLength(WAISMessage * header);
    +void writeWAISPacketHeader(char *header, long dataLen, long type,
    +			   char *server, long compression,
    +			   long encoding, long version);
    +
    +#endif /* ndef WMESSAGE_H */
    diff --git a/WWW/Library/Implementation/HTVMS_WaisUI.c b/WWW/Library/Implementation/HTVMS_WaisUI.c
    new file mode 100644
    index 0000000..716bbbb
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMS_WaisUI.c
    @@ -0,0 +1,2279 @@
    +/*
    + * $LynxId: HTVMS_WaisUI.c,v 1.21 2020/01/21 22:00:50 tom Exp $
    + *								HTVMS_WAISUI.c
    + *
    + *	Adaptation for Lynx by F.Macrides (macrides@sci.wfeb.edu)
    + *
    + *	30-May-1994 FM	Initial version.
    + *
    + *----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from UI.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * Brewster@think.com
    + */
    +
    +/*
    + * this is a simple ui toolkit for building other ui's on top.
    + * -brewster
    + *
    + * top level functions:
    + *   generate_search_apdu
    + *   generate_retrieval_apdu
    + *   interpret_message
    + *
    + */
    +
    +/* to do:
    + *   generate multiple queries for long documents.
    + *     this will crash if the file being retrieved is larger than 100k.
    + *   do log_write()
    + *
    + */
    +
    +#include <HTUtils.h>
    +
    +#ifdef VMS
    +#include <HTVMS_WaisUI.h>
    +#include <HTVMS_WaisProt.h>
    +#include <HTTCP.h>
    +
    +#undef MAXINT			/* we don't need it here, and www_tcp.h may conflict */
    +#include <math.h>
    +
    +#include <LYexit.h>
    +#include <LYLeaks.h>
    +
    +void log_write(char *s GCC_UNUSED)
    +{
    +    return;
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* returns a pointer in the buffer of the first free byte.
    +   if it overflows, then NULL is returned
    + */
    +char *generate_search_apdu(char *buff,	/* buffer to hold the apdu */
    +			   long *buff_len,	/* length of the buffer changed to reflect new data written */
    +			   char *seed_words,	/* string of the seed words */
    +			   char *database_name,
    +			   DocObj **docobjs,
    +			   long maxDocsRetrieved)
    +{
    +    /* local variables */
    +
    +    SearchAPDU *search3;
    +    char *end_ptr;
    +    static char *database_names[2] =
    +    {"", 0};
    +    any refID;
    +    WAISSearch *query;
    +
    +    refID.size = 1;
    +    refID.bytes = "3";
    +
    +    database_names[0] = database_name;
    +    query = makeWAISSearch(seed_words,
    +			   docobjs,	/* DocObjsPtr */
    +			   0,
    +			   1,	/* DateFactor */
    +			   0,	/* BeginDateRange */
    +			   0,	/* EndDateRange */
    +			   maxDocsRetrieved
    +	);
    +
    +    search3 = makeSearchAPDU(30,
    +			     5000,	/* should be large */
    +			     30,
    +			     1,	/* replace indicator */
    +			     "",	/* result set name */
    +			     database_names,	/* database name */
    +			     QT_RelevanceFeedbackQuery,		/* query_type */
    +			     0,	/* element name */
    +			     NULL,	/* reference ID */
    +			     query);
    +
    +    end_ptr = writeSearchAPDU(search3, buff, buff_len);
    +
    +    CSTFreeWAISSearch(query);
    +    freeSearchAPDU(search3);
    +    return (end_ptr);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* returns a pointer into the buffer of the next free byte.
    +   if it overflowed, then NULL is returned
    + */
    +
    +char *generate_retrieval_apdu(char *buff,
    +			      long *buff_len,	/* length of the buffer changed to reflect new data written */
    +			      any *docID,
    +			      long chunk_type,
    +			      long start,
    +			      long end,
    +			      char *type,
    +			      char *database_name)
    +{
    +    SearchAPDU *search;
    +    char *end_ptr;
    +
    +    static char *database_names[2];
    +    static char *element_names[3];
    +    any refID;
    +
    +    DocObj *DocObjs[2];
    +    any *query;			/* changed from char* by brewster */
    +
    +    if (NULL == type)
    +	type = s_strdup("TEXT");
    +
    +    database_names[0] = database_name;
    +    database_names[1] = NULL;
    +
    +    element_names[0] = " ";
    +    element_names[1] = ES_DocumentText;
    +    element_names[2] = NULL;
    +
    +    refID.size = 1;
    +    refID.bytes = "3";
    +
    +    switch (chunk_type) {
    +    case CT_line:
    +	DocObjs[0] = makeDocObjUsingLines(docID, type, start, end);
    +	break;
    +    case CT_byte:
    +	DocObjs[0] = makeDocObjUsingBytes(docID, type, start, end);
    +	break;
    +    }
    +    DocObjs[1] = NULL;
    +
    +    query = makeWAISTextQuery(DocObjs);
    +    search = makeSearchAPDU(10, 16, 15,
    +			    1,	/* replace indicator */
    +			    "FOO",	/* result set name */
    +			    database_names,	/* database name */
    +			    QT_TextRetrievalQuery,	/* query_type */
    +			    element_names,	/* element name */
    +			    &refID,	/* reference ID */
    +			    query);
    +    end_ptr = writeSearchAPDU(search, buff, buff_len);
    +    CSTFreeWAISTextQuery(query);
    +    freeSearchAPDU(search);
    +    return (end_ptr);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* this is a safe version of unix 'read' it does all the checking
    + * and looping necessary
    + * to those trying to modify the transport code to use non-UNIX streams:
    + *  This is the function to modify!
    + */
    +static long read_from_stream(int d, char *buf, long nbytes)
    +{
    +    long didRead;
    +    long toRead = nbytes;
    +    long totalRead = 0;		/* paranoia */
    +
    +    while (toRead > 0) {
    +	didRead = NETREAD(d, buf, (int) toRead);
    +	if (didRead == HT_INTERRUPTED)
    +	    return (HT_INTERRUPTED);
    +	if (didRead == -1)	/* error */
    +	    return (-1);
    +	if (didRead == 0)	/* eof */
    +	    return (-2);	/* maybe this should return 0? */
    +	toRead -= didRead;
    +	buf += didRead;
    +	totalRead += didRead;
    +    }
    +    if (totalRead != nbytes)	/* we overread for some reason */
    +	return (-totalRead);	/* bad news */
    +    return (totalRead);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* returns the length of the response, 0 if an error */
    +
    +static long transport_message(long connection,
    +			      char *request_message,
    +			      long request_length,
    +			      char *response_message,
    +			      long response_buffer_length)
    +{
    +    WAISMessage header;
    +    long response_length;
    +    int rv;
    +
    +    /* Write out message.  Read back header.  Figure out response length. */
    +
    +    if (request_length + HEADER_LENGTH !=
    +	NETWRITE(connection, request_message,
    +		 (int) (request_length + HEADER_LENGTH)))
    +	return 0;
    +
    +    /* read for the first '0' */
    +
    +    while (1) {
    +	rv = read_from_stream(connection, response_message, 1);
    +	if (rv == HT_INTERRUPTED)
    +	    return HT_INTERRUPTED;
    +	if (rv < 0)
    +	    return 0;
    +	if ('0' == response_message[0])
    +	    break;
    +    }
    +
    +    rv = read_from_stream(connection, response_message + 1, HEADER_LENGTH - 1);
    +    if (rv == HT_INTERRUPTED)
    +	return HT_INTERRUPTED;
    +    if (rv < 0)
    +	return 0;
    +
    +    readWAISPacketHeader(response_message, &header);
    +    {
    +	char length_array[11];
    +
    +	LYStrNCpy(length_array, header.msg_len, 10);
    +	response_length = atol(length_array);
    +	/*
    +	   if(verbose){
    +	   printf("WAIS header: '%s' length_array: '%s'\n",
    +	   response_message, length_array);
    +	   }
    +	 */
    +	if (response_length > response_buffer_length) {
    +	    /* we got a message that is too long, therefore empty the message out,
    +	       and return 0 */
    +	    long i;
    +
    +	    for (i = 0; i < response_length; i++) {
    +		rv = read_from_stream(connection,
    +				      response_message + HEADER_LENGTH,
    +				      1);
    +		if (rv == HT_INTERRUPTED)
    +		    return HT_INTERRUPTED;
    +		if (rv < 0)
    +		    return 0;
    +	    }
    +	    return (0);
    +	}
    +    }
    +    rv = read_from_stream(connection,
    +			  response_message + HEADER_LENGTH,
    +			  response_length);
    +    if (rv == HT_INTERRUPTED)
    +	return HT_INTERRUPTED;
    +    if (rv < 0)
    +	return 0;
    +    return (response_length);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* returns the number of bytes written.  0 if an error */
    +long interpret_message(char *request_message,
    +		       long request_length,	/* length of the buffer */
    +		       char *response_message,
    +		       long response_buffer_length,
    +		       long connection,
    +		       boolean verbose GCC_UNUSED)
    +{
    +    long response_length;
    +
    +    /* ?
    +       if(verbose){
    +       printf ("sending");
    +       if(hostname_internal && strlen(hostname_internal) > 0)
    +       printf(" to host %s", hostname_internal);
    +       if(service_name && strlen(service_name) > 0)
    +       printf(" for service %s", service_name);
    +       printf("\n");
    +       twais_dsply_rsp_apdu(request_message + HEADER_LENGTH,
    +       request_length);
    +       }
    +
    +     */
    +
    +    writeWAISPacketHeader(request_message,
    +			  request_length,
    +			  (long) 'z',	/* Z39.50 */
    +			  "wais      ",		/* server name */
    +			  (long) NO_COMPRESSION,	/* no compression */
    +			  (long) NO_ENCODING, (long) HEADER_VERSION);
    +    if (connection != 0) {
    +	response_length = transport_message(connection, request_message,
    +					    request_length,
    +					    response_message,
    +					    response_buffer_length);
    +	if (response_length == HT_INTERRUPTED)
    +	    return (HT_INTERRUPTED);
    +    } else
    +	return (0);
    +
    +    return (response_length);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* modifies the string to exclude all seeker codes. sets length to
    +   the new length. */
    +static char *delete_seeker_codes(char *string, long *length)
    +{
    +    long original_count;	/* index into the original string */
    +    long new_count = 0;		/* index into the collapsed string */
    +
    +    for (original_count = 0; original_count < *length; original_count++) {
    +	if (27 == string[original_count]) {
    +	    /* then we have an escape code */
    +	    /* if the next letter is '(' or ')', then ignore two letters */
    +	    if ('(' == string[original_count + 1] ||
    +		')' == string[original_count + 1])
    +		original_count += 1;	/* it is a term marker */
    +	    else
    +		original_count += 4;	/* it is a paragraph marker */
    +	} else
    +	    string[new_count++] = string[original_count];
    +    }
    +    *length = new_count;
    +    return (string);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#if defined(VMS) && defined(__GNUC__)	/* 10-AUG-1995 [pr] */
    +/*
    +  Workaround for an obscure bug in gcc's 2.6.[123] and 2.7.0 vax/vms port;
    +  sometimes global variables will end up not being defined properly,
    +  causing first gas to assume they're routines, then the linker to complain
    +  about unresolved symbols, and finally the program to reference the wrong
    +  objects (provoking ACCVIO).  It's triggered by the specific ordering of
    +  variable usage in the source code, hence rarely appears.  This bug is
    +  fixed in gcc 2.7.1, and was not present in 2.6.0 and earlier.
    +
    +   Make a reference to VAXCRTL's _ctype_[], and also one to this dummy
    +   variable itself to prevent any "defined but not used" warning.
    + */
    +static __const void *__const ctype_dummy[] =
    +{&_ctype_, &ctype_dummy};
    +#endif /* VMS && __GNUC__ */
    +
    +/* returns a pointer to a string with good stuff */
    +char *trim_junk(char *headline)
    +{
    +    long length = strlen(headline) + 1;		/* include the trailing null */
    +    size_t i;
    +
    +    headline = delete_seeker_codes(headline, &length);
    +    /* delete leading spaces */
    +    for (i = 0; i < strlen(headline); i++) {
    +	if (isprint(headline[i])) {
    +	    break;
    +	}
    +    }
    +    headline = headline + i;
    +    /* delete trailing stuff */
    +    for (i = strlen(headline) - 1; i > 0; i--) {
    +	if (isprint(headline[i])) {
    +	    break;
    +	}
    +	headline[i] = '\0';
    +    }
    +    return (headline);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from ZProt.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:`
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * 3.26.90	Harry Morris, morris@think.com
    + * 3.30.90  Harry Morris - Changed any->bits to any->bytes
    + * 4.11.90  HWM - generalized conditional includes (see c-dialect.h)
    + */
    +
    +#define RESERVE_SPACE_FOR_HEADER(spaceLeft)		\
    +	*spaceLeft -= HEADER_LEN;
    +
    +#define RELEASE_HEADER_SPACE(spaceLeft)			\
    +	if (*spaceLeft > 0)				\
    +	  *spaceLeft += HEADER_LEN;
    +
    +/*----------------------------------------------------------------------*/
    +
    +InitResponseAPDU *makeInitResponseAPDU(boolean result,
    +				       boolean search,
    +				       boolean present,
    +				       boolean deleteIt,
    +				       boolean accessControl,
    +				       boolean resourceControl,
    +				       long prefSize,
    +				       long maxMsgSize,
    +				       char *auth,
    +				       char *id,
    +				       char *name,
    +				       char *version,
    +				       any *refID,
    +				       void *userInfo)
    +/* build an initResponse APDU with user specified information */
    +{
    +    InitResponseAPDU *init = (InitResponseAPDU *) s_malloc((size_t) sizeof(InitResponseAPDU));
    +
    +    init->PDUType = initResponseAPDU;
    +    init->Result = result;
    +    init->willSearch = search;
    +    init->willPresent = present;
    +    init->willDelete = deleteIt;
    +    init->supportAccessControl = accessControl;
    +    init->supportResourceControl = resourceControl;
    +    init->PreferredMessageSize = prefSize;
    +    init->MaximumRecordSize = maxMsgSize;
    +    init->IDAuthentication = s_strdup(auth);
    +    init->ImplementationID = s_strdup(id);
    +    init->ImplementationName = s_strdup(name);
    +    init->ImplementationVersion = s_strdup(version);
    +    init->ReferenceID = duplicateAny(refID);
    +    init->UserInformationField = userInfo;	/* not copied! */
    +
    +    return (init);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeInitResponseAPDU(InitResponseAPDU *init)
    +/* free an initAPDU */
    +{
    +    s_free(init->IDAuthentication);
    +    s_free(init->ImplementationID);
    +    s_free(init->ImplementationName);
    +    s_free(init->ImplementationVersion);
    +    freeAny(init->ReferenceID);
    +    s_free(init);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeInitResponseAPDU(InitResponseAPDU *init, char *buffer, long *len)
    +/* write the initResponse to a buffer, adding system information */
    +{
    +    char *buf = buffer + HEADER_LEN;	/* leave room for the header-length-indicator */
    +    long size;
    +    bit_map *optionsBM = NULL;
    +
    +    RESERVE_SPACE_FOR_HEADER(len);
    +
    +    buf = writePDUType(init->PDUType, buf, len);
    +    buf = writeBoolean(init->Result, buf, len);
    +    buf = writeProtocolVersion(buf, len);
    +
    +    optionsBM = makeBitMap((unsigned long) 5, init->willSearch, init->willPresent,
    +			   init->willDelete, init->supportAccessControl,
    +			   init->supportResourceControl);
    +    buf = writeBitMap(optionsBM, DT_Options, buf, len);
    +    freeBitMap(optionsBM);
    +
    +    buf = writeNum(init->PreferredMessageSize,
    +		   DT_PreferredMessageSize,
    +		   buf,
    +		   len);
    +    buf = writeNum(init->MaximumRecordSize,
    +		   DT_MaximumRecordSize,
    +		   buf,
    +		   len);
    +    buf = writeString(init->IDAuthentication,
    +		      DT_IDAuthentication,
    +		      buf,
    +		      len);
    +    buf = writeString(init->ImplementationID,
    +		      DT_ImplementationID,
    +		      buf,
    +		      len);
    +    buf = writeString(init->ImplementationName,
    +		      DT_ImplementationName,
    +		      buf,
    +		      len);
    +    buf = writeString(init->ImplementationVersion,
    +		      DT_ImplementationVersion,
    +		      buf,
    +		      len);
    +    buf = writeAny(init->ReferenceID,
    +		   DT_ReferenceID,
    +		   buf,
    +		   len);
    +
    +    /* go back and write the header-length-indicator */
    +    RELEASE_HEADER_SPACE(len);
    +    size = buf - buffer - HEADER_LEN;
    +    writeBinaryInteger(size, HEADER_LEN, buffer, len);
    +
    +    if (init->UserInformationField != NULL)
    +	buf = writeInitResponseInfo(init, buf, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readInitResponseAPDU(InitResponseAPDU **init, char *buffer)
    +{
    +    char *buf = buffer;
    +    boolean search, present, delete, accessControl, resourceControl;
    +    long prefSize, maxMsgSize;
    +    char *auth, *id, *name, *version;
    +    long size;
    +    pdu_type pduType;
    +    bit_map *versionBM = NULL;
    +    bit_map *optionsBM = NULL;
    +    boolean result;
    +    any *refID = NULL;
    +    void *userInfo = NULL;
    +
    +    auth = id = name = version = NULL;
    +    refID = NULL;
    +
    +    /* read required part */
    +    buf = readBinaryInteger(&size, HEADER_LEN, buf);
    +    buf = readPDUType(&pduType, buf);
    +    buf = readBoolean(&result, buf);
    +    buf = readBitMap(&versionBM, buf);
    +    buf = readBitMap(&optionsBM, buf);
    +    buf = readNum(&prefSize, buf);
    +    buf = readNum(&maxMsgSize, buf);
    +
    +    /* decode optionsBM */
    +    search = bitAtPos(0, optionsBM);
    +    present = bitAtPos(1, optionsBM);
    +    delete = bitAtPos(2, optionsBM);
    +    accessControl = bitAtPos(3, optionsBM);
    +    resourceControl = bitAtPos(4, optionsBM);
    +
    +    /* read optional part */
    +    while (buf < (buffer + size + HEADER_LEN)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_IDAuthentication:
    +	    buf = readString(&auth, buf);
    +	    break;
    +	case DT_ImplementationID:
    +	    buf = readString(&id, buf);
    +	    break;
    +	case DT_ImplementationName:
    +	    buf = readString(&name, buf);
    +	    break;
    +	case DT_ImplementationVersion:
    +	    buf = readString(&version, buf);
    +	    break;
    +	case DT_ReferenceID:
    +	    buf = readAny(&refID, buf);
    +	    break;
    +	default:
    +	    freeBitMap(versionBM);
    +	    freeBitMap(optionsBM);
    +	    s_free(auth);
    +	    s_free(id);
    +	    s_free(name);
    +	    s_free(version);
    +	    freeAny(refID);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    buf = readInitResponseInfo(&userInfo, buf);
    +    if (buf == NULL) {
    +	freeBitMap(versionBM);
    +	freeBitMap(optionsBM);
    +	s_free(auth);
    +	s_free(id);
    +	s_free(name);
    +	s_free(version);
    +	freeAny(refID);
    +    }
    +    RETURN_ON_NULL(buf);
    +
    +    /* construct the basic init object */
    +    *init = makeInitResponseAPDU(result,
    +				 search,
    +				 present,
    +				 delete,
    +				 accessControl,
    +				 resourceControl,
    +				 prefSize,
    +				 maxMsgSize,
    +				 auth,
    +				 id,
    +				 name,
    +				 version,
    +				 refID,
    +				 userInfo);
    +
    +    freeBitMap(versionBM);
    +    freeBitMap(optionsBM);
    +    s_free(auth);
    +    s_free(id);
    +    s_free(name);
    +    s_free(version);
    +    freeAny(refID);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +InitResponseAPDU *replyToInitAPDU(InitAPDU * init, boolean result, void *userInfo)
    +/* respond to an init message in the default way - echoing back
    +   the init info
    + */
    +{
    +    InitResponseAPDU *initResp;
    +
    +    initResp = makeInitResponseAPDU(result,
    +				    init->willSearch,
    +				    init->willPresent,
    +				    init->willDelete,
    +				    init->supportAccessControl,
    +				    init->supportResourceControl,
    +				    init->PreferredMessageSize,
    +				    init->MaximumRecordSize,
    +				    init->IDAuthentication,
    +				    defaultImplementationID(),
    +				    defaultImplementationName(),
    +				    defaultImplementationVersion(),
    +				    init->ReferenceID,
    +				    userInfo);
    +    return (initResp);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +SearchAPDU *makeSearchAPDU(long small,
    +			   long large,
    +			   long medium,
    +			   boolean replace,
    +			   char *name,
    +			   char **databases,
    +			   char *type,
    +			   char **elements,
    +			   any *refID,
    +			   void *queryInfo)
    +{
    +    char *ptr = NULL;
    +    long i;
    +    SearchAPDU *query = (SearchAPDU *) s_malloc((size_t) sizeof(SearchAPDU));
    +
    +    query->PDUType = searchAPDU;
    +    query->SmallSetUpperBound = small;
    +    query->LargeSetLowerBound = large;
    +    query->MediumSetPresentNumber = medium;
    +    query->ReplaceIndicator = replace;
    +    query->ResultSetName = s_strdup(name);
    +    query->DatabaseNames = NULL;
    +    if (databases != NULL) {
    +	for (i = 0, ptr = databases[i]; ptr != NULL; ptr = databases[++i]) {
    +	    if (query->DatabaseNames == NULL)
    +		query->DatabaseNames = (char **) s_malloc((size_t) (sizeof(char
    +									   *)
    +								    * 2));
    +
    +	    else
    +		query->DatabaseNames = (char **) s_realloc((char *) query->DatabaseNames,
    +							   (size_t) (sizeof(char
    +									    *) *
    +								     (i + 2)));
    +
    +	    query->DatabaseNames[i] = s_strdup(ptr);
    +	    query->DatabaseNames[i + 1] = NULL;
    +	}
    +    }
    +    query->QueryType = s_strdup(type);
    +    query->ElementSetNames = NULL;
    +    if (elements != NULL) {
    +	for (i = 0, ptr = elements[i]; ptr != NULL; ptr = elements[++i]) {
    +	    if (query->ElementSetNames == NULL)
    +		query->ElementSetNames =
    +		    (char **) s_malloc((size_t) (sizeof(char *) * 2));
    +
    +	    else
    +		query->ElementSetNames = (char **) s_realloc((char *) query->ElementSetNames,
    +							     (size_t) (sizeof(char
    +									      *) *
    +								       (i + 2)));
    +
    +	    query->ElementSetNames[i] = s_strdup(ptr);
    +	    query->ElementSetNames[i + 1] = NULL;
    +	}
    +    }
    +    query->ReferenceID = duplicateAny(refID);
    +    query->Query = queryInfo;	/* not copied! */
    +    return (query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeSearchAPDU(SearchAPDU *query)
    +{
    +    s_free(query->ResultSetName);
    +    s_free(query->QueryType);
    +    doList((void **) query->DatabaseNames, fs_free);	/* can't use the macro here ! */
    +    s_free(query->DatabaseNames);
    +    doList((void **) query->ElementSetNames, fs_free);	/* can't use the macro here ! */
    +    s_free(query->ElementSetNames);
    +    freeAny(query->ReferenceID);
    +    s_free(query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define DB_DELIMITER	"\037"	/* hex 1F occurs between each database name */
    +#define ES_DELIMITER_1	"\037"	/* separates database name from element name */
    +#define ES_DELIMITER_2	"\036"	/* hex 1E separates <db,es> groups from one another */
    +
    +char *writeSearchAPDU(SearchAPDU *query, char *buffer, long *len)
    +{
    +    char *buf = buffer + HEADER_LEN;	/* leave room for the header-length-indicator */
    +    long size, i;
    +    char *ptr = NULL;
    +    char *scratch = NULL;
    +
    +    RESERVE_SPACE_FOR_HEADER(len);
    +
    +    buf = writePDUType(query->PDUType, buf, len);
    +    buf = writeBinaryInteger(query->SmallSetUpperBound, (size_t) 3, buf, len);
    +    buf = writeBinaryInteger(query->LargeSetLowerBound, (size_t) 3, buf, len);
    +    buf = writeBinaryInteger(query->MediumSetPresentNumber, (size_t) 3, buf, len);
    +    buf = writeBoolean(query->ReplaceIndicator, buf, len);
    +    buf = writeString(query->ResultSetName, DT_ResultSetName, buf, len);
    +    /* write database names */
    +    if (query->DatabaseNames != NULL) {
    +	for (i = 0, scratch = NULL, ptr = query->DatabaseNames[i]; ptr != NULL;
    +	     ptr = query->DatabaseNames[++i]) {
    +	    if (scratch == NULL)
    +		scratch = s_strdup(ptr);
    +	    else {
    +		size_t newScratchSize = (size_t) (strlen(scratch) +
    +						  strlen(ptr) + 2);
    +
    +		scratch = (char *) s_realloc(scratch, newScratchSize);
    +		s_strncat(scratch, DB_DELIMITER, 2, newScratchSize);
    +		s_strncat(scratch, ptr, strlen(ptr) + 1, newScratchSize);
    +	    }
    +	}
    +	buf = writeString(scratch, DT_DatabaseNames, buf, len);
    +	s_free(scratch);
    +    }
    +    buf = writeString(query->QueryType, DT_QueryType, buf, len);
    +    /* write element set names */
    +    if (query->ElementSetNames != NULL) {
    +	for (i = 0, scratch = NULL, ptr = query->ElementSetNames[i];
    +	     ptr != NULL;
    +	     ptr = query->ElementSetNames[++i]) {
    +	    if (scratch == NULL) {
    +		if (query->ElementSetNames[i + 1] == NULL)	/* there is a single element set name */
    +		{
    +		    scratch = (char *) s_malloc((size_t) strlen(ptr) + 2);
    +		    StrNCpy(scratch, ES_DELIMITER_1, 2);
    +		    s_strncat(scratch, ptr, strlen(ptr) + 1, strlen(ptr) + 2);
    +		} else {	/* this is the first of a series of element set names */
    +		    size_t newScratchSize = (size_t) (strlen(ptr) +
    +						      strlen(query->ElementSetNames[i
    +										    + 1])
    +						      + 2);
    +
    +		    scratch = s_strdup(ptr);	/* the database name */
    +		    ptr = query->ElementSetNames[++i];	/* the element set name */
    +		    scratch = (char *) s_realloc(scratch, newScratchSize);
    +		    s_strncat(scratch, ES_DELIMITER_1, 2, newScratchSize);
    +		    s_strncat(scratch, ptr, strlen(ptr) + 1, newScratchSize);
    +		}
    +	    } else {
    +		char *esPtr = query->ElementSetNames[++i];	/* the element set name */
    +		size_t newScratchSize = (size_t) (strlen(scratch) +
    +						  strlen(ptr) +
    +						  strlen(esPtr) +
    +						  3);
    +
    +		scratch = (char *) s_realloc(scratch, newScratchSize);
    +		s_strncat(scratch, ES_DELIMITER_2, 2, newScratchSize);
    +		s_strncat(scratch, ptr, strlen(ptr) + 1, newScratchSize);
    +		s_strncat(scratch, ES_DELIMITER_1, 2, newScratchSize);
    +		s_strncat(scratch, esPtr, strlen(esPtr) + 1, newScratchSize);
    +	    }
    +	}
    +	buf = writeString(scratch, DT_ElementSetNames, buf, len);
    +	s_free(scratch);
    +    }
    +    buf = writeAny(query->ReferenceID, DT_ReferenceID, buf, len);
    +
    +    /* go back and write the header-length-indicator */
    +    RELEASE_HEADER_SPACE(len);
    +    size = buf - buffer - HEADER_LEN;
    +    writeBinaryInteger(size, HEADER_LEN, buffer, len);
    +
    +    if (query->Query != NULL)
    +	buf = writeSearchInfo(query, buf, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +SearchResponseAPDU *makeSearchResponseAPDU(long result,
    +					   long count,
    +					   long recordsReturned,
    +					   long nextPos,
    +					   long resultStatus,
    +					   long presentStatus,
    +					   any *refID,
    +					   void *records)
    +{
    +    SearchResponseAPDU *query =
    +    (SearchResponseAPDU *) s_malloc((size_t) sizeof(SearchResponseAPDU));
    +
    +    query->PDUType = searchResponseAPDU;
    +    query->SearchStatus = result;
    +    query->ResultCount = count;
    +    query->NumberOfRecordsReturned = recordsReturned;
    +    query->NextResultSetPosition = nextPos;
    +    query->ResultSetStatus = resultStatus;
    +    query->PresentStatus = presentStatus;
    +    query->ReferenceID = duplicateAny(refID);
    +    query->DatabaseDiagnosticRecords = records;
    +    return (query);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeSearchResponseAPDU(SearchResponseAPDU *queryResponse)
    +{
    +    freeAny(queryResponse->ReferenceID);
    +    s_free(queryResponse);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeSearchResponseAPDU(SearchResponseAPDU *queryResponse, char *buffer,
    +			      long *len)
    +{
    +    char *buf = buffer + HEADER_LEN;	/* leave room for the header-length-indicator */
    +    long size;
    +
    +    RESERVE_SPACE_FOR_HEADER(len);
    +
    +    buf = writePDUType(queryResponse->PDUType,
    +		       buf,
    +		       len);
    +    buf = writeBinaryInteger(queryResponse->SearchStatus,
    +			     (size_t) 1,
    +			     buf,
    +			     len);
    +    buf = writeBinaryInteger(queryResponse->ResultCount,
    +			     (size_t) 3,
    +			     buf,
    +			     len);
    +    buf = writeBinaryInteger(queryResponse->NumberOfRecordsReturned,
    +			     (size_t) 3,
    +			     buf,
    +			     len);
    +    buf = writeBinaryInteger(queryResponse->NextResultSetPosition,
    +			     (size_t) 3,
    +			     buf,
    +			     len);
    +    buf = writeNum(queryResponse->ResultSetStatus,
    +		   DT_ResultSetStatus,
    +		   buf,
    +		   len);
    +    buf = writeNum(queryResponse->PresentStatus,
    +		   DT_PresentStatus,
    +		   buf,
    +		   len);
    +    buf = writeAny(queryResponse->ReferenceID,
    +		   DT_ReferenceID,
    +		   buf,
    +		   len);
    +
    +    /* go back and write the header-length-indicator */
    +    RELEASE_HEADER_SPACE(len);
    +    size = buf - buffer - HEADER_LEN;
    +    writeBinaryInteger(size, HEADER_LEN, buffer, len);
    +
    +    if (queryResponse->DatabaseDiagnosticRecords != NULL)
    +	buf = writeSearchResponseInfo(queryResponse, buf, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readSearchResponseAPDU(SearchResponseAPDU **queryResponse, char *buffer)
    +{
    +    char *buf = buffer;
    +    long size;
    +    pdu_type pduType;
    +    long result, count, recordsReturned, nextPos;
    +    long resultStatus, presentStatus;
    +    any *refID = NULL;
    +    void *userInfo = NULL;
    +
    +    /* read required part */
    +    buf = readBinaryInteger(&size, HEADER_LEN, buf);
    +    buf = readPDUType(&pduType, buf);
    +    buf = readBinaryInteger(&result, (size_t) 1, buf);
    +    buf = readBinaryInteger(&count, (size_t) 3, buf);
    +    buf = readBinaryInteger(&recordsReturned, (size_t) 3, buf);
    +    buf = readBinaryInteger(&nextPos, (size_t) 3, buf);
    +
    +    resultStatus = presentStatus = UNUSED;
    +    refID = NULL;
    +
    +    /* read optional part */
    +    while (buf < (buffer + size + HEADER_LEN)) {
    +	data_tag tag = peekTag(buf);
    +
    +	switch (tag) {
    +	case DT_ResultSetStatus:
    +	    buf = readNum(&resultStatus, buf);
    +	    break;
    +	case DT_PresentStatus:
    +	    buf = readNum(&presentStatus, buf);
    +	    break;
    +	case DT_ReferenceID:
    +	    buf = readAny(&refID, buf);
    +	    break;
    +	default:
    +	    freeAny(refID);
    +	    REPORT_READ_ERROR(buf);
    +	    break;
    +	}
    +    }
    +
    +    buf = readSearchResponseInfo(&userInfo, buf);
    +    if (buf == NULL)
    +	freeAny(refID);
    +    RETURN_ON_NULL(buf);
    +
    +    /* construct the search object */
    +    *queryResponse = makeSearchResponseAPDU(result,
    +					    count,
    +					    recordsReturned,
    +					    nextPos,
    +					    (long) resultStatus,
    +					    (long) presentStatus,
    +					    refID,
    +					    userInfo);
    +
    +    freeAny(refID);
    +
    +    return (buf);
    +}
    +
    +/*
    + *	Routines originally from ZUtil.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * 3.26.90	Harry Morris, morris@think.com
    + * 3.30.90  Harry Morris - Changed any->bits to any->bytes
    + * 4.11.90  HWM - fixed include file names, changed
    + *		- writeCompressedIntegerWithPadding() to
    + *		  writeCompressedIntWithPadding()
    + *		- generalized conditional includes (see c-dialect.h)
    + * 3.7.91   Jonny Goldman.  Replaced "short" in makeBitMap with "int" line 632.
    + */
    +
    +char *readErrorPosition = NULL;	/* pos where buf stopped making sense */
    +
    +/*----------------------------------------------------------------------*/
    +/* A note on error handling
    +   read - these are low level routines, they do not check the type tags
    +   which (sometimes) precede the data (this is done by the higher
    +   level functions which call these functions).  There is no
    +   attempt made to check that the reading does not exceed the read
    +   buffer.  Such cases should be very rare and usually will be
    +   caught by the calling functions. (note - it is unlikely that
    +   a series of low level reads will go far off the edge without
    +   triggering a type error.  However, it is possible for a single
    +   bad read in an array function (eg. readAny) to attempt to read a
    +   large amount, possibly causing a segmentation violation or out
    +   of memory condition.
    + */
    +/*----------------------------------------------------------------------*/
    +
    +diagnosticRecord *makeDiag(boolean surrogate, char *code, char *addInfo)
    +{
    +    diagnosticRecord *diag =
    +    (diagnosticRecord *) s_malloc((size_t) sizeof(diagnosticRecord));
    +
    +    diag->SURROGATE = surrogate;
    +    MemCpy(diag->DIAG, code, DIAGNOSTIC_CODE_SIZE);
    +    diag->ADDINFO = s_strdup(addInfo);
    +
    +    return (diag);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeDiag(diagnosticRecord * diag)
    +{
    +    if (diag != NULL) {
    +	if (diag->ADDINFO != NULL)
    +	    s_free(diag->ADDINFO);
    +	s_free(diag);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define END_OF_RECORD	0x1D
    +
    +char *writeDiag(diagnosticRecord * diag, char *buffer, long *len)
    +/* diagnostics (as per Appendix D) have a very weird format - this changes
    +   in SR-1
    + */
    +{
    +    char *buf = buffer;
    +    long length;
    +
    +    if (diag == NULL)		/* handle unspecified optional args */
    +	return (buf);
    +
    +    buf = writeTag(DT_DatabaseDiagnosticRecords, buf, len);
    +    CHECK_FOR_SPACE_LEFT(0, len);
    +
    +    length = 3;
    +    if (diag->ADDINFO != NULL)
    +	length += strlen(diag->ADDINFO);
    +
    +    if (length >= 0xFFFF)	/* make sure the length is reasonable */
    +    {
    +	length = 0xFFFF - 1;
    +	diag->ADDINFO[0xFFFF - 3 - 1] = '\0';
    +    }
    +
    +    buf = writeBinaryInteger(length, 2, buf, len);
    +
    +    CHECK_FOR_SPACE_LEFT(1, len);
    +    buf[0] = diag->DIAG[0];
    +    buf++;
    +
    +    CHECK_FOR_SPACE_LEFT(1, len);
    +    buf[0] = diag->DIAG[1];
    +    buf++;
    +
    +    if (length > 3) {
    +	CHECK_FOR_SPACE_LEFT(3, len);
    +	MemCpy(buf, diag->ADDINFO, length - 3);
    +	buf += length - 3;
    +    }
    +
    +    CHECK_FOR_SPACE_LEFT(1, len);
    +    buf[0] = diag->SURROGATE;
    +    buf++;
    +
    +    CHECK_FOR_SPACE_LEFT(1, len);
    +    buf[0] = END_OF_RECORD;
    +    buf++;
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readDiag(diagnosticRecord ** diag, char *buffer)
    +{
    +    char *buf = buffer;
    +    diagnosticRecord *d = (diagnosticRecord *) s_malloc((size_t) sizeof(diagnosticRecord));
    +    data_tag tag;
    +    long len;
    +
    +    buf = readTag(&tag, buf);
    +
    +    buf = readBinaryInteger(&len, 2, buf);
    +
    +    d->DIAG[0] = buf[0];
    +    d->DIAG[1] = buf[1];
    +    d->DIAG[2] = '\0';
    +
    +    if (len > 3) {
    +	d->ADDINFO = (char *) s_malloc((size_t) (len - 3 + 1));
    +	MemCpy(d->ADDINFO, (char *) (buf + 2), len - 3);
    +	d->ADDINFO[len - 3] = '\0';
    +    } else
    +	d->ADDINFO = NULL;
    +
    +    d->SURROGATE = buf[len - 1];
    +
    +    *diag = d;
    +
    +    return (buf + len + 1);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define continueBit	0x80
    +#define dataMask	0x7F
    +#define dataBits	7
    +
    +char *writeCompressedInteger(unsigned long num, char *buf, long *len)
    +/* write a binary integer in the format described on p. 40.
    +   this might be sped up
    +*/
    +{
    +    char byte;
    +    unsigned long i;
    +    unsigned long size;
    +
    +    size = writtenCompressedIntSize(num);
    +    CHECK_FOR_SPACE_LEFT(size, len);
    +
    +    for (i = size - 1; i != 0; i--) {
    +	byte = num & dataMask;
    +	if (i != (size - 1))	/* turn on continue bit */
    +	    byte = (char) (byte | continueBit);
    +	buf[i] = byte;
    +	num = num >> dataBits;	/* don't and here */
    +    }
    +
    +    return (buf + size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readCompressedInteger(unsigned long *num, char *buf)
    +/* read a binary integer in the format described on p. 40.
    +   this might be sped up
    +*/
    +{
    +    long i = 0;
    +    unsigned char byte;
    +
    +    *num = 0;
    +
    +    do {
    +	byte = buf[i++];
    +	*num = *num << dataBits;
    +	*num += (byte & dataMask);
    +    }
    +    while (byte & continueBit);
    +
    +    return (buf + i);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define pad	128		/* high bit is set */
    +
    +char *writeCompressedIntWithPadding(unsigned long num,
    +				    unsigned long size,
    +				    char *buffer,
    +				    long *len)
    +/* Like writeCompressedInteger, except writes padding (128) to make
    +   sure that size bytes are used.  This can be read correctly by
    +   readCompressedInteger()
    +*/
    +{
    +    char *buf = buffer;
    +    unsigned long needed, padding;
    +    long i;
    +
    +    CHECK_FOR_SPACE_LEFT(size, len);
    +
    +    needed = writtenCompressedIntSize(num);
    +    padding = size - needed;
    +    i = padding - 1;
    +
    +    for (i = padding - 1; i >= 0; i--) {
    +	buf[i] = pad;
    +    }
    +
    +    buf = writeCompressedInteger(num, buf + padding, len);
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenCompressedIntSize(unsigned long num)
    +/* return the number of bytes needed to represent the value num in
    +   compressed format.  currently limited to 4 bytes
    + */
    +{
    +    if (num < CompressedInt1Byte)
    +	return (1);
    +    else if (num < CompressedInt2Byte)
    +	return (2);
    +    else if (num < CompressedInt3Byte)
    +	return (3);
    +    else
    +	return (4);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeTag(data_tag tag, char *buf, long *len)
    +/* write out a data tag */
    +{
    +    return (writeCompressedInteger(tag, buf, len));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readTag(data_tag *tag, char *buf)
    +/* read a data tag */
    +{
    +    return (readCompressedInteger(tag, buf));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenTagSize(data_tag tag)
    +{
    +    return (writtenCompressedIntSize(tag));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +data_tag peekTag(char *buf)
    +/* read a data tag without advancing the buffer */
    +{
    +    data_tag tag;
    +
    +    readTag(&tag, buf);
    +    return (tag);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +any *makeAny(unsigned long size, char *data)
    +{
    +    any *a = (any *) s_malloc((size_t) sizeof(any));
    +
    +    a->size = size;
    +    a->bytes = data;
    +    return (a);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeAny(any *a)
    +/* destroy an any and its associated data.  Assumes a->bytes was
    +   allocated using the s_malloc family of libraries
    + */
    +{
    +    if (a != NULL) {
    +	if (a->bytes != NULL)
    +	    s_free(a->bytes);
    +	s_free(a);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +any *duplicateAny(any *a)
    +{
    +    any *copy = NULL;
    +
    +    if (a == NULL)
    +	return (NULL);
    +
    +    copy = (any *) s_malloc((size_t) sizeof(any));
    +
    +    copy->size = a->size;
    +    if (a->bytes == NULL)
    +	copy->bytes = NULL;
    +    else {
    +	copy->bytes = (char *) s_malloc((size_t) copy->size);
    +	MemCpy(copy->bytes, a->bytes, copy->size);
    +    }
    +    return (copy);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeAny(any *a, data_tag tag, char *buffer, long *len)
    +/* write an any + tag and size info */
    +{
    +    char *buf = buffer;
    +
    +    if (a == NULL)		/* handle unspecified optional args */
    +	return (buf);
    +
    +    /* write the tags */
    +    buf = writeTag(tag, buf, len);
    +    buf = writeCompressedInteger(a->size, buf, len);
    +
    +    /* write the bytes */
    +    CHECK_FOR_SPACE_LEFT(a->size, len);
    +    MemCpy(buf, a->bytes, a->size);
    +
    +    return (buf + a->size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readAny(any **anAny, char *buffer)
    +/* read an any + tag and size info */
    +{
    +    char *buf;
    +    any *a;
    +    data_tag tag;
    +
    +    a = (any *) s_malloc((size_t) sizeof(any));
    +
    +    buf = buffer;
    +
    +    buf = readTag(&tag, buf);
    +
    +    buf = readCompressedInteger(&a->size, buf);
    +
    +    /* now simply copy the bytes */
    +    a->bytes = (char *) s_malloc((size_t) a->size);
    +    MemCpy(a->bytes, buf, a->size);
    +    *anAny = a;
    +
    +    return (buf + a->size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenAnySize(data_tag tag, any *a)
    +{
    +    unsigned long size;
    +
    +    if (a == NULL)
    +	return (0);
    +
    +    size = writtenTagSize(tag);
    +    size += writtenCompressedIntSize(a->size);
    +    size += a->size;
    +    return (size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +any *stringToAny(char *s)
    +{
    +    any *a = NULL;
    +
    +    if (s == NULL)
    +	return (NULL);
    +
    +    a = (any *) s_malloc((size_t) sizeof(any));
    +
    +    a->size = strlen(s);
    +    a->bytes = (char *) s_malloc((size_t) a->size);
    +    MemCpy(a->bytes, s, a->size);
    +    return (a);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *anyToString(any *a)
    +{
    +    char *s = NULL;
    +
    +    if (a == NULL)
    +	return (NULL);
    +
    +    s = s_malloc((size_t) (a->size + 1));
    +    MemCpy(s, a->bytes, a->size);
    +    s[a->size] = '\0';
    +    return (s);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeString(char *s, data_tag tag, char *buffer, long *len)
    +/* Write a C style string.  The terminating null is not written.
    +   This function is not part of the Z39.50 spec.  It is provided
    +   for the convenience of those wishing to pass C strings in
    +   the place of an any.
    + */
    +{
    +    char *buf = buffer;
    +    any *data = NULL;
    +
    +    if (s == NULL)
    +	return (buffer);	/* handle unused optional item before making an any */
    +    data = (any *) s_malloc((size_t) sizeof(any));
    +
    +    data->size = strlen(s);
    +    data->bytes = s;		/* save a copy here by not using stringToAny() */
    +    buf = writeAny(data, tag, buf, len);
    +    s_free(data);		/* don't use freeAny() since it will free s too */
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readString(char **s, char *buffer)
    +/* Read an any and convert it into a C style string.
    +   This function is not part of the Z39.50 spec.  It is provided
    +   for the convenience of those wishing to pass C strings in
    +   the place of an any.
    + */
    +{
    +    any *data = NULL;
    +    char *buf = readAny(&data, buffer);
    +
    +    *s = anyToString(data);
    +    freeAny(data);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenStringSize(data_tag tag, char *s)
    +{
    +    unsigned long size;
    +
    +    if (s == NULL)
    +	return (0);
    +
    +    size = writtenTagSize(tag);
    +    size += writtenCompressedIntSize(size);
    +    size += strlen(s);
    +    return (size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +any *longToAny(long num)
    +/* a convenience function */
    +{
    +    char s[40];
    +
    +    sprintf(s, "%ld", num);
    +
    +    return (stringToAny(s));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +long anyToLong(any *a)
    +/* a convenience function */
    +{
    +    long num;
    +    char *str = NULL;
    +
    +    str = anyToString(a);
    +    sscanf(str, "%ld", &num);	/* could check the result and return
    +				   an error */
    +    s_free(str);
    +    return (num);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define bitsPerByte	8
    +
    +bit_map *makeBitMap(unsigned long numBits, ...)
    +/* construct and return a bitmap with numBits elements */
    +{
    +    va_list ap;
    +    unsigned long i, j;
    +    bit_map *bm = NULL;
    +
    +    LYva_start(ap, numBits);
    +
    +    bm = (bit_map *) s_malloc((size_t) sizeof(bit_map));
    +
    +    bm->size = (unsigned long) (ceil((double) numBits / bitsPerByte));
    +    bm->bytes = (char *) s_malloc((size_t) bm->size);
    +
    +    /* fill up the bits */
    +    for (i = 0; i < bm->size; i++)	/* iterate over bytes */
    +    {
    +	char byte = 0;
    +
    +	for (j = 0; j < bitsPerByte; j++)	/* iterate over bits */
    +	{
    +	    if ((i * bitsPerByte + j) < numBits) {
    +		boolean bit = false;
    +
    +		bit = (boolean) va_arg(ap, boolean);
    +
    +		if (bit) {
    +		    byte = byte | (1 << (bitsPerByte - j - 1));
    +		}
    +	    }
    +	}
    +	bm->bytes[i] = byte;
    +    }
    +
    +    va_end(ap);
    +    return (bm);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeBitMap(bit_map *bm)
    +/* destroy a bit map created by makeBitMap() */
    +{
    +    s_free(bm->bytes);
    +    s_free(bm);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* use this routine to interpret a bit map.  pos specifies the bit
    +   number.  bit 0 is the Leftmost bit of the first byte.
    +   Could do bounds checking.
    + */
    +
    +boolean bitAtPos(unsigned long pos, bit_map *bm)
    +{
    +    if (pos > bm->size * bitsPerByte)
    +	return false;
    +    else
    +	return ((bm->bytes[(pos / bitsPerByte)] &
    +		 (0x80 >> (pos % bitsPerByte))) ?
    +		true : false);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeBitMap(bit_map *bm, data_tag tag, char *buffer, long *len)
    +/* write a bitmap + type and size info */
    +{
    +    return (writeAny((any *) bm, tag, buffer, len));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readBitMap(bit_map **bm, char *buffer)
    +/* read a bitmap + type and size info */
    +{
    +    char *c;
    +
    +    c = readAny((any **) bm, buffer);
    +    return (c);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeByte(unsigned long byte, char *buf, long *len)
    +{
    +    CHECK_FOR_SPACE_LEFT(1, len);
    +    buf[0] = byte & 0xFF;	/* we really only want the first byte */
    +    return (buf + 1);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readByte(unsigned char *byte, char *buf)
    +{
    +    *byte = buf[0];
    +    return (buf + 1);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeBoolean(boolean flag, char *buf, long *len)
    +{
    +    return (writeByte(flag, buf, len));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readBoolean(boolean *flag, char *buffer)
    +{
    +    unsigned char byte;
    +    char *buf = readByte(&byte, buffer);
    +
    +    *flag = (byte == true) ? true : false;
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writePDUType(pdu_type pduType, char *buf, long *len)
    +/* PDUType is a single byte */
    +{
    +    return (writeBinaryInteger((long) pduType, (unsigned long) 1, buf, len));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readPDUType(pdu_type *pduType, char *buf)
    +/* PDUType is a single byte */
    +{
    +    return (readBinaryInteger((long *) pduType, (unsigned long) 1, buf));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +pdu_type peekPDUType(char *buf)
    +/* read the next pdu without advancing the buffer, Note that this
    +   function is to be used on a buffer that is known to contain an
    +   APDU.  The pdu_type is written HEADER_LEN bytes into the buffer
    + */
    +{
    +    pdu_type pdu;
    +
    +    readPDUType(&pdu, buf + HEADER_LEN);
    +    return (pdu);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define BINARY_INTEGER_BYTES	sizeof(long)	/* the number of bytes used by
    +						   a "binary integer" */
    +char *writeBinaryInteger(long num, unsigned long size, char *buf, long *len)
    +/* write out first size bytes of num - no type info
    +  XXX should this take unsigned longs instead ???  */
    +{
    +    long i;
    +    char byte;
    +
    +    if (size < 1 || size > BINARY_INTEGER_BYTES)
    +	return (NULL);		/* error */
    +
    +    CHECK_FOR_SPACE_LEFT(size, len);
    +
    +    for (i = size - 1; i >= 0; i--) {
    +	byte = (char) (num & 255);
    +	buf[i] = byte;
    +	num = num >> bitsPerByte;	/* don't and here */
    +    }
    +
    +    return (buf + size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readBinaryInteger(long *num, unsigned long size, char *buf)
    +/* read in first size bytes of num - no type info
    +  XXX this should take unsigned longs instead !!! */
    +{
    +    unsigned long i;
    +    unsigned char byte;
    +
    +    if (size < 1 || size > BINARY_INTEGER_BYTES)
    +	return (buf);		/* error */
    +    *num = 0;
    +
    +    for (i = 0; i < size; i++) {
    +	byte = buf[i];
    +	*num = *num << bitsPerByte;
    +	*num += byte;
    +    }
    +
    +    return (buf + size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenCompressedBinIntSize(long num)
    +/* return the number of bytes needed to represent the value num.
    +   currently limited to max of 4 bytes
    +   Only compresses for positive nums - negatives get whole 4 bytes
    + */
    +{
    +    if (num < 0L)
    +	return (4);
    +    else if (num < 256L)	/* 2**8 */
    +	return (1);
    +    else if (num < 65536L)	/* 2**16 */
    +	return (2);
    +    else if (num < 16777216L)	/* 2**24 */
    +	return (3);
    +    else
    +	return (4);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeNum(long num, data_tag tag, char *buffer, long *len)
    +/* write a binary integer + size and tag info */
    +{
    +    char *buf = buffer;
    +    long size = writtenCompressedBinIntSize(num);
    +
    +    if (num == UNUSED)
    +	return (buffer);
    +
    +    buf = writeTag(tag, buf, len);
    +    buf = writeCompressedInteger(size, buf, len);
    +    buf = writeBinaryInteger(num, (unsigned long) size, buf, len);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readNum(long *num, char *buffer)
    +/* read a binary integer + size and tag info */
    +{
    +    char *buf = buffer;
    +    data_tag tag;
    +    unsigned long size;
    +    unsigned long val;
    +
    +    buf = readTag(&tag, buf);
    +    buf = readCompressedInteger(&val, buf);
    +    size = (unsigned long) val;
    +    buf = readBinaryInteger(num, size, buf);
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +unsigned long writtenNumSize(data_tag tag, long num)
    +{
    +    long dataSize = writtenCompressedBinIntSize(num);
    +    long size;
    +
    +    size = writtenTagSize(tag);	/* space for the tag */
    +    size += writtenCompressedIntSize(dataSize);		/* space for the size */
    +    size += dataSize;		/* space for the data */
    +
    +    return (size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +typedef void (voidfunc) (void *);
    +
    +void doList(void **list, voidfunc * func)
    +/* call func on each element of the NULL terminated list of pointers */
    +{
    +    register long i;
    +    register void *ptr = NULL;
    +
    +    if (list == NULL)
    +	return;
    +    for (i = 0, ptr = list[i]; ptr != NULL; ptr = list[++i])
    +	(*func) (ptr);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *writeProtocolVersion(char *buf, long *len)
    +/* write a bitmap describing the protocols available */
    +{
    +    static bit_map *version = NULL;
    +
    +    if (version == NULL) {
    +	version = makeBitMap((unsigned long) 1, true);	/* version 1! */
    +    }
    +
    +    return (writeBitMap(version, DT_ProtocolVersion, buf, len));
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *defaultImplementationID(void)
    +{
    +    static char ImplementationID[] = "TMC";
    +
    +    return (ImplementationID);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *defaultImplementationName(void)
    +{
    +    static char ImplementationName[] = "Thinking Machines Corporation Z39.50";
    +
    +    return (ImplementationName);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *defaultImplementationVersion(void)
    +{
    +    static char ImplementationVersion[] = "2.0A";
    +
    +    return (ImplementationVersion);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from ZType1.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * 3.26.90	Harry Morris, morris@think.com
    + * 4.11.90  HWM - generalized conditional includes (see c-dialect.h)
    + */
    +/*----------------------------------------------------------------------*/
    +
    +query_term *makeAttributeTerm(char *use,
    +			      char *relation,
    +			      char *position,
    +			      char *structure,
    +			      char *truncation,
    +			      char *completeness,
    +			      any *term)
    +{
    +    query_term *qt = (query_term *) s_malloc((size_t) sizeof(query_term));
    +
    +    qt->TermType = TT_Attribute;
    +
    +    /* copy in the attributes */
    +    LYStrNCpy(qt->Use, use, ATTRIBUTE_SIZE);
    +    LYStrNCpy(qt->Relation, relation, ATTRIBUTE_SIZE);
    +    LYStrNCpy(qt->Position, position, ATTRIBUTE_SIZE);
    +    LYStrNCpy(qt->Structure, structure, ATTRIBUTE_SIZE);
    +    LYStrNCpy(qt->Truncation, truncation, ATTRIBUTE_SIZE);
    +    LYStrNCpy(qt->Completeness, completeness, ATTRIBUTE_SIZE);
    +
    +    qt->Term = duplicateAny(term);
    +
    +    qt->ResultSetID = NULL;
    +
    +    return (qt);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +query_term *makeResultSetTerm(any *resultSet)
    +{
    +    query_term *qt = (query_term *) s_malloc((size_t) sizeof(query_term));
    +
    +    qt->TermType = TT_ResultSetID;
    +
    +    qt->ResultSetID = duplicateAny(resultSet);
    +
    +    qt->Term = NULL;
    +
    +    return (qt);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +query_term *makeOperatorTerm(char *operatorCode)
    +{
    +    query_term *qt = (query_term *) s_malloc((size_t) sizeof(query_term));
    +
    +    qt->TermType = TT_Operator;
    +
    +    LYStrNCpy(qt->Operator, operatorCode, OPERATOR_SIZE);
    +
    +    qt->Term = NULL;
    +    qt->ResultSetID = NULL;
    +
    +    return (qt);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void freeTerm(void *param)
    +{
    +    query_term *qt = (query_term *) param;
    +
    +    switch (qt->TermType) {
    +    case TT_Attribute:
    +	freeAny(qt->Term);
    +	break;
    +    case TT_ResultSetID:
    +	freeAny(qt->ResultSetID);
    +	break;
    +    case TT_Operator:
    +	/* do nothing */
    +	break;
    +    default:
    +	panic("Implementation error: Unknown term type %ld",
    +	      qt->TermType);
    +	break;
    +    }
    +    s_free(qt);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define ATTRIBUTE_LIST_SIZE	ATTRIBUTE_SIZE * 6
    +#define AT_DELIMITER	" "
    +
    +char *writeQueryTerm(query_term *qt, char *buffer, long *len)
    +{
    +    char *buf = buffer;
    +    char attributes[ATTRIBUTE_LIST_SIZE];
    +
    +    switch (qt->TermType) {
    +    case TT_Attribute:
    +	LYStrNCpy(attributes, qt->Use, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, AT_DELIMITER, sizeof(AT_DELIMITER) + 1, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, qt->Relation, ATTRIBUTE_SIZE, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, AT_DELIMITER, sizeof(AT_DELIMITER) + 1, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, qt->Position, ATTRIBUTE_SIZE, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, AT_DELIMITER, sizeof(AT_DELIMITER) + 1, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, qt->Structure, ATTRIBUTE_SIZE, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, AT_DELIMITER, sizeof(AT_DELIMITER) + 1, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, qt->Truncation, ATTRIBUTE_SIZE, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, AT_DELIMITER, sizeof(AT_DELIMITER) + 1, ATTRIBUTE_LIST_SIZE);
    +	s_strncat(attributes, qt->Completeness, ATTRIBUTE_SIZE, ATTRIBUTE_LIST_SIZE);
    +	buf = writeString(attributes, DT_AttributeList, buf, len);
    +	buf = writeAny(qt->Term, DT_Term, buf, len);
    +	break;
    +    case TT_ResultSetID:
    +	buf = writeAny(qt->ResultSetID, DT_ResultSetID, buf, len);
    +	break;
    +    case TT_Operator:
    +	buf = writeString(qt->Operator, DT_Operator, buf, len);
    +	break;
    +    default:
    +	panic("Implementation error: Unknown term type %ld",
    +	      qt->TermType);
    +	break;
    +    }
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *readQueryTerm(query_term **qt, char *buffer)
    +{
    +    char *buf = buffer;
    +    char *attributeList = NULL;
    +    char *operator = NULL;
    +    any *term;
    +    char *use = NULL;
    +    char *relation = NULL;
    +    char *position = NULL;
    +    char *structure = NULL;
    +    char *truncation = NULL;
    +    char *completeness;
    +    any *resultSetID = NULL;
    +    data_tag tag;
    +
    +    tag = peekTag(buffer);
    +
    +    switch (tag) {
    +    case DT_AttributeList:
    +	buf = readString(&attributeList, buf);
    +	buf = readAny(&term, buf);
    +	use = strtok(attributeList, AT_DELIMITER);
    +	relation = strtok(NULL, AT_DELIMITER);
    +	position = strtok(NULL, AT_DELIMITER);
    +	structure = strtok(NULL, AT_DELIMITER);
    +	truncation = strtok(NULL, AT_DELIMITER);
    +	completeness = strtok(NULL, AT_DELIMITER);
    +	*qt = makeAttributeTerm(use, relation, position, structure,
    +				truncation, completeness, term);
    +	s_free(attributeList);
    +	freeAny(term);
    +	break;
    +    case DT_ResultSetID:
    +	buf = readAny(&resultSetID, buf);
    +	*qt = makeResultSetTerm(resultSetID);
    +	freeAny(resultSetID);
    +	break;
    +    case DT_Operator:
    +	buf = readString(&operator, buf);
    +	*qt = makeOperatorTerm(operator);
    +	s_free(operator);
    +	break;
    +    default:
    +	REPORT_READ_ERROR(buf);
    +	break;
    +    }
    +
    +    return (buf);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +static unsigned long getQueryTermSize(query_term *qt);
    +
    +static unsigned long getQueryTermSize(query_term *qt)
    +/* figure out how many bytes it will take to write this query */
    +{
    +    unsigned long size = 0;
    +    static char attributes[] = "11 22 33 44 55 66";	/* we just need this to
    +
    +							   calculate its written
    +							   size */
    +
    +    switch (qt->TermType) {
    +    case TT_Attribute:
    +	size = writtenStringSize(DT_AttributeList, attributes);
    +	size += writtenAnySize(DT_Term, qt->Term);
    +	break;
    +    case TT_ResultSetID:
    +	size = writtenAnySize(DT_ResultSetID, qt->ResultSetID);
    +	break;
    +    case TT_Operator:
    +	size = writtenStringSize(DT_Operator, qt->Operator);
    +	break;
    +    default:
    +	panic("Implementation error: Unknown term type %ld",
    +	      qt->TermType);
    +	break;
    +    }
    +
    +    return (size);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* A query is simply a null terminated list of query terms.  For
    +   transmission, a query is written into an any which is sent as
    +   the user information field. */
    +
    +any *writeQuery(query_term **terms)
    +{
    +    any *info = NULL;
    +    char *writePos = NULL;
    +    char *data = NULL;
    +    unsigned long size = 0;
    +    long remaining = 0;
    +    long i;
    +    query_term *qt = NULL;
    +
    +    if (terms == NULL)
    +	return (NULL);
    +
    +    /* calculate the size of write buffer */
    +    for (i = 0, qt = terms[i]; qt != NULL; qt = terms[++i])
    +	size += getQueryTermSize(qt);
    +
    +    data = (char *) s_malloc((size_t) size);
    +
    +    /* write the terms */
    +    writePos = data;
    +    remaining = size;
    +    for (i = 0, qt = terms[i]; qt != NULL; qt = terms[++i])
    +	writePos = writeQueryTerm(qt, writePos, &remaining);
    +
    +    info = makeAny(size, data);
    +
    +    return (info);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +query_term **readQuery(any *info)
    +{
    +    char *readPos = info->bytes;
    +    query_term **terms = NULL;
    +    query_term *qt = NULL;
    +    long numTerms = 0L;
    +    char tmp[100];
    +
    +    sprintf(tmp, "readquery: bytes: %ld", info->size);
    +    log_write(tmp);
    +
    +    while (readPos < info->bytes + info->size) {
    +	readPos = readQueryTerm(&qt, readPos);
    +
    +	if (terms == NULL) {
    +	    terms = (query_term **) s_malloc((size_t) (sizeof(query_term *) * 2));
    +	} else {
    +	    terms =
    +		(query_term **) s_realloc((char *) terms,
    +					  (size_t) (sizeof(query_term *) *
    +						      (numTerms + 2)));
    +	}
    +	if (qt == NULL)
    +	    log_write("qt = null");
    +	terms[numTerms++] = qt;
    +	terms[numTerms] = NULL;
    +    }
    +
    +    return (terms);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from panic.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * Morris@think.com
    + */
    +
    +/* panic is an error system interface.  On the Mac, it will pop
    + * up a little window to explain the problem.
    + * On a unix box, it will print out the error and call perror()
    + */
    +
    +/*----------------------------------------------------------------------*/
    +
    +static void exitAction(long error);
    +
    +static void exitAction(long error GCC_UNUSED)
    +{
    +    exit_immediately(EXIT_SUCCESS);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +#define PANIC_HEADER "Fatal Error:  "
    +
    +void panic(char *format, ...)
    +{
    +    va_list ap;			/* the variable arguments */
    +
    +    fprintf(stderr, PANIC_HEADER);
    +    LYva_start(ap, format);	/* init ap */
    +    vfprintf(stderr, format, ap);	/* print the contents */
    +    va_end(ap);			/* free ap */
    +    fflush(stderr);
    +
    +    exitAction(0);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from cutil.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +/* Wide AREA INFORMATION SERVER SOFTWARE
    + * No guarantees or restrictions.  See the readme file for the full standard
    + * disclaimer.
    + *
    + * 3.26.90	Harry Morris, morris@think.com
    + * 4.11.90  HWM - generalized conditional includes (see c-dialect.h)
    + */
    +
    +/*----------------------------------------------------------------------*/
    +
    +void fs_checkPtr(void *ptr)
    +/* If the ptr is NULL, give an error */
    +{
    +    if (ptr == NULL)
    +	panic("checkPtr found a NULL pointer");
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void *fs_malloc(size_t size)
    +/* does safety checks and optional accounting */
    +{
    +    register void *ptr = NULL;
    +
    +    ptr = (void *) calloc((size_t) size, (size_t) 1);
    +    s_checkPtr(ptr);
    +
    +    return (ptr);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void *fs_realloc(void *ptr, size_t size)
    +/* does safety checks and optional accounting
    +   note - we don't know how big ptr's memory is, so we can't ensure
    +   that any new memory allocated is NULLed!
    + */
    +{
    +    register void *nptr = NULL;
    +
    +    if (ptr == NULL)		/* this is really a malloc */
    +	return (s_malloc(size));
    +
    +    nptr = (void *) realloc(ptr, size);
    +    s_checkPtr(ptr);
    +
    +    return (nptr);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +void fs_free(void *ptr)
    +/* does safety checks and optional accounting */
    +{
    +    if (ptr != NULL)		/* some non-ansi compilers/os's can't handle freeing null */
    +    {				/* if we knew the size of this block of memory, we could clear it - oh well */
    +	free(ptr);
    +	ptr = NULL;
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *s_strdup(char *s)
    +
    +/* return a copy of s.  This is identical to the standard library routine
    +   strdup(), except that it is safe.  If s == NULL or malloc fails,
    +   appropriate action is taken.
    + */
    +{
    +    unsigned long len;
    +    char *copy = NULL;
    +
    +    if (s == NULL)		/* safety check to postpone stupid errors */
    +	return (NULL);
    +
    +    len = strlen(s);		/* length of string - terminator */
    +    copy = (char *) s_malloc((size_t) (sizeof(char) * (len + 1)));
    +
    +    StrNCpy(copy, s, len + 1);
    +    return (copy);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char *fs_strncat(char *dst, char *src, size_t maxToAdd, size_t maxTotal)
    +
    +/* like strncat, except the fourth argument limits the maximum total
    +   length of the resulting string
    + */
    +{
    +    size_t dstSize = strlen(dst);
    +    size_t srcSize = strlen(src);
    +
    +    if (dstSize + srcSize < maxTotal)	/* use regular old strncat */
    +	return (StrNCat(dst, src, maxToAdd));
    +    else {
    +	size_t truncateTo = maxTotal - dstSize - 1;
    +	char saveChar = src[truncateTo];
    +	char *result = NULL;
    +
    +	src[truncateTo] = '\0';
    +	result = StrNCat(dst, src, maxToAdd);
    +	src[truncateTo] = saveChar;
    +	return (result);
    +    }
    +}
    +
    +/*----------------------------------------------------------------------*/
    +
    +char char_downcase(unsigned long long_ch)
    +{
    +    unsigned char ch = long_ch & 0xFF;	/* just want one byte */
    +
    +    /* when ansi is the way of the world, this can be tolower */
    +    return (((ch >= 'A') && (ch <= 'Z')) ? (ch + 'a' - 'A') : ch);
    +}
    +
    +char *string_downcase(char *word)
    +{
    +    long i = 0;
    +
    +    while (word[i] != '\0') {
    +	word[i] = char_downcase((unsigned long) word[i]);
    +	i++;
    +    }
    +    return (word);
    +}
    +
    +/*----------------------------------------------------------------------*/
    +#endif /* VMS */
    diff --git a/WWW/Library/Implementation/HTVMS_WaisUI.h b/WWW/Library/Implementation/HTVMS_WaisUI.h
    new file mode 100644
    index 0000000..4b5b06d
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTVMS_WaisUI.h
    @@ -0,0 +1,664 @@
    +/*							HTVMS_WAISUI.h
    + *
    + *	Adaptation for Lynx by F.Macrides (macrides@sci.wfeb.edu)
    + *
    + *	31-May-1994 FM	Initial version.
    + */
    +
    +#ifndef HTVMSWAIS_H
    +#define HTVMSWAIS_H
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    void log_write(char *);
    +
    +/*
    + *	Routines originally from Panic.h -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +    void panic(char *format, ...);
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from CUtil.h -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +/* types and constants */
    +
    +#ifndef boolean
    +#define boolean unsigned long
    +#endif				/* boolean */
    +
    +#ifndef true
    +#define true	(boolean)1L
    +#endif				/* true */
    +
    +#ifndef false
    +#define false	(boolean)0L	/* used to be (!true), but broke
    +				   some compilers */
    +#endif				/* false */
    +
    +#ifndef TRUE
    +#define TRUE	true
    +#endif				/* TRUE */
    +
    +#ifndef FALSE
    +#define FALSE	false
    +#endif				/* FALSE */
    +
    +/*----------------------------------------------------------------------*/
    +/* functions */
    +
    +/* enhanced memory handling functions - don't call them directly, use the
    +   macros below */
    +    void fs_checkPtr(void *ptr);
    +    void *fs_malloc(size_t size);
    +    void *fs_realloc(void *ptr, size_t size);
    +    void fs_free(void *ptr);
    +    char *fs_strncat(char *dst, char *src, size_t maxToAdd, size_t maxTotal);
    +
    +/* macros for memory functions.  call these in your program.  */
    +#define s_checkPtr(ptr)		fs_checkPtr(ptr)
    +#define s_malloc(size)		fs_malloc(size)
    +#define s_realloc(ptr,size)	fs_realloc((ptr),(size))
    +#define s_free(ptr)		{ fs_free((char*)ptr); ptr = NULL; }
    +#define s_strncat(dst,src,maxToAdd,maxTotal)	fs_strncat((dst),(src),(maxToAdd),(maxTotal))
    +
    +    char *s_strdup(char *s);
    +
    +#define IS_DELIMITER	1
    +#define	NOT_DELIMITER	!IS_DELIMITER
    +
    +    char char_downcase(unsigned long ch);
    +    char *string_downcase(char *word);
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from ZUtil.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +/* Data types / constants */
    +
    +/* bytes to leave for the header size info */
    +#define HEADER_LEN	(size_t)2
    +
    +    typedef long pdu_type;
    +
    +#define	initAPDU			(pdu_type)20
    +#define	initResponseAPDU		(pdu_type)21
    +#define	searchAPDU			(pdu_type)22
    +#define	searchResponseAPDU		(pdu_type)23
    +#define	presentAPDU			(pdu_type)24
    +#define	presentResponseAPDU		(pdu_type)25
    +#define	deteteAPDU			(pdu_type)26
    +#define	deleteResponseAPDU		(pdu_type)27
    +#define	accessControlAPDU		(pdu_type)28
    +#define	accessControlResponseAPDU	(pdu_type)29
    +#define	resourceControlAPDU		(pdu_type)30
    +#define	resourceControlResponseAPDU	(pdu_type)31
    +
    +    typedef struct any {	/* an any is a non-ascii string of characters */
    +	unsigned long size;
    +	char *bytes;
    +    } any;
    +
    +    typedef any bit_map;	/* a bit_map is a group of packed bits */
    +
    +    typedef unsigned long data_tag;
    +
    +#define DT_PDUType			(data_tag)1
    +#define	DT_ReferenceID			(data_tag)2
    +#define	DT_ProtocolVersion		(data_tag)3
    +#define	DT_Options			(data_tag)4
    +#define	DT_PreferredMessageSize		(data_tag)5
    +#define	DT_MaximumRecordSize		(data_tag)6
    +#define	DT_IDAuthentication		(data_tag)7
    +#define	DT_ImplementationID		(data_tag)8
    +#define	DT_ImplementationName		(data_tag)9
    +#define	DT_ImplementationVersion	(data_tag)10
    +#define	DT_UserInformationField		(data_tag)11
    +#define	DT_Result			(data_tag)12
    +#define	DT_SmallSetUpperBound		(data_tag)13
    +#define	DT_LargeSetLowerBound		(data_tag)14
    +#define	DT_MediumSetPresentNumber	(data_tag)15
    +#define	DT_ReplaceIndicator		(data_tag)16
    +#define	DT_ResultSetName		(data_tag)17
    +#define	DT_DatabaseNames		(data_tag)18
    +#define DT_ElementSetNames		(data_tag)19
    +#define	DT_QueryType			(data_tag)20
    +#define	DT_Query			(data_tag)21
    +#define	DT_SearchStatus			(data_tag)22
    +#define	DT_ResultCount			(data_tag)23
    +#define	DT_NumberOfRecordsReturned	(data_tag)24
    +#define	DT_NextResultSetPosition	(data_tag)25
    +#define	DT_ResultSetStatus		(data_tag)26
    +#define	DT_PresentStatus		(data_tag)27
    +#define	DT_DatabaseDiagnosticRecords	(data_tag)28
    +#define	DT_NumberOfRecordsRequested	(data_tag)29
    +#define	DT_ResultSetStartPosition	(data_tag)30
    +#define	DT_ResultSetID			(data_tag)31
    +#define	DT_DeleteOperation		(data_tag)32
    +#define	DT_DeleteStatus			(data_tag)33
    +#define	DT_NumberNotDeleted		(data_tag)34
    +#define	DT_BulkStatuses			(data_tag)35
    +#define	DT_DeleteMSG			(data_tag)36
    +#define	DT_SecurityChallenge		(data_tag)37
    +#define	DT_SecurityChallengeResponse	(data_tag)38
    +#define	DT_SuspendedFlag		(data_tag)39
    +#define	DT_ResourceReport		(data_tag)40
    +#define	DT_PartialResultsAvailable	(data_tag)41
    +#define	DT_ContinueFlag			(data_tag)42
    +#define	DT_ResultSetWanted		(data_tag)43
    +
    +#define UNUSED	-1
    +
    +/* number of bytes required to represent the following sizes in compressed
    +   integer format
    + */
    +#define CompressedInt1Byte	128	/* 2 ^ 7 */
    +#define CompressedInt2Byte	16384	/* 2 ^ 14 */
    +#define CompressedInt3Byte	2097152		/* 2 ^ 21 */
    +/* others may follow ... */
    +
    +/* types of query */
    +#define QT_0	"0"		/* query whose non-standard format has been agreed upon
    +				   client and server */
    +/* values for InitAPDU option element */
    +#define	WILL_USE		TRUE
    +#define WILL_NOT_USE		FALSE
    +#define WILL_SUPPORT		TRUE
    +#define WILL_NOT_SUPPORT	FALSE
    +
    +/* values for InitResponseAPDU result element */
    +#define ACCEPT	TRUE
    +#define REJECT	FALSE
    +
    +/* values for SearchResponseAPDU search status element */
    +#define	SUCCESS	0		/* intuitive huh? */
    +#define FAILURE	1
    +
    +/* values for SearchResponseAPDU result set status element */
    +#define	SUBSET	1
    +#define INTERIM	2
    +#define NONE	3
    +
    +/* values for SearchResponseAPDU present status element */
    +/* SUCCESS already defined */
    +#define PARTIAL_1	1
    +#define PARTIAL_2	2
    +#define PARTIAL_3	3
    +#define PARTIAL_4	4
    +#define PS_NONE		5	/* can't use NONE since it was used by result
    +				   set status */
    +
    +#define DIAGNOSTIC_CODE_SIZE	(size_t)3
    +
    +    typedef struct diagnosticRecord {
    +	boolean SURROGATE;
    +	char DIAG[DIAGNOSTIC_CODE_SIZE];
    +	char *ADDINFO;
    +    } diagnosticRecord;
    +
    +#define D_PermanentSystemError	       "S1"
    +#define D_TemporarySystemError         "S2"
    +#define D_UnsupportedSearch            "S3"
    +#define D_TermsOnlyStopWords           "S5"
    +#define D_TooManyArgumentWords         "S6"
    +#define D_TooManyBooleanOperators      "S7"
    +#define D_TooManyTruncatedWords        "S8"
    +#define D_TooMany IncompleteSubfields  "S9"
    +#define D_TruncatedWordsTooShort       "SA"
    +#define D_InvalidFormatForRecordNumber "SB"
    +#define D_TooManyCharactersInSearch    "SC"
    +#define D_TooManyRecordsRetrieved      "SD"
    +#define D_PresentRequestOutOfRange     "SF"
    +#define D_SystemErrorInPresentRecords  "SG"
    +#define D_RecordNotAuthorizedToBeSent  "SH"
    +#define D_RecordExceedsPrefMessageSize "SI"
    +#define D_RecordExceedsMaxRecordSize   "SJ"
    +#define D_ResultSetNotSuppAsSearchTerm "SK"
    +#define D_OnlyOneRsltSetAsSrchTermSupp "SL"
    +#define D_OnlyANDingOfASnglRsltSetSupp "SM"
    +#define D_RsltSetExistsNoReplace       "SN"
    +#define D_ResultSetNamingNotSupported  "SO"
    +#define D_CombinationDatabasesNotSupp  "SP"
    +#define D_ElementSetNamesNotSupported  "SQ"
    +#define D_ElementSetNameNotValid       "SR"
    +#define D_OnlyASingleElmntSetNameSupp  "SS"
    +#define D_ResultSetDeletedByTarget     "ST"
    +#define D_ResultSetIsInUse             "SU"
    +#define D_DatabasesIsLocked            "SV"
    +#define D_TerminatedByNoContinueResp   "SW"
    +#define D_ResultSetDoesNotExist        "SX"
    +#define D_ResExNoResultsAvailable      "SY"
    +#define D_ResExUnpredictableResults    "SZ"
    +#define D_ResExValidSubsetOfResults    "T1"
    +#define D_AccessControlFailure         "T2"
    +#define D_SecurityNotIssuedReqTerm     "T3"
    +#define D_SecurityNotBeIssuedRecNotInc "T4"
    +
    +/*----------------------------------------------------------------------*/
    +
    +/* for internal error handling */
    +
    +    extern char *readErrorPosition;	/* pos where buf stopped making sense */
    +
    +/* the following are macros so that they can return OUT of the function
    +   which calls them
    + */
    +
    +#define RETURN_ON_NULL(var)					\
    +	if (var == NULL)					\
    +	  return(NULL);		/* jump out of caller */
    +
    +#define REPORT_READ_ERROR(pos)					\
    +	{ readErrorPosition = (pos);				\
    +	  return(NULL); /* jump out of caller */		\
    +    }
    +
    +#define CHECK_FOR_SPACE_LEFT(spaceNeeded,spaceLeft)		\
    +	{ if (*spaceLeft >= spaceNeeded)			\
    +	    (*spaceLeft) -= spaceNeeded;			\
    +	  else							\
    +	   { *spaceLeft = 0;					\
    +	     return(NULL); /* jump out of the caller */		\
    +	   }							\
    +	}
    +
    +/*----------------------------------------------------------------------*/
    +
    +    diagnosticRecord *makeDiag(boolean surrogate, char *code, char *addInfo);
    +    void freeDiag(diagnosticRecord * diag);
    +    char *writeDiag(diagnosticRecord * diag, char *buffer, long *len);
    +    char *readDiag(diagnosticRecord ** diag, char *buffer);
    +
    +    char *writeCompressedInteger(unsigned long num, char *buf, long *len);
    +    char *readCompressedInteger(unsigned long *num, char *buf);
    +    char *writeCompressedIntWithPadding(unsigned long num, unsigned long size,
    +					char *buffer, long *len);
    +    unsigned long writtenCompressedIntSize(unsigned long num);
    +
    +    char *writeTag(data_tag tag, char *buf, long *len);
    +    char *readTag(data_tag *tag, char *buf);
    +    data_tag peekTag(char *buf);
    +    unsigned long writtenTagSize(data_tag tag);
    +
    +    any *makeAny(unsigned long size, char *data);
    +    void freeAny(any *a);
    +    any *duplicateAny(any *a);
    +    char *writeAny(any *a, data_tag tag, char *buffer, long *len);
    +    char *readAny(any **anAny, char *buffer);
    +    unsigned long writtenAnySize(data_tag tag, any *a);
    +
    +    any *stringToAny(char *s);
    +    char *anyToString(any *a);
    +    unsigned long writtenStringSize(data_tag tag, char *s);
    +
    +    any *longToAny(long Num);
    +    long anyToLong(any *a);
    +
    +    char *writeString(char *s, data_tag tag, char *buffer, long *len);
    +    char *readString(char **s, char *buffer);
    +
    +    bit_map *makeBitMap(unsigned long numBits, ...);
    +
    +    void freeBitMap(bit_map *bm);
    +    boolean bitAtPos(unsigned long pos, bit_map *bm);
    +    char *writeBitMap(bit_map *bm, data_tag tag, char *buffer, long *len);
    +    char *readBitMap(bit_map **bm, char *buffer);
    +
    +    char *writeByte(unsigned long byte, char *buf, long *len);
    +    char *readByte(unsigned char *byte, char *buf);
    +
    +    char *writeBoolean(boolean flag, char *buf, long *len);
    +    char *readBoolean(boolean *flag, char *buf);
    +
    +    char *writePDUType(pdu_type pduType, char *buf, long *len);
    +    char *readPDUType(pdu_type *pduType, char *buf);
    +    pdu_type peekPDUType(char *buf);
    +
    +    char *writeBinaryInteger(long num, unsigned long size,
    +			     char *buf, long *len);
    +    char *readBinaryInteger(long *num, unsigned long size, char *buf);
    +    unsigned long writtenCompressedBinIntSize(long num);
    +
    +    char *writeNum(long num, data_tag tag, char *buffer, long *len);
    +    char *readNum(long *num, char *buffer);
    +    unsigned long writtenNumSize(data_tag tag, long num);
    +
    +    void doList(void **list, void (*func) (void *));
    +
    +    char *writeProtocolVersion(char *buf, long *len);
    +    char *defaultImplementationID(void);
    +    char *defaultImplementationName(void);
    +    char *defaultImplementationVersion(void);
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from ZType1.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +/* This file implements the type 1 query defined in appendices B & C
    +   of the SR 1 spec.
    + */
    +
    +/*----------------------------------------------------------------------*/
    +/* types and constants */
    +
    +/* new data tags */
    +#define	DT_AttributeList	(data_tag)44
    +#define DT_Term			(data_tag)45
    +#define DT_Operator		(data_tag)46
    +
    +#define QT_BooleanQuery	"1"	/* standard boolean query */
    +
    +/* general attribute code - use in place of any attribute */
    +#define IGNORE			"ig"
    +
    +/* use value codes */
    +#define	UV_ISBN			"ub"
    +#define	CORPORATE_NAME		"uc"
    +#define	ISSN			"us"
    +#define	PERSONAL_NAME		"up"
    +#define	SUBJECT			"uj"
    +#define	TITLE			"ut"
    +#define	GEOGRAPHIC_NAME		"ug"
    +#define	CODEN			"ud"
    +#define	SUBJECT_SUBDIVISION	"ue"
    +#define	SERIES_TITLE		"uf"
    +#define	MICROFORM_GENERATION	"uh"
    +#define	PLACE_OF_PUBLICATION	"ui"
    +#define	NUC_CODE		"uk"
    +#define	LANGUAGE		"ul"
    +#define	COMBINATION_OF_USE_VALUES	"um"
    +#define	SYSTEM_CONTROL_NUMBER	"un"
    +#define	DATE			"uo"
    +#define	LC_CONTROL_NUMBER	"ur"
    +#define	MUSIC_PUBLISHERS_NUMBER	"uu"
    +#define	GOVERNMENT_DOCUMENTS_NUMBER	"uv"
    +#define	SUBJECT_CLASSIFICATION	"uw"
    +#define	RECORD_TYPE		"uy"
    +
    +/* relation value codes */
    +#define	EQUAL			"re"
    +#define	GREATER_THAN		"rg"
    +#define	GREATER_THAN_OR_EQUAL	"ro"
    +#define	LESS_THAN		"rl"
    +#define	LESS_THAN_OR_EQUAL	"rp"
    +#define	NOT_EQUAL		"rn"
    +
    +/* position value codes */
    +#define	FIRST_IN_FIELD		"pf"
    +#define	FIRST_IN_SUBFIELD	"ps"
    +#define	FIRST_IN_A_SUBFIELD	"pa"
    +#define	FIRST_IN_NOT_A_SUBFIELD	"pt"
    +#define	ANY_POSITION_IN_FIELD	"py"
    +
    +/* structure value codes */
    +#define	PHRASE			"sp"
    +#define	WORD			"sw"
    +#define	KEY			"sk"
    +#define	WORD_LIST		"sl"
    +
    +/* truncation value codes */
    +#define	NO_TRUNCATION		"tn"
    +#define	RIGHT_TRUNCATION	"tr"
    +#define	PROC_NUM_INCLUDED_IN_SEARCH_ARG	"ti"
    +
    +/* completeness value codes */
    +#define	INCOMPLETE_SUBFIELD	"ci"
    +#define	COMPLETE_SUBFIELD	"cs"
    +#define	COMPLETEFIELD		"cf"
    +
    +/* operator codes */
    +#define AND	"a"
    +#define OR	"o"
    +#define AND_NOT	"n"
    +
    +/* term types */
    +#define TT_Attribute		1
    +#define	TT_ResultSetID		2
    +#define	TT_Operator		3
    +
    +#define ATTRIBUTE_SIZE		3
    +#define OPERATOR_SIZE		2
    +
    +    typedef struct query_term {
    +	/* type */
    +	long TermType;
    +	/* for term */
    +	char Use[ATTRIBUTE_SIZE + 1];
    +	char Relation[ATTRIBUTE_SIZE + 1];
    +	char Position[ATTRIBUTE_SIZE + 1];
    +	char Structure[ATTRIBUTE_SIZE + 1];
    +	char Truncation[ATTRIBUTE_SIZE + 1];
    +	char Completeness[ATTRIBUTE_SIZE + 1];
    +	any *Term;
    +	/* for result set */
    +	any *ResultSetID;
    +	/* for operator */
    +	char Operator[OPERATOR_SIZE];
    +    } query_term;
    +
    +/*----------------------------------------------------------------------*/
    +/* functions */
    +
    +    query_term *makeAttributeTerm(char *use, char *relation, char *position, char *structure,
    +				  char *truncation, char *completeness, any *term);
    +    query_term *makeResultSetTerm(any *resultSet);
    +    query_term *makeOperatorTerm(char *operatorCode);
    +    void freeTerm(void *qt);
    +    char *writeQueryTerm(query_term *qt, char *buffer, long *len);
    +    char *readQueryTerm(query_term **qt, char *buffer);
    +    any *writeQuery(query_term **terms);
    +    query_term **readQuery(any *info);
    +
    +/*----------------------------------------------------------------------*/
    +
    +/*
    + *	Routines originally from UI.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +    char *generate_retrieval_apdu(char *buff,
    +				  long *buff_len,
    +				  any *docID,
    +				  long chunk_type,
    +				  long start_line, long end_line,
    +				  char *type,
    +				  char *database_name);
    +
    +    long interpret_message(char *request_message,
    +			   long request_length,
    +			   char *response_message,
    +			   long response_buffer_length,		/* length of the buffer (modified) */
    +			   long connection,
    +			   boolean verbose);
    +
    +    char *trim_junk(char *headline);
    +
    +/*
    + *	Routines originally from ZProt.c -- FM
    + *
    + *----------------------------------------------------------------------*/
    +
    +/* APDU types */
    +
    +    typedef struct InitAPDU {
    +	pdu_type PDUType;
    +	boolean willSearch, willPresent, willDelete;
    +	boolean supportAccessControl, supportResourceControl;
    +	long PreferredMessageSize;
    +	long MaximumRecordSize;
    +	char *IDAuthentication;
    +	char *ImplementationID;
    +	char *ImplementationName;
    +	char *ImplementationVersion;
    +	any *ReferenceID;
    +	void *UserInformationField;
    +    } InitAPDU;
    +
    +    typedef struct InitResponseAPDU {
    +	pdu_type PDUType;
    +	boolean Result;
    +	boolean willSearch, willPresent, willDelete;
    +	boolean supportAccessControl, supportResourceControl;
    +	long PreferredMessageSize;
    +	long MaximumRecordSize;
    +	char *IDAuthentication;
    +	char *ImplementationID;
    +	char *ImplementationName;
    +	char *ImplementationVersion;
    +	any *ReferenceID;
    +	void *UserInformationField;
    +    } InitResponseAPDU;
    +
    +    typedef struct SearchAPDU {
    +	pdu_type PDUType;
    +	long SmallSetUpperBound;
    +	long LargeSetLowerBound;
    +	long MediumSetPresentNumber;
    +	boolean ReplaceIndicator;
    +	char *ResultSetName;
    +	char **DatabaseNames;
    +	char *QueryType;
    +	char **ElementSetNames;
    +	any *ReferenceID;
    +	void *Query;
    +    } SearchAPDU;
    +
    +    typedef struct SearchResponseAPDU {
    +	pdu_type PDUType;
    +	long SearchStatus;
    +	long ResultCount;
    +	long NumberOfRecordsReturned;
    +	long NextResultSetPosition;
    +	long ResultSetStatus;
    +	long PresentStatus;
    +	any *ReferenceID;
    +	void *DatabaseDiagnosticRecords;
    +    } SearchResponseAPDU;
    +
    +    typedef struct PresentAPDU {
    +	pdu_type PDUType;
    +	long NumberOfRecordsRequested;
    +	long ResultSetStartPosition;
    +	char *ResultSetID;
    +	char *ElementSetNames;
    +	any *ReferenceID;
    +	void *PresentInfo;
    +    } PresentAPDU;
    +
    +    typedef struct PresentResponseAPDU {
    +	pdu_type PDUType;
    +	boolean PresentStatus;
    +	long NumberOfRecordsReturned;
    +	long NextResultSetPosition;
    +	any *ReferenceID;
    +	void *DatabaseDiagnosticRecords;
    +    } PresentResponseAPDU;
    +
    +/*----------------------------------------------------------------------*/
    +/* Functions */
    +
    +    InitAPDU *makeInitAPDU(boolean search, boolean present, boolean deleteIt,
    +			   boolean accessControl, boolean resourceControl, long prefMsgSize,
    +			   long maxMsgSize,
    +			   char *auth,
    +			   char *id,
    +			   char *name,
    +			   char *version,
    +			   any *refID,
    +			   void *userInfo);
    +    void freeInitAPDU(InitAPDU * init);
    +    char *writeInitAPDU(InitAPDU * init, char *buffer, long *len);
    +    char *readInitAPDU(InitAPDU ** init, char *buffer);
    +
    +    InitResponseAPDU *makeInitResponseAPDU(boolean result,
    +					   boolean search,
    +					   boolean present,
    +					   boolean deleteIt,
    +					   boolean accessControl,
    +					   boolean resourceControl,
    +					   long prefMsgSize,
    +					   long maxMsgSize,
    +					   char *auth,
    +					   char *id,
    +					   char *name,
    +					   char *version,
    +					   any *refID,
    +					   void *userInfo);
    +    void freeInitResponseAPDU(InitResponseAPDU *init);
    +    char *writeInitResponseAPDU(InitResponseAPDU *init, char *buffer, long *len);
    +    char *readInitResponseAPDU(InitResponseAPDU **init, char *buffer);
    +    InitResponseAPDU *replyToInitAPDU(InitAPDU * init, boolean result, void *userInfo);
    +
    +    SearchAPDU *makeSearchAPDU(long small, long large, long medium,
    +			       boolean replace, char *name, char **databases,
    +			       char *type, char **elements, any *refID, void *queryInfo);
    +    void freeSearchAPDU(SearchAPDU *query);
    +    char *writeSearchAPDU(SearchAPDU *query, char *buffer, long *len);
    +    char *readSearchAPDU(SearchAPDU **query, char *buffer);
    +
    +    SearchResponseAPDU *makeSearchResponseAPDU(long result, long count,
    +					       long recordsReturned, long nextPos,
    +					       long resultStatus, long presentStatus,
    +					       any *refID, void *records);
    +    void freeSearchResponseAPDU(SearchResponseAPDU *queryResponse);
    +    char *writeSearchResponseAPDU(SearchResponseAPDU *queryResponse, char
    +				  *buffer, long *len);
    +    char *readSearchResponseAPDU(SearchResponseAPDU **queryResponse, char *buffer);
    +
    +    PresentAPDU *makePresentAPDU(long recsReq, long startPos,
    +				 char *resultID, any *refID, void *info);
    +    void freePresentAPDU(PresentAPDU * present);
    +    char *writePresentAPDU(PresentAPDU * present, char *buffer, long *len);
    +    char *readPresentAPDU(PresentAPDU ** present, char *buffer);
    +
    +    PresentResponseAPDU *makePresentResponseAPDU(boolean status, long recsRet,
    +						 long nextPos, any *refID,
    +						 void *records);
    +    void freePresentResponseAPDU(PresentResponseAPDU * present);
    +    char *writePresentResponseAPDU(PresentResponseAPDU * present, char
    +				   *buffer, long *len);
    +    char *readPresentResponseAPDU(PresentResponseAPDU ** present, char *buffer);
    +
    +/*----------------------------------------------------------------------*/
    +/* user extension hooks: */
    +
    +    extern char *writeInitInfo(InitAPDU * init, char *buffer, long *len);
    +    extern char *readInitInfo(void **info, char *buffer);
    +
    +    extern char *writeInitResponseInfo(InitResponseAPDU *init, char *buffer, long *len);
    +    extern char *readInitResponseInfo(void **info, char *buffer);
    +
    +    extern char *writeSearchInfo(SearchAPDU *query, char *buffer, long *len);
    +    extern char *readSearchInfo(void **info, char *buffer);
    +
    +    extern char *writeSearchResponseInfo(SearchResponseAPDU *query, char
    +					 *buffer, long *len);
    +    extern char *readSearchResponseInfo(void **info, char *buffer);
    +
    +    extern char *writePresentInfo(PresentAPDU * present, char *buffer, long *len);
    +    extern char *readPresentInfo(void **info, char *buffer);
    +
    +    extern char *writePresentResponseInfo(PresentResponseAPDU * present, char
    +					  *buffer, long *len);
    +    extern char *readPresentResponseInfo(void **info, char *buffer);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTVMSWAIS_H */
    diff --git a/WWW/Library/Implementation/HTWAIS.c b/WWW/Library/Implementation/HTWAIS.c
    new file mode 100644
    index 0000000..3c9fb0a
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTWAIS.c
    @@ -0,0 +1,1078 @@
    +/*
    + * $LynxId: HTWAIS.c,v 1.40 2020/01/21 22:22:15 tom Exp $
    + *
    + *	WorldWideWeb - Wide Area Information Server Access	HTWAIS.c
    + *	==================================================
    + *
    + *	This module allows a WWW server or client to read data from a
    + *	remote	WAIS
    + *  server, and provide that data to a WWW client in hypertext form.
    + *  Source files, once retrieved, are stored and used to provide
    + *  information about the index when that is acessed.
    + *
    + *  Authors
    + *	BK	Brewster Kahle, Thinking Machines, <Brewster@think.com>
    + *	TBL	Tim Berners-Lee, CERN <timbl@info.cern.ch>
    + *	FM	Foteos Macrides, WFEB <macrides@sci.wfeb.edu>
    + *
    + *  History
    + *	   Sep 91	TBL adapted shell-ui.c (BK) with HTRetrieve.c from WWW.
    + *	   Feb 91	TBL Generated HTML cleaned up a bit (quotes, escaping)
    + *			    Refers to lists of sources.
    + *	   Mar 93	TBL Lib 2.0 compatible module made.
    + *	   May 94	FM  Added DIRECT_WAIS support for VMS.
    + *
    + *  Bugs
    + *	Uses C stream i/o to read and write sockets, which won't work
    + *	on VMS TCP systems.
    + *
    + *	Should cache connections.
    + *
    + *	ANSI C only as written
    + *
    + *  Bugs fixed
    + *	NT Nathan Torkington (Nathan.Torkington@vuw.ac.nz)
    + *
    + *  WAIS comments:
    + *
    + *	1.	Separate directories for different system's .o would help
    + *	2.	Document ids are rather long!
    + *
    + * W WW Address mapping convention:
    + *
    + *	/servername/database/type/length/document-id
    + *
    + *	/servername/database?word+word+word
    + */
    +/* WIDE AREA INFORMATION SERVER SOFTWARE:
    +   No guarantees or restrictions.  See the readme file for the full standard
    +   disclaimer.
    +
    +   Brewster@think.com
    +*/
    +
    +#include <HTUtils.h>
    +#include <HTParse.h>
    +#include <HTAccess.h>		/* We implement a protocol */
    +#include <HTML.h>		/* The object we will generate */
    +#include <HTWSRC.h>
    +#include <HTTCP.h>
    +#include <HTCJK.h>
    +#include <HTAlert.h>
    +#include <LYStrings.h>
    +
    +#undef lines			/* term.h conflict with wais.h */
    +#undef alloca			/* alloca.h conflict with wais.h */
    +
    +/*			From WAIS
    + *			---------
    + */
    +#ifdef VMS
    +#include <HTVMS_WaisUI.h>
    +#include <HTVMS_WaisProt.h>
    +#elif defined(HAVE_WAIS_H)
    +#include <wais.h>
    +#else
    +#include <ui.h>
    +#endif /* VMS */
    +
    +#define MAX_MESSAGE_LEN 100000
    +#define CHARS_PER_PAGE 10000	/* number of chars retrieved in each request */
    +
    +#define WAISSEARCH_DATE "Fri Jul 19 1991"
    +
    +/*			FROM WWW
    + *			--------
    + */
    +#include <LYUtils.h>
    +#include <LYLeaks.h>
    +
    +#define DIRECTORY "/cnidr.org:210/directory-of-servers"
    +/* #define DIRECTORY "/quake.think.com:210/directory-of-servers" */
    +
    +#define BIG 1024		/* identifier size limit  @@@@@ */
    +
    +#define BUFFER_SIZE 4096	/* Arbitrary size for efficiency */
    +
    +#define HEX_ESCAPE '%'
    +
    +static BOOL as_gate;		/* Client is using us as gateway */
    +
    +static char line[2048];		/* For building strings to display */
    +
    +				/* Must be able to take id */
    +
    +#define PUTC(c) (*target->isa->put_character)(target, c)
    +#define PUTS(s) (*target->isa->put_string)(target, s)
    +#define START(e) (*target->isa->start_element)(target, e, 0, 0, -1, 0)
    +#define END(e) (*target->isa->end_element)(target, e, 0)
    +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
    +			(*target->isa->end_element)(target, e, 0)
    +#define FREE_TARGET (*target->isa->_free)(target)
    +
    +struct _HTStructured {
    +    const HTStructuredClass *isa;
    +    /* ... */
    +};
    +
    +/* ------------------------------------------------------------------------ */
    +/* ---------------- Local copy of connect_to_server calls ----------------- */
    +/* ------------------------------------------------------------------------ */
    +/* Returns 1 on success, 0 on fail, -1 on interrupt. */
    +static int fd_mosaic_connect_to_server(char *host_name,
    +				       long port,
    +				       long *fd)
    +{
    +    char *dummy = NULL;
    +    int status;
    +    int result;
    +
    +    HTSprintf0(&dummy, "%s//%s:%ld/", STR_WAIS_URL, host_name, port);
    +
    +    status = HTDoConnect(dummy, "WAIS", 210, (int *) fd);
    +    if (status == HT_INTERRUPTED) {
    +	result = -1;
    +    } else if (status < 0) {
    +	result = 0;
    +    } else {
    +	result = 1;
    +    }
    +    FREE(dummy);
    +    return result;
    +}
    +
    +/* Returns 1 on success, 0 on fail, -1 on interrupt. */
    +#ifdef VMS
    +static int mosaic_connect_to_server(char *host_name,
    +				    long port,
    +				    long *fdp)
    +#else
    +static int mosaic_connect_to_server(char *host_name,
    +				    long port,
    +				    FILE **fp)
    +#endif				/* VMS */
    +{
    +#ifndef VMS
    +    FILE *file;
    +#endif /* VMS */
    +    long fd;
    +    int rv;
    +
    +    rv = fd_mosaic_connect_to_server(host_name, port, &fd);
    +    if (rv == 0) {
    +	HTAlert(gettext("Could not connect to WAIS server."));
    +	return 0;
    +    } else if (rv == -1) {
    +	HTAlert(CONNECTION_INTERRUPTED);
    +	return -1;
    +    }
    +#ifndef VMS
    +    if ((file = fdopen(fd, "r+")) == NULL) {
    +	HTAlert(gettext("Could not open WAIS connection for reading."));
    +	return 0;
    +    }
    +
    +    *fp = file;
    +#else
    +    *fdp = fd;
    +#endif /* VMS */
    +    return 1;
    +}
    +/* ------------------------------------------------------------------------ */
    +/* ------------------------------------------------------------------------ */
    +
    +/*								showDiags
    +*/
    +/* modified from Jonny G's version in ui/question.c */
    +static void showDiags(HTStream *target, diagnosticRecord ** d)
    +{
    +    long i;
    +
    +    for (i = 0; d[i] != NULL; i++) {
    +	if (d[i]->ADDINFO != NULL) {
    +	    PUTS(gettext("Diagnostic code is "));
    +	    PUTS(d[i]->DIAG);
    +	    PUTC(' ');
    +	    PUTS(d[i]->ADDINFO);
    +	    PUTC('\n');
    +	}
    +    }
    +}
    +
    +/*	Matrix of allowed characters in filenames
    + *	-----------------------------------------
    + */
    +
    +static BOOL acceptable[256];
    +static BOOL acceptable_inited = NO;
    +
    +static void init_acceptable(void)
    +{
    +    unsigned int i;
    +    char *good =
    +    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";
    +
    +    for (i = 0; i < 256; i++)
    +	acceptable[i] = NO;
    +    for (; *good; good++)
    +	acceptable[(unsigned int) *good] = YES;
    +    acceptable_inited = YES;
    +}
    +
    +/*	Transform file identifier into WWW address
    + *	------------------------------------------
    + *
    + *
    + * On exit,
    + *	returns		nil if error
    + *			pointer to malloced string (must be freed) if ok
    + */
    +static char *WWW_from_archie(char *file)
    +{
    +    char *end;
    +    char *result;
    +    char *colon;
    +
    +    for (end = file; *end > ' '; end++) ;	/* assumes ASCII encoding */
    +    result = (char *) malloc(10 + (end - file));
    +    if (!result)
    +	return result;		/* Malloc error */
    +    strcpy(result, "file://");
    +    StrNCat(result, file, end - file);
    +    colon = StrChr(result + 7, ':');	/* Expect colon after host */
    +    if (colon) {
    +	for (; colon[0]; colon[0] = colon[1], colon++) ;	/* move down */
    +    }
    +    return result;
    +}				/* WWW_from_archie */
    +
    +/*	Transform document identifier into URL
    + *	--------------------------------------
    + *
    + *  Bugs: A static buffer of finite size is used!
    + *	The format of the docid MUST be good!
    + *
    + *  On exit,
    + *	returns		nil if error
    + *			pointer to malloced string (must be freed) if ok
    + */
    +static char hex[17] = "0123456789ABCDEF";
    +
    +static char *WWW_from_WAIS(any *docid)
    +{
    +    static char buf[BIG];
    +    char *q = buf;
    +    char *p = (docid->bytes);
    +    char *result = NULL;
    +    int i, l;
    +
    +    if (TRACE) {
    +	char *p2;
    +
    +	fprintf(tfp, "WAIS id (%d bytes) is ", (int) docid->size);
    +	for (p2 = docid->bytes; p2 < docid->bytes + docid->size; p2++) {
    +	    if ((*p2 >= ' ') && (*p2 <= '~'))	/* Assume ASCII! */
    +		fprintf(tfp, "%c", *p2);
    +	    else
    +		fprintf(tfp, "<%x>", (unsigned) *p2);
    +	}
    +	fprintf(tfp, "\n");
    +    }
    +    for (p = docid->bytes;
    +	 (p < docid->bytes + docid->size) && (q < &buf[BIG]);) {
    +	CTRACE((tfp, "    Record type %d, length %d\n", p[0], p[1]));
    +	if (*p > 10) {
    +	    CTRACE((tfp, "Eh?  DOCID record type of %d!\n", *p));
    +	    return 0;
    +	} {			/* Bug fix -- allow any byte value 15 Apr 93 */
    +	    unsigned int i2 = (unsigned) *p++;
    +
    +	    if (i2 > 99) {
    +		*q++ = (i2 / 100) + '0';
    +		i2 = i2 % 100;
    +	    }
    +	    if (i2 > 9) {
    +		*q++ = (i2 / 10) + '0';
    +		i2 = i2 % 10;
    +	    }
    +	    *q++ = i2 + '0';	/* Record type */
    +	}
    +	*q++ = '=';		/* Separate */
    +	l = *p++;		/* Length */
    +	for (i = 0; i < l; i++, p++) {
    +	    if (!acceptable[UCH(*p)]) {
    +		*q++ = HEX_ESCAPE;	/* Means hex coming */
    +		*q++ = hex[(*p) >> 4];
    +		*q++ = hex[(*p) & 15];
    +	    } else
    +		*q++ = *p;
    +	}
    +	*q++ = ';';		/* Terminate field */
    +    }
    +    *q++ = 0;			/* Terminate string */
    +    CTRACE((tfp, "WWW form of id: %s\n", buf));
    +    StrAllocCopy(result, buf);
    +    return result;
    +}				/* WWW_from_WAIS */
    +
    +/*	Transform URL into WAIS document identifier
    + *	-------------------------------------------
    + *
    + *  On entry,
    + *	docname		points to valid name produced originally by
    + *			WWW_from_WAIS
    + *  On exit,
    + *	docid->size	is valid
    + *	docid->bytes	is malloced and must later be freed.
    + */
    +static any *WAIS_from_WWW(any *docid, char *docname)
    +{
    +    char *z;			/* Output pointer */
    +    char *sor;			/* Start of record - points to size field. */
    +    char *p;			/* Input pointer */
    +    char *q;			/* Poisition of "=" */
    +    char *s;			/* Position of semicolon */
    +    int n;			/* size */
    +
    +    CTRACE((tfp, "WWW id (to become WAIS id): %s\n", docname));
    +    for (n = 0, p = docname; *p; p++) {		/* Count sizes of strings */
    +	n++;
    +	if (*p == ';')
    +	    n--;		/* Not converted */
    +	else if (*p == HEX_ESCAPE)
    +	    n = n - 2;		/* Save two bytes */
    +	docid->size = n;
    +    }
    +
    +    if (!(docid->bytes = (char *) malloc(docid->size)))		/* result record */
    +	outofmem(__FILE__, "WAIS_from_WWW");
    +    z = docid->bytes;
    +
    +    for (p = docname; *p;) {	/* Convert of strings */
    +	/* Record type */
    +
    +	*z = 0;			/* Initialize record type */
    +	while (*p >= '0' && *p <= '9') {
    +	    *z = *z * 10 + (*p++ - '0');	/* Decode decimal record type */
    +	}
    +	z++;
    +	if (*p != '=')
    +	    return 0;
    +	q = p;
    +
    +	s = StrChr(q, ';');	/* (Check only) */
    +	if (!s)
    +	    return 0;		/* Bad! No ';'; */
    +	sor = z;		/* Remember where the size field was */
    +	z++;			/* Skip record size for now     */
    +	for (p = q + 1; *p != ';';) {
    +	    if (*p == HEX_ESCAPE) {
    +		char c;
    +		unsigned int b;
    +
    +		p++;
    +		c = *p++;
    +		b = from_hex(c);
    +		c = *p++;
    +		if (!c)
    +		    break;	/* Odd number of chars! */
    +		*z++ = (b << 4) + from_hex(c);
    +	    } else {
    +		*z++ = *p++;	/* Record */
    +	    }
    +	}
    +	*sor = (z - sor - 1);	/* Fill in size -- not counting size itself */
    +	p++;			/* After semicolon: start of next record */
    +    }
    +
    +    if (TRACE) {
    +	char *p2;
    +
    +	fprintf(tfp, "WAIS id (%d bytes) is ", (int) docid->size);
    +	for (p2 = docid->bytes; p2 < docid->bytes + docid->size; p2++) {
    +	    if ((*p2 >= ' ') && (*p2 <= '~'))	/* Assume ASCII! */
    +		fprintf(tfp, "%c", *p2);
    +	    else
    +		fprintf(tfp, "<%x>", (unsigned) *p2);
    +	}
    +	fprintf(tfp, "\n");
    +    }
    +    return docid;		/* Ok */
    +
    +}				/* WAIS_from_WWW */
    +
    +/*	Send a plain text record to the client		output_text_record()
    + *	--------------------------------------
    + */
    +static void output_text_record(HTStream *target,
    +			       WAISDocumentText *record,
    +			       boolean binary)
    +{
    +    unsigned long count;
    +
    +    /* printf(" Text\n");
    +       print_any("     DocumentID:  ", record->DocumentID);
    +       printf("     VersionNumber:  %d\n", record->VersionNumber);
    +     */
    +
    +    if (binary) {
    +	(*target->isa->put_block) (target,
    +				   record->DocumentText->bytes,
    +				   record->DocumentText->size);
    +	return;
    +    }
    +
    +    for (count = 0; count < record->DocumentText->size; count++) {
    +	long ch = (unsigned char) record->DocumentText->bytes[count];
    +
    +	if (ch == 27) {		/* What is this in for?  Tim */
    +	    /* then we have an escape code */
    +	    /* if the next letter is '(' or ')', then ignore two letters */
    +	    if ('(' == record->DocumentText->bytes[count + 1] ||
    +		')' == record->DocumentText->bytes[count + 1])
    +		count += 1;	/* it is a term marker */
    +	    else
    +		count += 4;	/* it is a paragraph marker */
    +	} else if (ch == '\n' || ch == '\r') {
    +	    PUTC('\n');
    +	} else if (IS_CJK_TTY || ch == '\t' || isprint(ch)) {
    +	    PUTC(ch);
    +	}
    +    }
    +}				/* output text record */
    +
    +/*	Format A Search response for the client		display_search_response
    + *	---------------------------------------
    + */
    +/* modified from tracy shen's version in wutil.c
    + * displays either a text record or a set of headlines.
    + */
    +static void display_search_response(HTStructured * target, SearchResponseAPDU *response,
    +				    char *the_database,
    +				    char *keywords)
    +{
    +    WAISSearchResponse *info;
    +    long i, k;
    +
    +    BOOL archie = strstr(the_database, "archie") != 0;	/* Special handling */
    +
    +    CTRACE((tfp, "HTWAIS: Displaying search response\n"));
    +    PUTS(gettext("Index "));
    +    START(HTML_EM);
    +    PUTS(the_database);
    +    END(HTML_EM);
    +    sprintf(line, gettext(" contains the following %d item%s relevant to \""),
    +	    (int) (response->NumberOfRecordsReturned),
    +	    response->NumberOfRecordsReturned == 1 ? "" : "s");
    +    PUTS(line);
    +    START(HTML_EM);
    +    PUTS(keywords);
    +    END(HTML_EM);
    +    PUTS("\".\n");
    +    PUTS(gettext("The first figure after each entry is its relative score, "));
    +    PUTS(gettext("the second is the number of lines in the item."));
    +    START(HTML_BR);
    +    START(HTML_BR);
    +    PUTC('\n');
    +    START(HTML_OL);
    +
    +    if (response->DatabaseDiagnosticRecords != 0) {
    +	info = (WAISSearchResponse *) response->DatabaseDiagnosticRecords;
    +	i = 0;
    +
    +	if (info->Diagnostics != NULL)
    +	    showDiags((HTStream *) target, info->Diagnostics);
    +
    +	if (info->DocHeaders != 0) {
    +	    for (k = 0; info->DocHeaders[k] != 0; k++) {
    +		WAISDocumentHeader *head = info->DocHeaders[k];
    +		char *headline = trim_junk(head->Headline);
    +		any *docid = head->DocumentID;
    +		char *docname;	/* printable version of docid */
    +
    +		i++;
    +		/*
    +		 * Make a printable string out of the document id.
    +		 */
    +		CTRACE((tfp, "HTWAIS:  %2ld: Score: %4ld, lines:%4ld '%s'\n",
    +			i,
    +			(long int) (info->DocHeaders[k]->Score),
    +			(long int) (info->DocHeaders[k]->Lines),
    +			headline));
    +
    +		START(HTML_LI);
    +
    +		if (archie) {
    +		    char *www_name = WWW_from_archie(headline);
    +
    +		    if (www_name) {
    +			HTStartAnchor(target, NULL, www_name);
    +			PUTS(headline);
    +			END(HTML_A);
    +			FREE(www_name);
    +		    } else {
    +			PUTS(headline);
    +			PUTS(gettext(" (bad file name)"));
    +		    }
    +		} else {	/* Not archie */
    +		    docname = WWW_from_WAIS(docid);
    +		    if (docname) {
    +			if ((head->Types) &&
    +			    (!strcmp(head->Types[0], "URL"))) {
    +			    HTStartAnchor(target, NULL, headline);
    +			} else {
    +			    char *dbname = HTEscape(the_database, URL_XPALPHAS);
    +			    char *w3_address = NULL;
    +
    +			    HTSprintf0(&w3_address,
    +				       "/%s/%s/%d/%s",
    +				       dbname,
    +				       head->Types ? head->Types[0] : "TEXT",
    +				       (int) (head->DocumentLength),
    +				       docname);
    +			    HTStartAnchor(target, NULL, w3_address);
    +			    FREE(w3_address);
    +			    FREE(dbname);
    +			}
    +			PUTS(headline);
    +			END(HTML_A);
    +			FREE(docname);
    +		    } else {
    +			PUTS(gettext("(bad doc id)"));
    +		    }
    +		}
    +
    +		sprintf(line, "%5ld  %5ld  ",
    +			head->Score,
    +			head->Lines);
    +		PUTS(line);
    +		MAYBE_END(HTML_LI);
    +	    }			/* next document header */
    +	}
    +	/* if there were any document headers */
    +	if (info->ShortHeaders != 0) {
    +	    k = 0;
    +	    while (info->ShortHeaders[k] != 0) {
    +		i++;
    +		PUTS(gettext("(Short Header record, can't display)"));
    +	    }
    +	}
    +	if (info->LongHeaders != 0) {
    +	    k = 0;
    +	    while (info->LongHeaders[k] != 0) {
    +		i++;
    +		PUTS(gettext("\nLong Header record, can't display\n"));
    +	    }
    +	}
    +	if (info->Text != 0) {
    +	    k = 0;
    +	    while (info->Text[k] != 0) {
    +		i++;
    +		PUTS(gettext("\nText record\n"));
    +		output_text_record((HTStream *) target,
    +				   info->Text[k++], false);
    +	    }
    +	}
    +	if (info->Headlines != 0) {
    +	    k = 0;
    +	    while (info->Headlines[k] != 0) {
    +		i++;
    +		PUTS(gettext("\nHeadline record, can't display\n"));
    +		/* dsply_headline_record( info->Headlines[k++]); */
    +	    }
    +	}
    +	if (info->Codes != 0) {
    +	    k = 0;
    +	    while (info->Codes[k] != 0) {
    +		i++;
    +		PUTS(gettext("\nCode record, can't display\n"));
    +		/* dsply_code_record( info->Codes[k++]); */
    +	    }
    +	}
    +    }				/* Loop: display user info */
    +    END(HTML_OL);
    +    PUTC('\n');
    +}
    +
    +/*		Load by name					HTLoadWAIS
    + *		============
    + *
    + *  This renders any object or search as required.
    + */
    +int HTLoadWAIS(const char *arg,
    +	       HTParentAnchor *anAnchor,
    +	       HTFormat format_out,
    +	       HTStream *sink)
    +#define MAX_KEYWORDS_LENGTH 1000
    +#define MAX_SERVER_LENGTH 1000
    +#define MAX_DATABASE_LENGTH 1000
    +#define MAX_SERVICE_LENGTH 1000
    +#define MAXDOCS 200
    +
    +{
    +    char *key;			/* pointer to keywords in URL */
    +    char *request_message = NULL;	/* arbitrary message limit */
    +    char *response_message = NULL;	/* arbitrary message limit */
    +    long request_buffer_length;	/* how of the request is left */
    +    SearchResponseAPDU *retrieval_response = 0;
    +    char keywords[MAX_KEYWORDS_LENGTH + 1];
    +    char *the_server_name;
    +    char *wais_database = NULL;	/* name of current database */
    +    char *www_database;		/* Same name escaped */
    +    char *service;
    +    char *doctype;
    +    char *doclength;
    +    long document_length = 0;
    +    char *docname = 0;
    +
    +#ifdef VMS
    +    long connection = 0;
    +
    +#else
    +    FILE *connection = NULL;
    +#endif /* VMS */
    +    char *names;		/* Copy of arg to be hacked up */
    +    BOOL ok = NO;
    +    int return_status = HT_LOADED;
    +    int rv;
    +
    +    if (!acceptable_inited)
    +	init_acceptable();
    +
    +    /* Decipher and check syntax of WWW address:
    +     * ----------------------------------------
    +     *
    +     * First we remove the "wais:" if it was specified.  920110
    +     */
    +    names = HTParse(arg, "", PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
    +    key = StrChr(names, '?');
    +
    +    if (key) {
    +	char *p;
    +
    +	*key++ = 0;		/* Split off keywords */
    +	for (p = key; *p; p++)
    +	    if (*p == '+')
    +		*p = ' ';
    +	HTUnEscape(key);
    +    }
    +    if (names[0] == '/') {
    +	the_server_name = names + 1;
    +	if ((as_gate = (*the_server_name == '/')) != 0)
    +	    the_server_name++;	/* Accept one or two */
    +	www_database = StrChr(the_server_name, '/');
    +	if (www_database) {
    +	    *www_database++ = 0;	/* Separate database name */
    +	    doctype = StrChr(www_database, '/');
    +	    if (key)
    +		ok = YES;	/* Don't need doc details */
    +	    else if (doctype) {	/* If not search parse doc details */
    +		*doctype++ = 0;	/* Separate rest of doc address */
    +		doclength = StrChr(doctype, '/');
    +		if (doclength) {
    +		    *doclength++ = 0;
    +		    document_length = atol(doclength);
    +		    if (document_length) {
    +			docname = StrChr(doclength, '/');
    +			if (docname) {
    +			    *docname++ = 0;
    +			    ok = YES;	/* To avoid a goto! */
    +			}	/* if docname */
    +		    }		/* if document_length valid */
    +		}		/* if doclength */
    +	    } else {		/* no doctype?  Assume index required */
    +		if (!key)
    +		    key = "";
    +		ok = YES;
    +	    }			/* if doctype */
    +	}			/* if database */
    +    }
    +
    +    if (!ok)
    +	return HTLoadError(sink, 500, gettext("Syntax error in WAIS URL"));
    +
    +    CTRACE((tfp, "HTWAIS: Parsed OK\n"));
    +
    +    service = StrChr(names, ':');
    +    if (service)
    +	*service++ = 0;
    +    else
    +	service = "210";
    +
    +    if (the_server_name[0] == 0) {
    +#ifdef VMS
    +	connection = 0;
    +#else
    +	connection = NULL;
    +#endif /* VMS */
    +
    +    } else if (!(key && !*key)) {
    +	int status;
    +
    +	CTRACE((tfp, "===WAIS=== calling mosaic_connect_to_server\n"));
    +	status = mosaic_connect_to_server(the_server_name,
    +					  atoi(service),
    +					  &connection);
    +	if (status == 0) {
    +	    CTRACE((tfp, "===WAIS=== connection failed\n"));
    +	    FREE(names);
    +	    return HT_NOT_LOADED;
    +	} else if (status == -1) {
    +	    CTRACE((tfp, "===WAIS=== connection interrupted\n"));
    +	    FREE(names);
    +	    return HT_NOT_LOADED;
    +	}
    +    }
    +
    +    StrAllocCopy(wais_database, www_database);
    +    HTUnEscape(wais_database);
    +
    +    /*
    +     * This below fixed size stuff is terrible.
    +     */
    +#ifdef VMS
    +    if ((request_message = typecallocn(char, MAX_MESSAGE_LEN)) == 0)
    +	  outofmem(__FILE__, "HTLoadWAIS");
    +    if ((response_message = typecallocn(char, MAX_MESSAGE_LEN)) == 0)
    +	  outofmem(__FILE__, "HTLoadWAIS");
    +
    +#else
    +    request_message = (char *) s_malloc((size_t) MAX_MESSAGE_LEN * sizeof(char));
    +    response_message = (char *) s_malloc((size_t) MAX_MESSAGE_LEN * sizeof(char));
    +#endif /* VMS */
    +
    +    /*
    +     * If keyword search is performed but there are no keywords, the user has
    +     * followed a link to the index itself.  It would be appropriate at this
    +     * point to send him the .SRC file - how?
    +     */
    +    if (key && !*key) {		/* I N D E X */
    +#ifdef CACHE_FILE_PREFIX
    +	char *filename = NULL;
    +	FILE *fp;
    +#endif
    +	HTStructured *target = HTML_new(anAnchor, format_out, sink);
    +
    +	START(HTML_HEAD);
    +	PUTC('\n');
    +	HTStartIsIndex(target, HTWAIS_SOLICIT_QUERY, NULL);
    +	PUTC('\n');
    +
    +	{
    +	    START(HTML_TITLE);
    +	    PUTS(wais_database);
    +	    PUTS(gettext(" (WAIS Index)"));
    +	    END(HTML_TITLE);
    +	    PUTC('\n');
    +	    END(HTML_HEAD);
    +	    PUTC('\n');
    +
    +	    START(HTML_H1);
    +	    PUTS(gettext("WAIS Index: "));
    +	    START(HTML_EM);
    +	    PUTS(wais_database);
    +	    END(HTML_EM);
    +	    END(HTML_H1);
    +	    PUTC('\n');
    +	    PUTS(gettext("This is a link for searching the "));
    +	    START(HTML_EM);
    +	    PUTS(wais_database);
    +	    END(HTML_EM);
    +	    PUTS(gettext(" WAIS Index.\n"));
    +
    +	}
    +	/*
    +	 * If we have seen a source file for this database, use that.
    +	 */
    +#ifdef CACHE_FILE_PREFIX
    +	HTSprintf0(&filename, "%sWSRC-%s:%s:%.100s.txt",
    +		   CACHE_FILE_PREFIX,
    +		   the_server_name, service, www_database);
    +
    +	fp = fopen(filename, "r");	/* Have we found this already? */
    +	CTRACE((tfp, "HTWAIS: Description of server %s %s.\n",
    +		filename,
    +		fp ? "exists already" : "does NOT exist!"));
    +
    +	if (fp) {
    +	    char c;
    +
    +	    START(HTML_PRE);	/* Preformatted description */
    +	    PUTC('\n');
    +	    while ((c = getc(fp)) != EOF)
    +		PUTC(c);	/* Transfer file */
    +	    END(HTML_PRE);
    +	    fclose(fp);
    +	}
    +	FREE(filename);
    +#endif
    +	START(HTML_P);
    +	PUTS(gettext("\nEnter the 's'earch command and then specify search words.\n"));
    +
    +	FREE_TARGET;
    +    } else if (key) {		/* S E A R C H */
    +	char *p;
    +	HTStructured *target;
    +
    +	LYStrNCpy(keywords, key, MAX_KEYWORDS_LENGTH);
    +	while ((p = StrChr(keywords, '+')) != 0)
    +	    *p = ' ';
    +
    +	/*
    +	 * Send advance title to get something fast to the other end.
    +	 */
    +	target = HTML_new(anAnchor, format_out, sink);
    +
    +	START(HTML_HEAD);
    +	PUTC('\n');
    +	HTStartIsIndex(target, HTWAIS_SOLICIT_QUERY, NULL);
    +	PUTC('\n');
    +	START(HTML_TITLE);
    +	PUTS(keywords);
    +	PUTS(gettext(" (in "));
    +	PUTS(wais_database);
    +	PUTC(')');
    +	END(HTML_TITLE);
    +	PUTC('\n');
    +	END(HTML_HEAD);
    +	PUTC('\n');
    +
    +	START(HTML_H1);
    +	PUTS(gettext("WAIS Search of \""));
    +	START(HTML_EM);
    +	PUTS(keywords);
    +	END(HTML_EM);
    +	PUTS(gettext("\" in: "));
    +	START(HTML_EM);
    +	PUTS(wais_database);
    +	END(HTML_EM);
    +	END(HTML_H1);
    +	PUTC('\n');
    +
    +	request_buffer_length = MAX_MESSAGE_LEN;	/* Amount left */
    +	CTRACE((tfp, "HTWAIS: Search for `%s' in `%s'\n",
    +		keywords, wais_database));
    +	if (NULL ==
    +	    generate_search_apdu(request_message + HEADER_LENGTH,
    +				 &request_buffer_length,
    +				 keywords, wais_database, NULL, MAXDOCS)) {
    +#ifdef VMS
    +	    HTAlert(gettext("HTWAIS: Request too large."));
    +	    return_status = HT_NOT_LOADED;
    +	    FREE_TARGET;
    +	    goto CleanUp;
    +#else
    +	    panic("request too large");
    +#endif /* VMS */
    +	}
    +
    +	HTProgress(gettext("Searching WAIS database..."));
    +	rv = interpret_message(request_message,
    +			       MAX_MESSAGE_LEN - request_buffer_length,
    +			       response_message,
    +			       MAX_MESSAGE_LEN,
    +			       connection,
    +			       false	/* true verbose */
    +	    );
    +
    +	if (rv == HT_INTERRUPTED) {
    +	    HTAlert(gettext("Search interrupted."));
    +	    return_status = HT_INTERRUPTED;
    +	    FREE_TARGET;
    +	    goto CleanUp;
    +	} else if (!rv) {
    +#ifdef VMS
    +	    HTAlert(HTWAIS_MESSAGE_TOO_BIG);
    +	    return_status = HT_NOT_LOADED;
    +	    FREE_TARGET;
    +	    goto CleanUp;
    +#else
    +	    panic("returned message too large");
    +#endif /* VMS */
    +	} else {		/* returned message ok */
    +	    SearchResponseAPDU *query_response = 0;
    +
    +	    readSearchResponseAPDU(&query_response,
    +				   response_message + HEADER_LENGTH);
    +	    display_search_response(target,
    +				    query_response, wais_database, keywords);
    +	    if (query_response->DatabaseDiagnosticRecords)
    +		freeWAISSearchResponse(query_response->DatabaseDiagnosticRecords);
    +	    freeSearchResponseAPDU(query_response);
    +	}			/* returned message not too large */
    +	FREE_TARGET;
    +    } else {			/* D O C U M E N T    F E T C H */
    +	HTFormat format_in;
    +	boolean binary;		/* how to transfer stuff coming over */
    +	HTStream *target;
    +	long count;
    +	any doc_chunk;
    +	any *docid = &doc_chunk;
    +
    +	CTRACE((tfp,
    +		"HTWAIS: Retrieve document id `%s' type `%s' length %ld\n",
    +		NonNull(docname), doctype, document_length));
    +
    +	format_in =
    +	    !strcmp(doctype, "WSRC") ? HTAtom_for("application/x-wais-source") :
    +	    !strcmp(doctype, "TEXT") ? HTAtom_for(STR_PLAINTEXT) :
    +	    !strcmp(doctype, "HTML") ? HTAtom_for(STR_HTML) :
    +	    !strcmp(doctype, "GIF") ? HTAtom_for("image/gif") :
    +	    HTAtom_for(STR_BINARY);
    +	binary =
    +	    0 != strcmp(doctype, "WSRC") &&
    +	    0 != strcmp(doctype, "TEXT") &&
    +	    0 != strcmp(doctype, "HTML");
    +
    +	target = HTStreamStack(format_in, format_out, sink, anAnchor);
    +	if (!target)
    +	    return HTLoadError(sink, 500,
    +			       gettext("Can't convert format of WAIS document"));
    +	/*
    +	 * Decode hex or literal format for document ID.
    +	 */
    +	WAIS_from_WWW(docid, docname);
    +
    +	/*
    +	 * Loop over slices of the document.
    +	 */
    +	for (count = 0;
    +	     count * CHARS_PER_PAGE < document_length;
    +	     count++) {
    +#ifdef VMS
    +	    char *type = NULL;
    +
    +	    StrAllocCopy(type, doctype);
    +#else
    +	    char *type = s_strdup(doctype);	/* Gets freed I guess */
    +#endif /* VMS */
    +	    request_buffer_length = MAX_MESSAGE_LEN;	/* Amount left */
    +	    CTRACE((tfp, "HTWAIS: Slice number %ld\n", count));
    +
    +	    if (HTCheckForInterrupt()) {
    +		HTAlert(TRANSFER_INTERRUPTED);
    +		(*target->isa->_abort) (target, NULL);
    +#ifdef VMS
    +		FREE(type);
    +#endif /* VMS */
    +		return_status = HT_NOT_LOADED;
    +		goto CleanUp;
    +	    }
    +
    +	    if (0 ==
    +		generate_retrieval_apdu(request_message + HEADER_LENGTH,
    +					&request_buffer_length,
    +					docid,
    +					CT_byte,
    +					count * CHARS_PER_PAGE,
    +					(((count + 1) * CHARS_PER_PAGE <= document_length)
    +					 ? (count + 1) * CHARS_PER_PAGE
    +					 : document_length),
    +					type,
    +					wais_database)) {
    +#ifdef VMS
    +		HTAlert(gettext("HTWAIS: Request too long."));
    +		return_status = HT_NOT_LOADED;
    +		FREE_TARGET;
    +		FREE(type);
    +		FREE(docid->bytes);
    +		goto CleanUp;
    +#else
    +		panic("request too long");
    +#endif /* VMS */
    +	    }
    +
    +	    /*
    +	     * Actually do the transaction given by request_message.
    +	     */
    +	    HTProgress(gettext("Fetching WAIS document..."));
    +	    rv = interpret_message(request_message,
    +				   MAX_MESSAGE_LEN - request_buffer_length,
    +				   response_message,
    +				   MAX_MESSAGE_LEN,
    +				   connection,
    +				   false	/* true verbose */
    +		);
    +	    if (rv == HT_INTERRUPTED) {
    +		HTAlert(TRANSFER_INTERRUPTED);
    +		return_status = HT_INTERRUPTED;
    +		FREE_TARGET;
    +		FREE(type);
    +		FREE(docid->bytes);
    +		goto CleanUp;
    +	    } else if (!rv) {
    +#ifdef VMS
    +		HTAlert(HTWAIS_MESSAGE_TOO_BIG);
    +		return_status = HT_NOT_LOADED;
    +		FREE_TARGET;
    +		FREE(type);
    +		FREE(docid->bytes);
    +		goto CleanUp;
    +#else
    +		panic("Returned message too large");
    +#endif /* VMS */
    +	    }
    +
    +	    /*
    +	     * Parse the result which came back into memory.
    +	     */
    +	    readSearchResponseAPDU(&retrieval_response,
    +				   response_message + HEADER_LENGTH);
    +
    +	    if (NULL ==
    +		((WAISSearchResponse *)
    +		 retrieval_response->DatabaseDiagnosticRecords)->Text) {
    +		/* display_search_response(target, retrieval_response,
    +		   wais_database, keywords); */
    +		PUTS(gettext("No text was returned!\n"));
    +		/* panic("No text was returned"); */
    +	    } else {
    +		output_text_record(target,
    +				   ((WAISSearchResponse *)
    +				    retrieval_response->DatabaseDiagnosticRecords)->Text[0],
    +				   binary);
    +	    }			/* If text existed */
    +
    +#ifdef VMS
    +	    FREE(type);
    +#endif /* VMS */
    +	}			/* Loop over slices */
    +
    +	FREE_TARGET;
    +	FREE(docid->bytes);
    +
    +	freeWAISSearchResponse(retrieval_response->DatabaseDiagnosticRecords);
    +	freeSearchResponseAPDU(retrieval_response);
    +
    +    }				/* If document rather than search */
    +
    +  CleanUp:
    +    /*
    +     * (This postponed until later, after a timeout:)
    +     */
    +#ifdef VMS
    +    if (connection)
    +	NETCLOSE((int) connection);
    +#else
    +    if (connection)
    +	fclose(connection);
    +#endif /* VMS */
    +    FREE(wais_database);
    +#ifdef VMS
    +    FREE(request_message);
    +    FREE(response_message);
    +#else
    +    s_free(request_message);
    +    s_free(response_message);
    +#endif /* VMS */
    +    FREE(names);
    +    return (return_status);
    +}
    +
    +#ifdef GLOBALDEF_IS_MACRO
    +#define _HTWAIS_C_1_INIT { "wais", HTLoadWAIS, NULL }
    +GLOBALDEF(HTProtocol, HTWAIS, _HTWAIS_C_1_INIT);
    +#else
    +GLOBALDEF HTProtocol HTWAIS =
    +{"wais", HTLoadWAIS, NULL};
    +#endif /* GLOBALDEF_IS_MACRO */
    diff --git a/WWW/Library/Implementation/HTWAIS.h b/WWW/Library/Implementation/HTWAIS.h
    new file mode 100644
    index 0000000..4ba6f67
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTWAIS.h
    @@ -0,0 +1,43 @@
    +/*                                                    WAIS protocol module for the W3 library
    +                                 WAIS PROTOCOL INTERFACE
    +
    +   This module does not actually perform the WAIS protocol directly, but it does using one
    +   or more libraries of the freeWAIS distribution.  The ui.a library came with the old free
    +   WAIS from TMC,  the client.a and wais.a libraries are needed from the freeWAIS from
    +   CNIDR.
    +
    +   If you include this module in the library, you must also
    +
    +      Register the HTWAIS protocol at initialisation (e.g., HTInit or HTSInit) by compiling
    +      it with -DDIRECT_WAIS
    +
    +      Link with the WAIS libraries
    +
    +   The wais source files are parsed by a separate and independent module, HTWSRC .   You
    +   can include HTWSRC without including direct wais using this module, and your WWW code
    +   will be able to read source files, and access WAIS indexes through a gateway.
    +
    +   A WAIS-WWW gateway is just a normal W3 server with a libwww compiled with this module.
    +
    +   Anyways, this interface won't change much:
    +
    + */
    +#ifndef HTWAIS_H
    +#define HTWAIS_H
    +
    +#include <HTAccess.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#ifdef GLOBALREF_IS_MACRO
    +    extern GLOBALREF (HTProtocol, HTWAIS);
    +
    +#else
    +    GLOBALREF HTProtocol HTWAIS;
    +#endif				/* GLOBALDEF_IS_MACRO */
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTWAIS_H */
    diff --git a/WWW/Library/Implementation/HTWSRC.c b/WWW/Library/Implementation/HTWSRC.c
    new file mode 100644
    index 0000000..ba12cc7
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTWSRC.c
    @@ -0,0 +1,486 @@
    +/*
    + * $LynxId: HTWSRC.c,v 1.31 2020/01/21 22:05:46 tom Exp $
    + *
    + *			Parse WAIS Source file			HTWSRC.c
    + *			======================
    + *
    + *	This module parses a stream with WAIS source file
    + *	format information on it and creates a structured stream.
    + *	That structured stream is then converted into whatever.
    + *
    + *	3 June 93	Bug fix: Won't crash if no description
    + */
    +
    +#define HTSTREAM_INTERNAL 1
    +
    +#include <HTUtils.h>
    +
    +#include <HTWSRC.h>
    +#include <LYUtils.h>
    +
    +#include <HTML.h>
    +#include <HTParse.h>
    +
    +#include <LYLeaks.h>
    +
    +#define BIG 10000		/* Arbitrary limit to value length */
    +#define PARAM_MAX BIG
    +#define CACHE_PERIOD (7*86400)	/* Time to keep .src file in seconds */
    +
    +struct _HTStructured {
    +    const HTStructuredClass *isa;
    +    /* ... */
    +};
    +
    +#define PUTC(c) (*me->target->isa->put_character)(me->target, c)
    +#define PUTS(s) (*me->target->isa->put_string)(me->target, s)
    +#define START(e) (*me->target->isa->start_element)(me->target, e, 0, 0, -1, 0)
    +#define END(e) (*me->target->isa->end_element)(me->target, e, 0)
    +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
    +			(*me->target->isa->end_element)(me->target, e, 0)
    +
    +/*	Here are the parameters which can be specified in a  source file
    +*/
    +static const char *par_name[] =
    +{
    +    "version",
    +    "ip-address",
    +#define PAR_IP_NAME 2
    +    "ip-name",
    +#define PAR_TCP_PORT 3
    +    "tcp-port",
    +#define PAR_DATABASE_NAME 4
    +    "database-name",
    +#define PAR_COST 5
    +    "cost",
    +#define PAR_COST_UNIT 6
    +    "cost-unit",
    +#define PAR_FREE 7
    +    "free",
    +#define PAR_MAINTAINER 8
    +    "maintainer",
    +#define PAR_DESCRIPTION 9
    +    "description",
    +    "keyword-list",
    +    "source",
    +    "window-geometry",
    +    "configuration",
    +    "script",
    +    "update-time",
    +    "contact-at",
    +    "last-contacted",
    +    "confidence",
    +    "num-docs-to-request",
    +    "font",
    +    "font-size",
    +#define PAR_UNKNOWN 22
    +    "unknown",
    +    0,				/* Terminate list */
    +#define PAR_COUNT 23
    +};
    +
    +enum tokenstate {
    +    beginning,
    +    before_tag,
    +    colon,
    +    before_value,
    +    value,
    +    bracketed_value,
    +    quoted_value,
    +    escape_in_quoted,
    +    done
    +};
    +
    +/*		Stream Object
    + *		------------
    + *
    + *	The target is the structured stream down which the
    + *	parsed results will go.
    + *
    + *	all the static stuff below should go in here to make it reentrant
    + */
    +
    +struct _HTStream {
    +    const HTStreamClass *isa;
    +    HTStructured *target;
    +    char *par_value[PAR_COUNT];
    +    enum tokenstate state;
    +    char param[BIG + 1];
    +    int param_number;
    +    int param_count;
    +};
    +
    +/*	Decode one hex character
    +*/
    +char from_hex(char c)
    +{
    +    return (char) ((c >= '0') && (c <= '9') ? c - '0'
    +		   : (c >= 'A') && (c <= 'F') ? c - 'A' + 10
    +		   : (c >= 'a') && (c <= 'f') ? c - 'a' + 10
    +		   : 0);
    +}
    +
    +/*			State machine
    + *			-------------
    + *
    + * On entry,
    + *	me->state	is a valid state (see WSRC_init)
    + *	c		is the next character
    + * On exit,
    + *	returns 1	Done with file
    + *		0	Continue. me->state is updated if necessary.
    + *		-1	Syntax error error
    + */
    +
    +/*		Treat One Character
    + *		-------------------
    + */
    +static void WSRCParser_put_character(HTStream *me, int c)
    +{
    +    switch (me->state) {
    +    case beginning:
    +	if (c == '(')
    +	    me->state = before_tag;
    +	break;
    +
    +    case before_tag:
    +	if (c == ')') {
    +	    me->state = done;
    +	    return;		/* Done with input file */
    +	} else if (c == ':') {
    +	    me->param_count = 0;
    +	    me->state = colon;
    +	}			/* Ignore other text */
    +	break;
    +
    +    case colon:
    +	if (WHITE(c)) {
    +	    me->param[me->param_count++] = 0;	/* Terminate */
    +	    for (me->param_number = 0;
    +		 par_name[me->param_number];
    +		 me->param_number++) {
    +		if (0 == strcmp(par_name[me->param_number], me->param)) {
    +		    break;
    +		}
    +	    }
    +	    if (!par_name[me->param_number]) {	/* Unknown field */
    +		CTRACE((tfp, "HTWSRC: Unknown field `%s' in source file\n",
    +			me->param));
    +		me->param_number = PAR_UNKNOWN;
    +		me->state = before_value;	/* Could be better ignore */
    +		return;
    +	    }
    +	    me->state = before_value;
    +	} else {
    +	    if (me->param_count < PARAM_MAX)
    +		me->param[me->param_count++] = (char) c;
    +	}
    +	break;
    +
    +    case before_value:
    +	if (c == ')') {
    +	    me->state = done;
    +	    return;		/* Done with input file */
    +	}
    +	if (WHITE(c))
    +	    return;		/* Skip white space */
    +	me->param_count = 0;
    +	if (c == '"') {
    +	    me->state = quoted_value;
    +	} else {
    +	    me->state = ((c == '(')
    +			 ? bracketed_value
    +			 : value);
    +	    me->param[me->param_count++] = (char) c;	/* Don't miss first character */
    +	}
    +	break;
    +
    +    case value:
    +	if (WHITE(c)) {
    +	    me->param[me->param_count] = 0;
    +	    StrAllocCopy(me->par_value[me->param_number], me->param);
    +	    me->state = before_tag;
    +	} else {
    +	    if (me->param_count < PARAM_MAX)
    +		me->param[me->param_count++] = (char) c;
    +	}
    +	break;
    +
    +    case bracketed_value:
    +	if (c == ')') {
    +	    me->param[me->param_count] = 0;
    +	    StrAllocCopy(me->par_value[me->param_number], me->param);
    +	    me->state = before_tag;
    +	    break;
    +	}
    +	if (me->param_count < PARAM_MAX)
    +	    me->param[me->param_count++] = (char) c;
    +	break;
    +
    +    case quoted_value:
    +	if (c == '"') {
    +	    me->param[me->param_count] = 0;
    +	    StrAllocCopy(me->par_value[me->param_number], me->param);
    +	    me->state = before_tag;
    +	    break;
    +	}
    +
    +	if (c == '\\') {	/* Ignore escape but switch state */
    +	    me->state = escape_in_quoted;
    +	    break;
    +	}
    +	/* Fall through! */
    +
    +    case escape_in_quoted:
    +	if (me->param_count < PARAM_MAX)
    +	    me->param[me->param_count++] = (char) c;
    +	me->state = quoted_value;
    +	break;
    +
    +    case done:			/* Ignore anything after EOF */
    +	return;
    +
    +    }				/* switch me->state */
    +}
    +
    +/*			Open Cache file
    + *			===============
    + *
    + *   Bugs: Maybe for filesystem-challenged platforms (MSDOS for example) we
    + *   should make a hash code for the filename.
    + */
    +
    +#ifdef CACHE_FILE_PREFIX
    +static BOOL write_cache(HTStream *me)
    +{
    +    FILE *fp;
    +    char *cache_file_name = NULL;
    +    char *www_database;
    +    int result = NO;
    +
    +    if (!me->par_value[PAR_DATABASE_NAME]
    +	|| !me->par_value[PAR_IP_NAME]
    +	)
    +	return NO;
    +
    +    www_database = HTEscape(me->par_value[PAR_DATABASE_NAME], URL_XALPHAS);
    +    HTSprintf0(&cache_file_name, "%sWSRC-%s:%s:%.100s.txt",
    +	       CACHE_FILE_PREFIX,
    +	       me->par_value[PAR_IP_NAME],
    +	       (me->par_value[PAR_TCP_PORT]
    +		? me->par_value[PAR_TCP_PORT]
    +		: "210"),
    +	       www_database);
    +
    +    if ((fp = fopen(cache_file_name, TXT_W)) != 0) {
    +	result = YES;
    +	if (me->par_value[PAR_DESCRIPTION])
    +	    fputs(me->par_value[PAR_DESCRIPTION], fp);
    +	else
    +	    fputs("Description not available\n", fp);
    +	fclose(fp);
    +    }
    +    FREE(www_database);
    +    FREE(cache_file_name);
    +    return result;
    +}
    +#endif
    +
    +/*			Output equivalent HTML
    + *			----------------------
    + *
    + */
    +
    +static void give_parameter(HTStream *me, int p)
    +{
    +    PUTS(par_name[p]);
    +    if (me->par_value[p]) {
    +	PUTS(": ");
    +	PUTS(me->par_value[p]);
    +	PUTS("; ");
    +    } else {
    +	PUTS(gettext(" NOT GIVEN in source file; "));
    +    }
    +}
    +
    +/*			Generate Output
    + *			===============
    + */
    +static void WSRC_gen_html(HTStream *me, int source_file)
    +{
    +    if (me->par_value[PAR_DATABASE_NAME]) {
    +	char *shortname = 0;
    +	int l;
    +
    +	StrAllocCopy(shortname, me->par_value[PAR_DATABASE_NAME]);
    +	l = (int) strlen(shortname);
    +	if (l > 4 && !strcasecomp(shortname + l - 4, ".src")) {
    +	    shortname[l - 4] = 0;	/* Chop of .src -- boring! */
    +	}
    +
    +	START(HTML_HEAD);
    +	PUTC('\n');
    +	START(HTML_TITLE);
    +	PUTS(shortname);
    +	PUTS(source_file ? gettext(" WAIS source file") : INDEX_SEGMENT);
    +	END(HTML_TITLE);
    +	PUTC('\n');
    +	END(HTML_HEAD);
    +
    +	START(HTML_H1);
    +	PUTS(shortname);
    +	PUTS(source_file ? gettext(" description") : INDEX_SEGMENT);
    +	END(HTML_H1);
    +	PUTC('\n');
    +	FREE(shortname);
    +    }
    +
    +    START(HTML_DL);		/* Definition list of details */
    +
    +    if (source_file) {
    +	START(HTML_DT);
    +	PUTS(gettext("Access links"));
    +	MAYBE_END(HTML_DT);
    +	START(HTML_DD);
    +	if (me->par_value[PAR_IP_NAME] &&
    +	    me->par_value[PAR_DATABASE_NAME]) {
    +
    +	    char *WSRC_address = NULL;
    +	    char *www_database;
    +
    +	    www_database = HTEscape(me->par_value[PAR_DATABASE_NAME],
    +				    URL_XALPHAS);
    +	    HTSprintf0(&WSRC_address, "%s//%s%s%s/%s",
    +		       STR_WAIS_URL,
    +		       me->par_value[PAR_IP_NAME],
    +		       me->par_value[PAR_TCP_PORT] ? ":" : "",
    +		       (me->par_value[PAR_TCP_PORT]
    +			? me->par_value[PAR_TCP_PORT]
    +			: ""),
    +		       www_database);
    +
    +	    HTStartAnchor(me->target, NULL, WSRC_address);
    +	    PUTS(gettext("Direct access"));
    +	    END(HTML_A);
    +	    /** Proxy will be used if defined, so let user know that - FM **/
    +	    PUTS(gettext(" (or via proxy server, if defined)"));
    +
    +	    FREE(www_database);
    +	    FREE(WSRC_address);
    +
    +	} else {
    +	    give_parameter(me, PAR_IP_NAME);
    +	    give_parameter(me, PAR_DATABASE_NAME);
    +	}
    +	MAYBE_END(HTML_DD);
    +
    +    }
    +    /* end if source_file */
    +    if (me->par_value[PAR_MAINTAINER]) {
    +	START(HTML_DT);
    +	PUTS(gettext("Maintainer"));
    +	MAYBE_END(HTML_DT);
    +	START(HTML_DD);
    +	PUTS(me->par_value[PAR_MAINTAINER]);
    +	MAYBE_END(HTML_DD);
    +    }
    +    if (me->par_value[PAR_IP_NAME]) {
    +	START(HTML_DT);
    +	PUTS(gettext("Host"));
    +	MAYBE_END(HTML_DT);
    +	START(HTML_DD);
    +	PUTS(me->par_value[PAR_IP_NAME]);
    +	MAYBE_END(HTML_DD);
    +    }
    +
    +    END(HTML_DL);
    +
    +    if (me->par_value[PAR_DESCRIPTION]) {
    +	START(HTML_PRE);	/* Preformatted description */
    +	PUTS(me->par_value[PAR_DESCRIPTION]);
    +	END(HTML_PRE);
    +    }
    +
    +    (*me->target->isa->_free) (me->target);
    +
    +    return;
    +}				/* generate html */
    +
    +static void WSRCParser_put_string(HTStream *context, const char *str)
    +{
    +    const char *p;
    +
    +    for (p = str; *p; p++)
    +	WSRCParser_put_character(context, *p);
    +}
    +
    +static void WSRCParser_write(HTStream *context, const char *str,
    +			     int l)
    +{
    +    const char *p;
    +    const char *e = str + l;
    +
    +    for (p = str; p < e; p++)
    +	WSRCParser_put_character(context, *p);
    +}
    +
    +static void WSRCParser_free(HTStream *me)
    +{
    +    WSRC_gen_html(me, YES);
    +#ifdef CACHE_FILE_PREFIX
    +    write_cache(me);
    +#endif
    +    {
    +	int p;
    +
    +	for (p = 0; par_name[p]; p++) {		/* Clear out old values */
    +	    FREE(me->par_value[p]);
    +	}
    +    }
    +    FREE(me);
    +}
    +
    +static void WSRCParser_abort(HTStream *me, HTError e GCC_UNUSED)
    +{
    +    WSRCParser_free(me);
    +}
    +
    +/*		Stream subclass		-- method routines
    + *		---------------
    + */
    +
    +static HTStreamClass WSRCParserClass =
    +{
    +    "WSRCParser",
    +    WSRCParser_free,
    +    WSRCParser_abort,
    +    WSRCParser_put_character,
    +    WSRCParser_put_string,
    +    WSRCParser_write
    +};
    +
    +/*		Converter from WAIS Source to whatever
    + *		--------------------------------------
    + */
    +HTStream *HTWSRCConvert(HTPresentation *pres, HTParentAnchor *anchor,
    +			HTStream *sink)
    +{
    +    HTStream *me = (HTStream *) malloc(sizeof(*me));
    +
    +    if (!me)
    +	outofmem(__FILE__, "HTWSRCConvert");
    +
    +    me->isa = &WSRCParserClass;
    +    me->target = HTML_new(anchor, pres->rep_out, sink);
    +
    +    {
    +	int p;
    +
    +	for (p = 0; p < PAR_COUNT; p++) {	/* Clear out parameter values */
    +	    me->par_value[p] = 0;
    +	}
    +    }
    +    me->state = beginning;
    +
    +    return me;
    +}
    diff --git a/WWW/Library/Implementation/HTWSRC.h b/WWW/Library/Implementation/HTWSRC.h
    new file mode 100644
    index 0000000..6c81132
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTWSRC.h
    @@ -0,0 +1,43 @@
    +/*                                                             A parser for WAIS source files
    +                                 WAIS SOURCE FILE PARSER
    +
    +   This converter returns a stream object into which a WAIS source file can be
    +   written.  The result is put via a structured stream into whatever format was
    +   required for the output stream.
    +
    +   See also:  HTWAIS protocol interface module
    +
    + */
    +#ifndef HTWSRC_H
    +#define HTWSRC_H
    +
    +#include <HTFormat.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern char from_hex(char c);
    +
    +    extern HTStream *HTWSRCConvert(HTPresentation *pres,
    +				   HTParentAnchor *anchor,
    +				   HTStream *sink);
    +
    +/*
    +
    +Escaping Strings
    +
    +   HTDeSlash takes out the invalid characters in a URL path ELEMENT by
    +   converting them into hex-escaped characters.  HTEnSlash does the reverse.
    +
    +   Each returns a pointer to a newly allocated string which must eventually be
    +   freed by the caller.
    +
    + */
    +    extern char *HTDeSlash(const char *str);
    +
    +    extern char *HTEnSlash(const char *str);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTWSRC_H */
    diff --git a/WWW/Library/Implementation/HText.h b/WWW/Library/Implementation/HText.h
    new file mode 100644
    index 0000000..93ff4a6
    --- /dev/null
    +++ b/WWW/Library/Implementation/HText.h
    @@ -0,0 +1,219 @@
    +/*
    + * $LynxId: HText.h,v 1.17 2020/01/21 22:08:07 tom Exp $
    + *                                                           Rich Hypertext object for libWWW
    + *                                RICH HYPERTEXT OBJECT
    + *
    + * This is the C interface to the Objective-C (or whatever) Style-oriented
    + * HyperText class.  It is used when a style-oriented text object is available
    + * or craeted in order to display hypertext.
    + */
    +#ifndef HTEXT_H
    +#define HTEXT_H
    +
    +#include <HTAnchor.h>
    +#include <HTStyle.h>
    +#include <HTStream.h>
    +#include <SGML.h>
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +#ifndef THINK_C
    +#ifndef HyperText		/* Objective C version defined HyperText */
    +    typedef struct _HText HText;	/* Normal Library */
    +#endif
    +#else
    +    class CHyperText;		/* Mac Think-C browser hook */
    +    typedef CHyperText HText;
    +#endif
    +
    +    extern HText *HTMainText;	/* Pointer to current main text */
    +    extern HTParentAnchor *HTMainAnchor;	/* Pointer to current text's anchor */
    +
    +    extern const char *HTAppName;	/* Application name */
    +    extern const char *HTAppVersion;	/* Application version */
    +
    +/*
    +
    +Creation and deletion
    +
    +  HTEXT_NEW: CREATE HYPERTEXT OBJECT
    +
    +   There are several methods depending on how much you want to specify.  The
    +   output stream is used with objects which need to output the hypertext to a
    +   stream.  The structure is for objects which need to refer to the structure
    +   which is kep by the creating stream.
    +
    + */
    +    extern HText *HText_new(HTParentAnchor *anchor);
    +
    +    extern HText *HText_new2(HTParentAnchor *anchor,
    +			     HTStream *output_stream);
    +
    +    extern HText *HText_new3(HTParentAnchor *anchor,
    +			     HTStream *output_stream,
    +			     HTStructured * structure);
    +
    +/*
    +
    +  FREE HYPERTEXT OBJECT
    +
    + */
    +    extern void HText_free(HText *me);
    +
    +/*
    +
    +Object Building methods
    +
    +   These are used by a parser to build the text in an object HText_beginAppend
    +   must be called, then any combination of other append calls, then
    +   HText_endAppend.  This allows optimised handling using buffers and caches
    +   which are flushed at the end.
    +
    + */
    +    extern void HText_beginAppend(HText *text);
    +
    +    extern void HText_endAppend(HText *text);
    +
    +/*
    +
    +  SET THE STYLE FOR FUTURE TEXT
    +
    + */
    +
    +    extern void HText_setStyle(HText *text, HTStyle *style);
    +
    +/*
    +
    +  ADD ONE CHARACTER
    +
    + */
    +    extern void HText_appendCharacter(HText *text, int ch);
    +
    +/*
    +
    +  ADD A ZERO-TERMINATED STRING
    +
    + */
    +
    +    extern void HText_appendText(HText *text, const char *str);
    +
    +/*
    +
    +  NEW PARAGRAPH
    +
    +   and similar things
    +
    + */
    +    extern void HText_appendParagraph(HText *text);
    +
    +    extern void HText_appendLineBreak(HText *text);
    +
    +    extern void HText_appendHorizontalRule(HText *text);
    +
    +/*
    +
    +  START/END SENSITIVE TEXT
    +
    + */
    +
    +/*
    +
    +   The anchor object is created and passed to HText_beginAnchor.  The sensitive
    +   text is added to the text object, and then HText_endAnchor is called. 
    +   Anchors may not be nested.
    +
    + */
    +    extern int HText_beginAnchor(HText *text, int underline,
    +				 HTChildAnchor *anc);
    +    extern void HText_endAnchor(HText *text, int number);
    +    extern BOOL HText_isAnchorBlank(HText *text, int number);
    +
    +/*
    +
    +  APPEND AN INLINE IMAGE
    +
    +   The image is handled by the creation of an anchor whose destination is the
    +   image document to be included.  The semantics is the intended inline display
    +   of the image.
    +
    +   An alternative implementation could be, for example, to begin an anchor,
    +   append the alternative text or "IMAGE", then end the anchor.  This would
    +   simply generate some text linked to the image itself as a separate document.
    +
    + */
    +    extern void HText_appendImage(HText *text, HTChildAnchor *anc,
    +				  const char *alternative_text,
    +				  int alignment,
    +				  int isMap);
    +
    +/*
    +
    +  RETURN THE ANCHOR ASSOCIATED WITH THIS NODE
    +
    + */
    +    extern HTParentAnchor *HText_nodeAnchor(HText *me);
    +
    +/*
    +
    +Browsing functions
    +
    + */
    +
    +/*
    +
    +  BRING TO FRONT AND HIGHLIGHT IT
    +
    + */
    +
    +    extern BOOL HText_select(HText *text);
    +    extern BOOL HText_selectAnchor(HText *text, HTChildAnchor *anchor);
    +
    +/*
    +
    +Editing functions
    +
    +   These are called from the application.  There are many more functions not
    +   included here from the original text object.  These functions NEED NOT BE
    +   IMPLEMENTED in a browser which cannot edit.
    +
    + */
    +/*      Style handling:
    +*/
    +/*      Apply this style to the selection
    +*/
    +    extern void HText_applyStyle(HText *me, HTStyle *style);
    +
    +/*      Update all text with changed style.
    +*/
    +    extern void HText_updateStyle(HText *me, HTStyle *style);
    +
    +/*      Return style of  selection
    +*/
    +    extern HTStyle *HText_selectionStyle(HText *me, HTStyleSheet *sheet);
    +
    +/*      Paste in styled text
    +*/
    +    extern void HText_replaceSel(HText *me, const char *aString,
    +				 HTStyle *aStyle);
    +
    +/*      Apply this style to the selection and all similarly formatted text
    + *      (style recovery only)
    + */
    +    extern void HTextApplyToSimilar(HText *me, HTStyle *style);
    +
    +/*      Select the first unstyled run.
    + *      (style recovery only)
    + */
    +    extern void HTextSelectUnstyled(HText *me, HTStyleSheet *sheet);
    +
    +/*      Anchor handling:
    +*/
    +    extern void HText_unlinkSelection(HText *me);
    +    extern HTAnchor *HText_referenceSelected(HText *me);
    +    extern HTAnchor *HText_linkSelTo(HText *me, HTAnchor * anchor);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* HTEXT_H */
    diff --git a/WWW/Library/Implementation/HTioctl.h b/WWW/Library/Implementation/HTioctl.h
    new file mode 100644
    index 0000000..99f8632
    --- /dev/null
    +++ b/WWW/Library/Implementation/HTioctl.h
    @@ -0,0 +1,11 @@
    +/*
    + *  A routine to mimic the ioctl function for UCX.
    + *  Bjorn S. Nilsson, 25-Nov-1993. Based on an example in the UCX manual.
    + */
    +#include <iodef.h>
    +#define IOC_OUT (int)0x40000000
    +extern int vaxc$get_sdc(), sys$qiow();
    +
    +#ifndef UCX$C_IOCTL
    +#define UCX$C_IOCTL TCPIP$C_IOCTL
    +#endif
    diff --git a/WWW/Library/Implementation/LYLeaks.h b/WWW/Library/Implementation/LYLeaks.h
    new file mode 100644
    index 0000000..a47b962
    --- /dev/null
    +++ b/WWW/Library/Implementation/LYLeaks.h
    @@ -0,0 +1,309 @@
    +/*
    + * $LynxId: LYLeaks.h,v 1.19 2020/01/21 22:05:46 tom Exp $
    + */
    +#ifndef __LYLEAKS_H
    +/*
    + *	Avoid include redundancy
    + *	Include only if finding memory leaks.
    + */
    +#define __LYLEAKS_H
    +
    +/*
    + *  Copyright (c) 1994, University of Kansas, All Rights Reserved
    + *
    + *  Include File:	LYLeaks.h
    + *  Purpose:		Header to convert requests for allocation to Lynx
    + *			custom functions to track memory leaks.
    + *  Remarks/Portability/Dependencies/Restrictions:
    + *	For the stdlib.h allocation functions to be overridden by the
    + *		Lynx memory tracking functions all modules allocating,
    + *		freeing, or resizing memory must have LY_FIND_LEAKS
    + *		defined before including this file.
    + *	This header file should be included in every source file which
    + *		does any memory manipulation through use of the
    + *		stdlib.h memory functions.
    + *	For proper reporting of memory leaks, the function LYLeaks
    + *		should be registered for execution by atexit as the
    + *		very first executable statement in main.
    + *	This code is slow and should not be used except in debugging
    + *		circumstances (don't define LY_FIND_LEAKS).
    + *	If you are using LY_FIND_LEAKS and don't want the LYLeak*
    + *		memory functions to be used in a certain file,
    + *		define NO_MEMORY_TRACKING before including this file.
    + *	The only safe way to call the LYLeak* functions is to use
    + *		the below macros because they depend on the static
    + *		string created by __FILE__ to not be dynamic in
    + *		nature (don't free it and assume will exist at all
    + *		times during execution).
    + *	If you are using LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED and
    + *		want only normal memory tracking (not extended for
    + *		HTSprintf/HTSprintf0) to be used in a certain file,
    + *		define NO_EXTENDED_MEMORY_TRACKING and don't define
    + *		NO_MEMORY_TRACKING before including this file.
    + *  Revision History:
    + *	05-26-94	created for Lynx 2-3-1, Garrett Arch Blythe
    + *	10-30-97	modified to handle StrAllocCopy() and
    + *			StrAllocCat(). - KW & FM
    + *	1999-10-17	modified to handle HTSprintf0 and HTSprintf(),
    + *			and to provide mark_malloced, if
    + *			LY_FIND_LEAKS_EXTENDED is defined. - kw
    + *	2003-01-22	add sequence-id for counting mallocs/frees -TD
    + *	2004-04-27	ANSIfy'd -TD
    + *	2012-02-09	add bstring interfaces -TD
    + */
    +
    +/* Undefine this to get no improved HTSprintf0/HTSprintf tracking: */
    +#define LY_FIND_LEAKS_EXTENDED
    +
    +/*
    + *  Required includes
    + */
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +/*
    + *	Constant defines
    + */
    +#define MAX_CONTENT_LENGTH 50
    +#ifdef VMS
    +#define LEAKAGE_SINK "sys$login:Lynx.leaks"
    +#else
    +#define LEAKAGE_SINK "Lynx.leaks"
    +#endif				/* VMS */
    +/*
    + * Data structures
    + */
    +    typedef struct SourceLocation_tag {
    +	/*
    +	 * The file name and line number of where an event took place.
    +	 */
    +	const char *cp_FileName;
    +	short ssi_LineNumber;
    +    } SourceLocation;
    +
    +    typedef struct AllocationList_tag {
    +	/*
    +	 * A singly linked list.
    +	 */
    +	struct AllocationList_tag *ALp_Next;
    +
    +	/*
    +	 * Count the number of mallocs.
    +	 */
    +	long st_Sequence;
    +
    +	/*
    +	 * The memory pointer allocated.  If set to NULL, then an invalid request
    +	 * was made.  The invalid pointer also.
    +	 */
    +	void *vp_Alloced;
    +	void *vp_BadRequest;
    +
    +	/*
    +	 * The size in bytes of the allocated memory.
    +	 */
    +	size_t st_Bytes;
    +
    +	/*
    +	 * The source location of specific event (calloc, malloc, free).  realloc
    +	 * kept separate since will track last realloc on pointer.
    +	 */
    +	SourceLocation SL_memory;
    +	SourceLocation SL_realloc;
    +    } AllocationList;
    +
    +/*
    + *  Global variable declarations
    + */
    +    extern char LYLeaksPath[];
    +
    +/*
    + *  Macros
    + */
    +#if defined(LY_FIND_LEAKS) && !defined(NO_MEMORY_TRACKING)
    +/*
    + * Only use these macros if we are to track memory allocations.  The reason for
    + * using a macro instead of a define is that we want to track where the initial
    + * allocation took place or where the last reallocation took place.  Track
    + * where the allocation took place by the __FILE__ and __LINE__ defines which
    + * are automatic to the compiler.
    + */
    +#ifdef malloc
    +#undef malloc
    +#endif				/* malloc */
    +#define malloc(st_bytes) LYLeakMalloc(st_bytes, __FILE__, __LINE__)
    +
    +#ifdef calloc
    +#undef calloc
    +#endif				/* calloc */
    +#define calloc(st_number, st_bytes) LYLeakCalloc(st_number, st_bytes, \
    +	__FILE__, __LINE__)
    +
    +#ifdef realloc
    +#undef realloc
    +#endif				/* realloc */
    +#define realloc(vp_alloced, st_newbytes) LYLeakRealloc(vp_alloced, \
    +	st_newbytes, __FILE__, __LINE__)
    +
    +#ifdef free
    +#undef free
    +#endif				/* free */
    +#define free(vp_alloced) LYLeakFree(vp_alloced, __FILE__, __LINE__)
    +
    +#ifdef strdup
    +#undef strdup
    +#endif				/* free */
    +#define strdup(vp_alloced) LYLeakStrdup(vp_alloced, __FILE__, __LINE__)
    +
    +/*
    + * Added the following two defines to track Lynx's frequent use of those
    + * macros.  - KW 1997-10-12
    + */
    +#ifdef StrAllocCopy
    +#undef StrAllocCopy
    +#endif				/* StrAllocCopy */
    +#define StrAllocCopy(dest, src) LYLeakSACopy(&(dest), src, __FILE__, __LINE__)
    +
    +#ifdef StrAllocCat
    +#undef StrAllocCat
    +#endif				/* StrAllocCat */
    +#define StrAllocCat(dest, src)  LYLeakSACat(&(dest), src, __FILE__, __LINE__)
    +
    +#ifdef BStrAlloc
    +#undef BStrAlloc
    +#endif
    +#define BStrAlloc(d,n)   LYLeakSABAlloc( &(d), n, __FILE__, __LINE__)
    +
    +#ifdef BStrCopy
    +#undef BStrCopy
    +#endif
    +#define BStrCopy(d,s)  LYLeakSABCopy( &(d), BStrData(s), BStrLen(s), __FILE__, __LINE__)
    +
    +#ifdef BStrCopy0
    +#undef BStrCopy0
    +#endif
    +#define BStrCopy0(d,s)  LYLeakSABCopy0( &(d), s, __FILE__, __LINE__)
    +
    +#ifdef BStrCat
    +#undef BStrCat
    +#endif
    +#define BStrCat(d,s)  LYLeakSABCat( &(d), BStrData(s), BStrLen(s), __FILE__, __LINE__)
    +
    +#ifdef BStrCat0
    +#undef BStrCat0
    +#endif
    +#define BStrCat0(d,s)  LYLeakSABCat0( &(d), s, __FILE__, __LINE__)
    +
    +#define mark_malloced(a,size) LYLeak_mark_malloced(a,size, __FILE__, __LINE__)
    +
    +#if defined(LY_FIND_LEAKS_EXTENDED) && !defined(NO_EXTENDED_MEMORY_TRACKING)
    +
    +#ifdef HTSprintf0
    +#undef HTSprintf0
    +#endif				/* HTSprintf0 */
    +#define HTSprintf0 (Get_htsprintf0_fn(__FILE__,__LINE__))
    +
    +#ifdef HTSprintf
    +#undef HTSprintf
    +#endif				/* HTSprintf */
    +#define HTSprintf (Get_htsprintf_fn(__FILE__,__LINE__))
    +
    +#endif				/* LY_FIND_LEAKS_EXTENDED and not NO_EXTENDED_MEMORY_TRACKING */
    +
    +#else				/* LY_FIND_LEAKS && !NO_MEMORY_TRACKING */
    +
    +#define mark_malloced(a,size)	/* no-op */
    +#define LYLeakSequence() (-1)
    +
    +#endif				/* LY_FIND_LEAKS && !NO_MEMORY_TRACKING */
    +
    +#if defined(LY_FIND_LEAKS)
    +#define PUBLIC_IF_FIND_LEAKS	/* nothing */
    +#else
    +#define PUBLIC_IF_FIND_LEAKS static
    +#endif
    +
    +/*
    + * Function declarations.
    + * See the appropriate source file for usage.
    + */
    +#ifndef LYLeakSequence
    +    extern long LYLeakSequence(void);
    +#endif
    +    extern void LYLeaks(void);
    +
    +#ifdef LY_FIND_LEAKS_EXTENDED
    +    extern AllocationList *LYLeak_mark_malloced(void *vp_alloced,
    +						size_t st_bytes,
    +						const char *cp_File,
    +						const short ssi_Line);
    +#endif				/* LY_FIND_LEAKS_EXTENDED */
    +    extern void *LYLeakMalloc(size_t st_bytes, const char *cp_File,
    +			      const short ssi_Line);
    +    extern void *LYLeakCalloc(size_t st_number, size_t st_bytes, const char *cp_File,
    +			      const short ssi_Line);
    +    extern void *LYLeakRealloc(void *vp_alloced,
    +			       size_t st_newbytes,
    +			       const char *cp_File,
    +			       const short ssi_Line);
    +    extern void LYLeakFree(void *vp_alloced,
    +			   const char *cp_File,
    +			   const short ssi_Line);
    +    extern char *LYLeakStrdup(const char *src,
    +			      const char *cp_File,
    +			      const short ssi_Line);
    +    extern char *LYLeakSACopy(char **dest,
    +			      const char *src,
    +			      const char *cp_File,
    +			      const short ssi_Line);
    +    extern char *LYLeakSACat(char **dest,
    +			     const char *src,
    +			     const char *cp_File,
    +			     const short ssi_Line);
    +    extern void LYLeakSABAlloc(bstring **dest,
    +			       int len,
    +			       const char *cp_File,
    +			       const short ssi_Line);
    +    extern void LYLeakSABCopy(bstring **dest,
    +			      const char *src,
    +			      int len,
    +			      const char *cp_File,
    +			      const short ssi_Line);
    +    extern void LYLeakSABCopy0(bstring **dest,
    +			       const char *src,
    +			       const char *cp_File,
    +			       const short ssi_Line);
    +    extern void LYLeakSABCat(bstring **dest,
    +			     const char *src,
    +			     int len,
    +			     const char *cp_File,
    +			     const short ssi_Line);
    +    extern void LYLeakSABCat0(bstring **dest,
    +			      const char *src,
    +			      const char *cp_File,
    +			      const short ssi_Line);
    +    extern void LYLeakSABFree(bstring **ptr,
    +			      const char *cp_File,
    +			      const short ssi_Line);
    +
    +#ifdef LY_FIND_LEAKS_EXTENDED
    +/*
    + * Trick to get tracking of var arg functions without relying on var arg
    + * preprocessor macros:
    + */
    +    typedef char *HTSprintflike(char **, const char *, ...);
    +    extern HTSprintflike *Get_htsprintf_fn(const char *cp_File,
    +					   const short ssi_Line);
    +    extern HTSprintflike *Get_htsprintf0_fn(const char *cp_File,
    +					    const short ssi_Line);
    +#endif				/* LY_FIND_LEAKS_EXTENDED */
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* __LYLEAKS_H */
    diff --git a/WWW/Library/Implementation/LYexit.h b/WWW/Library/Implementation/LYexit.h
    new file mode 100644
    index 0000000..dd4b0e6
    --- /dev/null
    +++ b/WWW/Library/Implementation/LYexit.h
    @@ -0,0 +1,67 @@
    +#ifndef __LYEXIT_H
    +/*
    + *	Avoid include redundancy
    + */
    +#define __LYEXIT_H
    +
    +/*
    + *	Copyright (c) 1994, University of Kansas, All Rights Reserved
    + *
    + *	Include File:	LYexit.h
    + *	Purpose:	Provide an atexit function for libraries without such.
    + *	Remarks/Portability/Dependencies/Restrictions:
    + *		Include this header in every file that you have an exit or
    + *			atexit statement.
    + *	Revision History:
    + *		06-15-94	created Lynx 2-3-1 Garrett Arch Blythe
    + */
    +
    +/*
    + *	Required includes
    + */
    +#ifdef _WINDOWS
    +#include <process.h>		/* declares exit() */
    +#endif
    +
    +#ifndef HTUTILS_H
    +#include <HTUtils.h>
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +/*
    + *	Constant defines
    + */
    +#ifdef exit
    +#undef exit
    +#endif
    +#define exit(code) LYexit(code)
    +#define atexit LYatexit
    +#define ATEXITSIZE 50
    +
    +/*
    + *	Data structures
    + */
    +
    +/*
    + * Global variable declarations
    + */
    +
    +/*
    + * Macros
    + */
    +
    +/*
    + * Function declarations
    + */
    +    extern GCC_NORETURN void outofmem(const char *fname, const char *func);
    +    extern void reset_signals(void);
    +    extern GCC_NORETURN void exit_immediately(int status);
    +    extern GCC_NORETURN void LYexit(int status);
    +    extern int LYatexit(void (*function) (void));
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* __LYEXIT_H */
    diff --git a/WWW/Library/Implementation/SGML.c b/WWW/Library/Implementation/SGML.c
    new file mode 100644
    index 0000000..2534606
    --- /dev/null
    +++ b/WWW/Library/Implementation/SGML.c
    @@ -0,0 +1,5159 @@
    +/*
    + * $LynxId: SGML.c,v 1.183 2022/06/13 00:20:50 tom Exp $
    + *
    + *			General SGML Parser code		SGML.c
    + *			========================
    + *
    + *	This module implements an HTStream object.  To parse an
    + *	SGML file, create this object which is a parser.  The object
    + *	is (currently) created by being passed a DTD structure,
    + *	and a target HTStructured object at which to throw the parsed stuff.
    + *
    + *	 6 Feb 93  Binary searches used. Interface modified.
    + */
    +
    +#define HTSTREAM_INTERNAL 1
    +
    +#include <HTUtils.h>
    +
    +#include <SGML.h>
    +#include <HTMLDTD.h>
    +#include <HTAccess.h>
    +#include <UCAux.h>
    +
    +#include <HTChunk.h>
    +#include <HTUtils.h>
    +
    +#include <LYCharSets.h>
    +#include <LYCharVals.h>		/* S/390 -- gil -- 0635 */
    +#include <LYGlobalDefs.h>
    +#include <LYStrings.h>
    +#include <LYLeaks.h>
    +#include <LYUtils.h>
    +
    +#ifdef USE_COLOR_STYLE
    +# include <LYStyle.h>
    +#endif
    +#ifdef USE_PRETTYSRC
    +# include <LYPrettySrc.h>
    +#endif
    +
    +/* a global variable doesn't work with info-stages which convert encoding */
    +#if defined(EXP_CHINESEUTF8_SUPPORT)
    +#undef IS_CJK_TTY
    +#define IS_CJK_TTY me->T.do_cjk
    +#endif
    +
    +#define AssumeCP1252(me) \
    +	(((me)->inUCLYhndl == LATIN1 \
    +	  || (me)->inUCLYhndl == US_ASCII) \
    +	 && html5_charsets)
    +
    +#define INVALID (-1)
    +
    +static int sgml_offset;
    +
    +#ifdef USE_PRETTYSRC
    +
    +static char *entity_string;	/* this is used for printing entity name.
    +
    +				   Unconditionally added since redundant assignments don't hurt much */
    +
    +static void fake_put_character(HTStream *p GCC_UNUSED,
    +			       int c GCC_UNUSED)
    +{
    +}
    +
    +#define START TRUE
    +#define STOP FALSE
    +
    +#define PUTS_TR(x) psrc_convert_string = TRUE; PUTS(x)
    +
    +#endif
    +
    +/* my_casecomp() - optimized by the first character, NOT_ASCII ok */
    +#define my_casecomp(a,b)  ((TOUPPER(*a) == TOUPPER(*b)) ? \
    +			AS_casecomp(a,b) : \
    +			(TOASCII(TOUPPER(*a)) - TOASCII(TOUPPER(*b))))
    +
    +/* ...used for comments and attributes value like href... */
    +#define HTChunkPutUtf8Char(ch,x) \
    +    { \
    +    if ((TOASCII(x) < 128)  && (ch->size < ch->allocated)) \
    +	ch->data[ch->size++] = (char)x; \
    +    else \
    +	(HTChunkPutUtf8Char)(ch,x); \
    +    }
    +
    +#define PUTS(str) ((*me->actions->put_string)(me->target, str))
    +#define PUTC(ch)  ((*me->actions->put_character)(me->target, (char) ch))
    +#define PUTUTF8(code) (UCPutUtf8_charstring((HTStream *)me->target, \
    +		      (putc_func_t*)(me->actions->put_character), code))
    +
    +#ifdef USE_PRETTYSRC
    +#define PRETTYSRC_PUTC(c) if (psrc_view) PUTC(c)
    +#else
    +#define PRETTYSRC_PUTC(c)	/* nothing */
    +#endif
    +
    +/*the following macros are used for pretty source view. */
    +#define IS_C(attr) (attr.type == HTMLA_CLASS)
    +
    +#if defined(USE_JAPANESEUTF8_SUPPORT)
    +# define UTF8_TTY_ISO2022JP (me->T.output_utf8)
    +#else
    +# define UTF8_TTY_ISO2022JP 0
    +#endif
    +
    +HTCJKlang HTCJK = NOCJK;	/* CJK enum value.              */
    +BOOL HTPassEightBitRaw = FALSE;	/* Pass 161-172,174-255 raw.    */
    +BOOL HTPassEightBitNum = FALSE;	/* Pass ^ numeric entities raw. */
    +BOOL HTPassHighCtrlRaw = FALSE;	/* Pass 127-160,173, raw. */
    +BOOL HTPassHighCtrlNum = FALSE;	/* Pass €-Ÿ raw.      */
    +
    +/*	The State (context) of the parser
    + *
    + *	This is passed with each call to make the parser reentrant
    + */
    +
    +#define MAX_ATTRIBUTES 36	/* Max number of attributes per element */
    +
    +/*		Element Stack
    + *		-------------
    + *	This allows us to return down the stack reselecting styles.
    + *	As we return, attribute values will be garbage in general.
    + */
    +typedef struct _HTElement HTElement;
    +struct _HTElement {
    +    HTElement *next;		/* Previously nested element or 0 */
    +    HTTag *tag;			/* The tag at this level  */
    +};
    +
    +typedef enum {
    +    S_text = 0
    +    ,S_attr
    +    ,S_attr_gap
    +    ,S_comment
    +    ,S_cro
    +    ,S_doctype
    +    ,S_dollar
    +    ,S_dollar_dq
    +    ,S_dollar_paren
    +    ,S_dollar_paren_dq
    +    ,S_dollar_paren_sq
    +    ,S_dollar_sq
    +    ,S_dquoted
    +    ,S_end
    +    ,S_entity
    +    ,S_equals
    +    ,S_ero
    +    ,S_esc
    +    ,S_esc_dq
    +    ,S_esc_sq
    +    ,S_exclamation
    +    ,S_in_kanji
    +    ,S_incro
    +    ,S_junk_tag
    +    ,S_litteral
    +    ,S_marked
    +    ,S_nonascii_text
    +    ,S_nonascii_text_dq
    +    ,S_nonascii_text_sq
    +    ,S_paren
    +    ,S_paren_dq
    +    ,S_paren_sq
    +    ,S_pcdata
    +    ,S_pi
    +    ,S_script
    +    ,S_sgmlatt
    +    ,S_sgmlele
    +    ,S_sgmlent
    +    ,S_squoted
    +    ,S_tag
    +    ,S_tag_gap
    +    ,S_tagname_slash
    +    ,S_value
    +} sgml_state;
    +
    +/*	Internal Context Data Structure
    + *	-------------------------------
    + */
    +struct _HTStream {
    +
    +    const HTStreamClass *isa;	/* inherited from HTStream */
    +
    +    const SGML_dtd *dtd;
    +    const HTStructuredClass *actions;	/* target class  */
    +    HTStructured *target;	/* target object */
    +
    +    HTTag *current_tag;
    +    HTTag *slashedtag;
    +    const HTTag *unknown_tag;
    +    BOOL extended_html;		/* xhtml */
    +    BOOL strict_xml;		/* xml */
    +    BOOL inSELECT;
    +    BOOL no_lynx_specialcodes;
    +    int current_attribute_number;
    +    HTChunk *string;
    +    int leading_spaces;
    +    int trailing_spaces;
    +    HTElement *element_stack;
    +    sgml_state state;
    +    unsigned char kanji_buf;
    +#ifdef CALLERDATA
    +    void *callerData;
    +#endif				/* CALLERDATA */
    +    BOOL present[MAX_ATTRIBUTES];	/* Flags: attribute is present? */
    +    char *value[MAX_ATTRIBUTES];	/* NULL, or strings alloc'd with StrAllocCopy_extra() */
    +
    +    BOOL lead_exclamation;
    +    BOOL first_dash;
    +    BOOL end_comment;
    +    BOOL doctype_bracket;
    +    BOOL first_bracket;
    +    BOOL second_bracket;
    +    BOOL isHex;
    +
    +    HTParentAnchor *node_anchor;
    +    LYUCcharset *inUCI;		/* pointer to anchor UCInfo */
    +    int inUCLYhndl;		/* charset we are fed       */
    +    LYUCcharset *outUCI;	/* anchor UCInfo for target */
    +    int outUCLYhndl;		/* charset for target       */
    +    UTFDecodeState U;
    +    UCTransParams T;
    +    int current_tag_charset;	/* charset to pass attributes */
    +
    +    char *recover;
    +    int recover_index;
    +    char *include;
    +    char *active_include;
    +    int include_index;
    +    char *url;
    +    char *csi;
    +    int csi_index;
    +#ifdef USE_PRETTYSRC
    +    BOOL cur_attr_is_href;
    +    BOOL cur_attr_is_name;
    +#endif
    +};
    +
    +#ifdef NO_LYNX_TRACE
    +#define state_name(n) "state"
    +#else
    +static const char *state_name(sgml_state n)
    +{
    +    const char *result = "?";
    +    /* *INDENT-OFF* */
    +    switch (n) {
    +    case S_attr:                result = "S_attr";              break;
    +    case S_attr_gap:            result = "S_attr_gap";          break;
    +    case S_comment:             result = "S_comment";           break;
    +    case S_cro:                 result = "S_cro";               break;
    +    case S_doctype:             result = "S_doctype";           break;
    +    case S_dollar:              result = "S_dollar";            break;
    +    case S_dollar_dq:           result = "S_dollar_dq";         break;
    +    case S_dollar_paren:        result = "S_dollar_paren";      break;
    +    case S_dollar_paren_dq:     result = "S_dollar_paren_dq";   break;
    +    case S_dollar_paren_sq:     result = "S_dollar_paren_sq";   break;
    +    case S_dollar_sq:           result = "S_dollar_sq";         break;
    +    case S_dquoted:             result = "S_dquoted";           break;
    +    case S_end:                 result = "S_end";               break;
    +    case S_entity:              result = "S_entity";            break;
    +    case S_equals:              result = "S_equals";            break;
    +    case S_ero:                 result = "S_ero";               break;
    +    case S_esc:                 result = "S_esc";               break;
    +    case S_esc_dq:              result = "S_esc_dq";            break;
    +    case S_esc_sq:              result = "S_esc_sq";            break;
    +    case S_exclamation:         result = "S_exclamation";       break;
    +    case S_in_kanji:            result = "S_in_kanji";          break;
    +    case S_incro:               result = "S_incro";             break;
    +    case S_pi:                  result = "S_pi";                break;
    +    case S_junk_tag:            result = "S_junk_tag";          break;
    +    case S_litteral:            result = "S_litteral";          break;
    +    case S_marked:              result = "S_marked";            break;
    +    case S_nonascii_text:       result = "S_nonascii_text";     break;
    +    case S_nonascii_text_dq:    result = "S_nonascii_text_dq";  break;
    +    case S_nonascii_text_sq:    result = "S_nonascii_text_sq";  break;
    +    case S_paren:               result = "S_paren";             break;
    +    case S_paren_dq:            result = "S_paren_dq";          break;
    +    case S_paren_sq:            result = "S_paren_sq";          break;
    +    case S_pcdata:              result = "S_pcdata";            break;
    +    case S_script:              result = "S_script";            break;
    +    case S_sgmlatt:             result = "S_sgmlatt";           break;
    +    case S_sgmlele:             result = "S_sgmlele";           break;
    +    case S_sgmlent:             result = "S_sgmlent";           break;
    +    case S_squoted:             result = "S_squoted";           break;
    +    case S_tag:                 result = "S_tag";               break;
    +    case S_tag_gap:             result = "S_tag_gap";           break;
    +    case S_tagname_slash:       result = "S_tagname_slash";     break;
    +    case S_text:                result = "S_text";              break;
    +    case S_value:               result = "S_value";             break;
    +    }
    +    /* *INDENT-ON* */
    +
    +    return result;
    +}
    +#endif
    +
    +/* storage for Element Stack */
    +#define DEPTH 10
    +static HTElement pool[DEPTH];
    +static int depth = 0;
    +
    +static HTElement *pool_alloc(void)
    +{
    +    depth++;
    +    if (depth > DEPTH)
    +	return (HTElement *) malloc(sizeof(HTElement));
    +    return (pool + depth - 1);
    +}
    +
    +static void pool_free(HTElement * e)
    +{
    +    if (depth > DEPTH)
    +	FREE(e);
    +    depth--;
    +    return;
    +}
    +
    +#ifdef USE_PRETTYSRC
    +
    +static void HTMLSRC_apply_markup(HTStream *me,
    +				 HTlexeme lexeme,
    +				 int start)
    +{
    +    HT_tagspec *ts = *((start ? lexeme_start : lexeme_end) + lexeme);
    +
    +    while (ts) {
    +#ifdef USE_COLOR_STYLE
    +	if (ts->start) {
    +	    current_tag_style = ts->style;
    +	    force_current_tag_style = TRUE;
    +	    forced_classname = ts->class_name;
    +	    force_classname = TRUE;
    +	}
    +#endif
    +	CTRACE((tfp, ts->start ? "SRCSTART %d\n" : "SRCSTOP %d\n", (int) lexeme));
    +	if (ts->start)
    +	    (*me->actions->start_element) (me->target,
    +					   (int) ts->element,
    +					   ts->present,
    +					   (STRING2PTR) ts->value,
    +					   me->current_tag_charset,
    +					   &me->include);
    +	else
    +	    (*me->actions->end_element) (me->target,
    +					 (int) ts->element,
    +					 &me->include);
    +	ts = ts->next;
    +    }
    +}
    +
    +#define PSRCSTART(x)	HTMLSRC_apply_markup(me,HTL_##x,START)
    +#define PSRCSTOP(x)   HTMLSRC_apply_markup(me,HTL_##x,STOP)
    +
    +#define attr_is_href me->cur_attr_is_href
    +#define attr_is_name me->cur_attr_is_name
    +#endif
    +
    +static void set_chartrans_handling(HTStream *me,
    +				   HTParentAnchor *anchor,
    +				   int chndl)
    +{
    +    if (chndl < 0) {
    +	/*
    +	 * Nothing was set for the parser in earlier stages, so the HTML
    +	 * parser's UCLYhndl should still be its default.  - FM
    +	 */
    +	chndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_STRUCTURED);
    +	if (chndl < 0)
    +	    /*
    +	     * That wasn't set either, so seek the HText default.  - FM
    +	     */
    +	    chndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_HTEXT);
    +	if (chndl < 0)
    +	    /*
    +	     * That wasn't set either, so assume the current display character
    +	     * set.  - FM
    +	     */
    +	    chndl = current_char_set;
    +	/*
    +	 * Try to set the HText and HTML stages' chartrans info with the
    +	 * default lock level (will not be changed if it was set previously
    +	 * with a higher lock level).  - FM
    +	 */
    +	HTAnchor_setUCInfoStage(anchor, chndl,
    +				UCT_STAGE_HTEXT,
    +				UCT_SETBY_DEFAULT);
    +	HTAnchor_setUCInfoStage(anchor, chndl,
    +				UCT_STAGE_STRUCTURED,
    +				UCT_SETBY_DEFAULT);
    +	/*
    +	 * Get the chartrans info for output to the HTML parser.  - FM
    +	 */
    +	me->outUCI = HTAnchor_getUCInfoStage(anchor,
    +					     UCT_STAGE_STRUCTURED);
    +	me->outUCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
    +					       UCT_STAGE_STRUCTURED);
    +    }
    +    /*
    +     * Set the in->out transformation parameters.  - FM
    +     */
    +    UCSetTransParams(&me->T,
    +		     me->inUCLYhndl, me->inUCI,
    +		     me->outUCLYhndl, me->outUCI);
    +    /*
    +     * This is intended for passing the SGML parser's input charset as an
    +     * argument in each call to the HTML parser's start tag function, but it
    +     * would be better to call a Lynx_HTML_parser function to set an element in
    +     * its HTStructured object, itself, if this were needed.  - FM
    +     */
    +#ifndef USE_JAPANESEUTF8_SUPPORT
    +    if (IS_CJK_TTY) {
    +	me->current_tag_charset = -1;
    +    } else
    +#endif
    +    if (me->T.transp) {
    +	me->current_tag_charset = me->inUCLYhndl;
    +    } else if (me->T.decode_utf8) {
    +	me->current_tag_charset = me->inUCLYhndl;
    +    } else if (me->T.do_8bitraw ||
    +	       me->T.use_raw_char_in) {
    +	me->current_tag_charset = me->inUCLYhndl;
    +    } else if (me->T.output_utf8 ||
    +	       me->T.trans_from_uni) {
    +	me->current_tag_charset = UCGetLYhndl_byMIME("utf-8");
    +    } else {
    +	me->current_tag_charset = LATIN1;
    +    }
    +}
    +
    +static void change_chartrans_handling(HTStream *me)
    +{
    +    int new_LYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
    +					  UCT_STAGE_PARSER);
    +
    +    if (new_LYhndl != me->inUCLYhndl &&
    +	new_LYhndl >= 0) {
    +	/*
    +	 * Something changed.  but ignore if a META wants an unknown charset.
    +	 */
    +	LYUCcharset *new_UCI = HTAnchor_getUCInfoStage(me->node_anchor,
    +						       UCT_STAGE_PARSER);
    +
    +	if (new_UCI) {
    +	    LYUCcharset *next_UCI = HTAnchor_getUCInfoStage(me->node_anchor,
    +							    UCT_STAGE_STRUCTURED);
    +	    int next_LYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_STRUCTURED);
    +
    +	    me->inUCI = new_UCI;
    +	    me->inUCLYhndl = new_LYhndl;
    +	    me->outUCI = next_UCI;
    +	    me->outUCLYhndl = next_LYhndl;
    +	    set_chartrans_handling(me,
    +				   me->node_anchor, next_LYhndl);
    +	}
    +    }
    +}
    +
    +#ifdef USE_COLOR_STYLE
    +#include <AttrList.h>
    +static int current_is_class = 0;
    +#endif
    +
    +/*	Handle Attribute
    + *	----------------
    + */
    +/* PUBLIC const char * SGML_default = "";   ?? */
    +
    +static void handle_attribute_name(HTStream *me, const char *s)
    +{
    +    HTTag *tag = me->current_tag;
    +    const attr *attributes = tag->attributes;
    +    int high, low, i, diff;
    +
    +#ifdef USE_PRETTYSRC
    +    if (psrc_view) {
    +	attr_is_href = FALSE;
    +	attr_is_name = FALSE;
    +    }
    +#endif
    +    /*
    +     * Ignore unknown tag.  - KW
    +     */
    +    if (tag == me->unknown_tag) {
    +#ifdef USE_PRETTYSRC
    +	if (psrc_view)
    +	    me->current_attribute_number = 1;	/* anything !=INVALID */
    +#endif
    +	return;
    +    }
    +
    +    /*
    +     * Binary search for attribute name.
    +     */
    +    for (low = 0, high = tag->number_of_attributes;
    +	 high > low;
    +	 diff < 0 ? (low = i + 1) : (high = i)) {
    +	i = (low + (high - low) / 2);
    +	diff = my_casecomp(attributes[i].name, s);
    +	if (diff == 0) {	/* success: found it */
    +	    me->current_attribute_number = i;
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		attr_is_name = (BOOL) (attributes[i].type == HTMLA_ANAME);
    +		attr_is_href = (BOOL) (attributes[i].type == HTMLA_HREF);
    +	    } else
    +#endif
    +	    {
    +		me->present[i] = YES;
    +		Clear_extra(me->value[i]);
    +#ifdef USE_COLOR_STYLE
    +#   ifdef USE_PRETTYSRC
    +		current_is_class = IS_C(attributes[i]);
    +#   else
    +		current_is_class = (!strcasecomp("class", s));
    +#   endif
    +		CTRACE((tfp, "SGML: found attribute %s, %d\n", s, current_is_class));
    +#endif
    +	    }
    +	    return;
    +	}
    +	/* if */
    +    }				/* for */
    +
    +    CTRACE((tfp, "SGML: Unknown attribute %s for tag %s\n",
    +	    s, NonNull(me->current_tag->name)));
    +    me->current_attribute_number = INVALID;	/* Invalid */
    +}
    +
    +/*	Handle attribute value
    + *	----------------------
    + */
    +static void handle_attribute_value(HTStream *me, const char *s)
    +{
    +    if (me->current_attribute_number != INVALID) {
    +	StrAllocCopy_extra(me->value[me->current_attribute_number], s);
    +#ifdef USE_COLOR_STYLE
    +	if (current_is_class) {
    +	    StrNCpy(class_string, s, TEMPSTRINGSIZE);
    +	    CTRACE((tfp, "SGML: class is '%s'\n", s));
    +	} else {
    +	    CTRACE((tfp, "SGML: attribute value is '%s'\n", s));
    +	}
    +#endif
    +    } else {
    +	CTRACE((tfp, "SGML: Attribute value %s ***ignored\n", s));
    +    }
    +    me->current_attribute_number = INVALID;	/* can't have two assignments! */
    +}
    +
    +/*
    + *  Translate some Unicodes to Lynx special codes and output them.
    + *  Special codes - ones those output depend on parsing.
    + *
    + *  Additional issue, like handling bidirectional text if necessary
    + *  may be called from here:  zwnj (8204), zwj (8205), lrm (8206), rlm (8207)
    + *  - currently they are ignored in SGML.c and LYCharUtils.c
    + *  but also in UCdomap.c because they are non printable...
    + *
    + */
    +static BOOL put_special_unicodes(HTStream *me, UCode_t code)
    +{
    +    /* (Tgf_nolyspcl) */
    +    if (me->no_lynx_specialcodes) {
    +	/*
    +	 * We were asked by a "DTD" flag to not generate lynx specials.  - kw
    +	 */
    +	return NO;
    +    }
    +
    +    if (code == CH_NBSP) {	/* S/390 -- gil -- 0657 */
    +	/*
    +	 * Use Lynx special character for nbsp.
    +	 */
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view)
    +#endif
    +	    PUTC(HT_NON_BREAK_SPACE);
    +    } else if (code == CH_SHY) {
    +	/*
    +	 * Use Lynx special character for shy.
    +	 */
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view)
    +#endif
    +	    PUTC(LY_SOFT_HYPHEN);
    +    } else if (code == 8194 || code == 8201) {
    +	/*
    +	 * Use Lynx special character for ensp or thinsp.
    +	 *
    +	 * Originally, Lynx use space '32' as word delimiter and omits this
    +	 * space at end of line if word is wrapped to the next line.  There are
    +	 * several other spaces in the Unicode repertoire and we should teach
    +	 * Lynx to understand them, not only as regular characters but in the
    +	 * context of line wrapping.  Unfortunately, if we use HT_EN_SPACE we
    +	 * override the chartrans tables for those spaces with a single '32'
    +	 * for all (but do line wrapping more fancy).
    +	 *
    +	 * We may treat emsp as one or two ensp (below).
    +	 */
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view)
    +#endif
    +	    PUTC(HT_EN_SPACE);
    +    } else if (code == 8195) {
    +	/*
    +	 * Use Lynx special character for emsp.
    +	 */
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view) {
    +#endif
    +	    /* PUTC(HT_EN_SPACE);  let's stay with a single space :) */
    +	    PUTC(HT_EN_SPACE);
    +#ifdef USE_PRETTYSRC
    +	}
    +#endif
    +    } else {
    +	/*
    +	 * Return NO if nothing done.
    +	 */
    +	return NO;
    +    }
    +    /*
    +     * We have handled it.
    +     */
    +    return YES;
    +}
    +
    +#ifdef USE_PRETTYSRC
    +static void put_pretty_entity(HTStream *me, int term)
    +{
    +    PSRCSTART(entity);
    +    PUTC('&');
    +    PUTS(entity_string);
    +    if (term)
    +	PUTC((char) term);
    +    PSRCSTOP(entity);
    +}
    +
    +static void put_pretty_number(HTStream *me)
    +{
    +    PSRCSTART(entity);
    +    PUTS((me->isHex ? "&#x" : "&#"));
    +    PUTS(entity_string);
    +    PUTC(';');
    +    PSRCSTOP(entity);
    +}
    +#endif /* USE_PRETTYSRC */
    +
    +/*	Handle entity
    + *	-------------
    + *
    + * On entry,
    + *	s	contains the entity name zero terminated
    + * Bugs:
    + *	If the entity name is unknown, the terminator is treated as
    + *	a printable non-special character in all cases, even if it is '<'
    + * Bug-fix:
    + *	Modified SGML_character() so we only come here with terminator
    + *	as '\0' and check a FoundEntity flag. -- Foteos Macrides
    + *
    + * Modified more (for use with Lynx character translation code):
    + */
    +static char replace_buf[64];	/* buffer for replacement strings */
    +static BOOL FoundEntity = FALSE;
    +
    +static void handle_entity(HTStream *me, int term)
    +{
    +    UCode_t code;
    +    long uck = -1;
    +    const char *s = me->string->data;
    +
    +    /*
    +     * Handle all entities normally.  - FM
    +     */
    +    FoundEntity = FALSE;
    +    if ((code = HTMLGetEntityUCValue(s)) != 0) {
    +	/*
    +	 * We got a Unicode value for the entity name.  Check for special
    +	 * Unicodes.  - FM
    +	 */
    +	if (put_special_unicodes(me, code)) {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    }
    +#endif
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +	/*
    +	 * Seek a translation from the chartrans tables.
    +	 */
    +	if ((uck = UCTransUniChar(code, me->outUCLYhndl)) >= 32 &&
    +/* =============== work in ASCII below here ===============  S/390 -- gil -- 0672 */
    +	    uck < 256 &&
    +	    (uck < 127 ||
    +	     uck >= LYlowest_eightbit[me->outUCLYhndl])) {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    } else
    +#endif
    +		PUTC(FROMASCII((char) uck));
    +	    FoundEntity = TRUE;
    +	    return;
    +	} else if ((uck == -4 ||
    +		    (me->T.repl_translated_C0 &&
    +		     uck > 0 && uck < 32)) &&
    +	    /*
    +	     * Not found; look for replacement string.
    +	     */
    +		   (uck = UCTransUniCharStr(replace_buf, 60, code,
    +					    me->outUCLYhndl, 0) >= 0)) {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    } else
    +#endif
    +		PUTS(replace_buf);
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +	/*
    +	 * If we're displaying UTF-8, try that now.  - FM
    +	 */
    +#ifndef USE_PRETTYSRC
    +	if (me->T.output_utf8 && PUTUTF8(code)) {
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +#else
    +	if (me->T.output_utf8 && (psrc_view
    +				  ? (UCPutUtf8_charstring((HTStream *) me->target,
    +							  (putc_func_t *) (fake_put_character),
    +							  code))
    +				  : PUTUTF8(code))) {
    +
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    }
    +
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +#endif
    +	/*
    +	 * If it's safe ASCII, use it.  - FM
    +	 */
    +	if (code >= 32 && code < 127) {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    } else
    +#endif
    +
    +		PUTC(FROMASCII((char) code));
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +/* =============== work in ASCII above here ===============  S/390 -- gil -- 0682 */
    +	/*
    +	 * Ignore zwnj (8204) and zwj (8205), if we get to here.  Note that
    +	 * zwnj may have been handled as <WBR> by the calling function.  - FM
    +	 */
    +	if (!strcmp(s, "zwnj") ||
    +	    !strcmp(s, "zwj")) {
    +	    CTRACE((tfp, "handle_entity: Ignoring '%s'.\n", s));
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    }
    +#endif
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +	/*
    +	 * Ignore lrm (8206), and rln (8207), if we get to here.  - FM
    +	 */
    +	if (!strcmp(s, "lrm") ||
    +	    !strcmp(s, "rlm")) {
    +	    CTRACE((tfp, "handle_entity: Ignoring '%s'.\n", s));
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		put_pretty_entity(me, term);
    +	    }
    +#endif
    +	    FoundEntity = TRUE;
    +	    return;
    +	}
    +    }
    +
    +    /*
    +     * If entity string not found, display as text.
    +     */
    +#ifdef USE_PRETTYSRC
    +    if (psrc_view)
    +	PSRCSTART(badseq);
    +#endif
    +    /* S/390 -- gil -- 0695 */
    +    CTRACE((tfp, "SGML: Unknown entity '%s' %" PRI_UCode_t " %ld\n", s, code, uck));
    +    PUTC('&');
    +    PUTS(s);
    +    if (term != '\0')
    +	PUTC(term);
    +#ifdef USE_PRETTYSRC
    +    if (psrc_view)
    +	PSRCSTOP(badseq);
    +#endif
    +}
    +
    +/*	Handle comment
    + *	--------------
    + */
    +static void handle_comment(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Comment:\n<%s>\n", s));
    +
    +    if (me->csi == NULL &&
    +	StrNCmp(s, "!--#", 4) == 0 &&
    +	LYCheckForCSI(me->node_anchor, &me->url) == TRUE) {
    +	LYDoCSI(me->url, s, &me->csi);
    +    } else {
    +	LYCommentHacks(me->node_anchor, me->string->data);
    +    }
    +
    +    return;
    +}
    +
    +/*	Handle identifier
    + *	-----------------
    + */
    +static void handle_identifier(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Identifier:\n<%s>\n", s));
    +
    +    return;
    +}
    +
    +/*	Handle doctype
    + *	--------------
    + */
    +static void handle_doctype(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Doctype:\n<%s>\n", s));
    +    if (strstr(s, "DTD XHTML ") != 0) {
    +	CTRACE((tfp, "...processing extended HTML\n"));
    +	me->extended_html = TRUE;
    +    }
    +
    +    return;
    +}
    +
    +/*	Handle marked
    + *	-------------
    + */
    +static void handle_marked(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Marked Section:\n<%s>\n", s));
    +
    +    if (!StrNCmp(me->string->data, "![INCLUDE[", 10)) {
    +	me->string->data[me->string->size - 3] = '\0';
    +	StrAllocCat(me->include, me->string->data + 10);
    +	/* @@@ This needs to take charset into account! @@@
    +	   the wrong assumptions will be made about the data's
    +	   charset once it is in include - kw */
    +
    +    } else if (!StrNCmp(me->string->data, "![CDATA[", 8)) {
    +	(*me->actions->put_block) (me->target,
    +				   me->string->data + 8,
    +				   me->string->size - 11);
    +
    +    }
    +    return;
    +}
    +
    +/*	Handle processing instruction
    + *	-----------------------------
    + */
    +static void handle_processing_instruction(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Processing instruction:\n<%s>\n", s));
    +
    +    if (!StrNCmp(s, "?xml ", 5)) {
    +	int flag = me->T.decode_utf8;
    +
    +	me->strict_xml = TRUE;
    +	/*
    +	 * Switch to UTF-8 if the encoding is explicitly "utf-8".
    +	 */
    +	if (!flag) {
    +	    char *t = strstr(s, "encoding=");
    +
    +	    if (t != 0) {
    +		t += 9;
    +		if (*t == '"')
    +		    ++t;
    +		flag = !StrNCmp(t, "utf-8", 5);
    +	    }
    +	    if (flag) {
    +		CTRACE((tfp, "...Use UTF-8 for XML\n"));
    +		me->T.decode_utf8 = TRUE;
    +	    }
    +	}
    +    }
    +
    +    return;
    +}
    +
    +/*	Handle sgmlent
    + *	--------------
    + */
    +static void handle_sgmlent(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Entity Declaration:\n<%s>\n", s));
    +
    +    return;
    +}
    +
    +/*	Handle sgmlent
    + *	--------------
    + */
    +static void handle_sgmlele(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Element Declaration:\n<%s>\n", s));
    +
    +    return;
    +}
    +
    +/*	Handle sgmlatt
    + *	--------------
    + */
    +static void handle_sgmlatt(HTStream *me)
    +{
    +    const char *s = me->string->data;
    +
    +    CTRACE((tfp, "SGML Attribute Declaration:\n<%s>\n", s));
    +
    +    return;
    +}
    +
    +/*
    + * Convenience macros - tags (elements) are identified sometimes by an int or
    + * enum value ('TAGNUM'), sometimes by a pointer to HTTag ('TAGP').  - kw
    + */
    +#define TAGNUM_OF_TAGP(t) (HTMLElement) (t - me->dtd->tags)
    +#define TAGP_OF_TAGNUM(e) (me->dtd->tags + e)
    +
    +/*
    + * The following implement special knowledge about OBJECT.  As long as
    + * HTML_OBJECT is the only tag for which an alternative variant exist, they can
    + * be simple macros.  - kw
    + */
    +/* does 'TAGNUM' e have an alternative (variant) parsing mode? */
    +#define HAS_ALT_TAGNUM(e) (e == HTML_OBJECT)
    +
    +/* return 'TAGNUM' of the alternative mode for 'TAGNUM' e, if any. */
    +#define ALT_TAGNUM(e) ((e == HTML_OBJECT) ? HTML_ALT_OBJECT : e)
    +
    +/* return 'TAGNUM' of the normal mode for 'TAGNUM' e which may be alt. */
    +#define NORMAL_TAGNUM(e) (((int)(e) >= HTML_ELEMENTS) ? HTML_OBJECT : (HTMLElement)e)
    +
    +/* More convenience stuff. - kw */
    +#define ALT_TAGP_OF_TAGNUM(e) TAGP_OF_TAGNUM(ALT_TAGNUM(e))
    +#define NORMAL_TAGP_OF_TAGNUM(e) TAGP_OF_TAGNUM(NORMAL_TAGNUM(e))
    +
    +#define ALT_TAGP(t) ALT_TAGP_OF_TAGNUM(TAGNUM_OF_TAGP(t))
    +#define NORMAL_TAGP(t) NORMAL_TAGP_OF_TAGNUM(TAGNUM_OF_TAGP(t))
    +
    +#define IsTagAlias(a,b) (((a) == (b)) || ((a) - (a)->alias == (b) - (b)->alias))
    +
    +static BOOL element_valid_within(HTTag * new_tag, HTTag * stacked_tag, int direct)
    +{
    +    BOOL result = YES;
    +    TagClass usecontains, usecontained;
    +
    +    if (stacked_tag && new_tag) {
    +	usecontains = (direct ? stacked_tag->contains : stacked_tag->icontains);
    +	usecontained = (direct ? new_tag->contained : new_tag->icontained);
    +	if (IsTagAlias(new_tag, stacked_tag)) {
    +	    result = (BOOL) ((Tgc_same & usecontains) &&
    +			     (Tgc_same & usecontained));
    +	} else {
    +	    result = (BOOL) ((new_tag->tagclass & usecontains) &&
    +			     (stacked_tag->tagclass & usecontained));
    +	}
    +    }
    +    return result;
    +}
    +
    +static BOOL element_really_within(HTTag * new_tag, HTTag * stacked_tag, int direct)
    +{
    +    BOOL result = YES;
    +    TagClass usecontains, usecontained;
    +
    +    if (stacked_tag && new_tag) {
    +	usecontains = (direct ? stacked_tag->contains : stacked_tag->icontains);
    +	usecontained = (direct ? new_tag->contained : new_tag->icontained);
    +	if (IsTagAlias(new_tag, stacked_tag)) {
    +	    result = (BOOL) ((Tgc_same & usecontains) &&
    +			     (Tgc_same & usecontained));
    +	} else {
    +	    result = (BOOL) ((new_tag->tagclass & usecontains) ==
    +			     new_tag->tagclass &&
    +			     (stacked_tag->tagclass & usecontained) == stacked_tag->tagclass);
    +	}
    +    }
    +    return result;
    +}
    +
    +typedef enum {
    +    close_NO = 0,
    +    close_error = 1,
    +    close_valid = 2
    +} canclose_t;
    +
    +static canclose_t can_close(HTTag * new_tag, HTTag * stacked_tag)
    +{
    +    canclose_t result;
    +
    +    if (!stacked_tag) {
    +	result = close_NO;
    +    } else if (stacked_tag->flags & Tgf_endO) {
    +	result = close_valid;
    +    } else if (IsTagAlias(new_tag, stacked_tag)) {
    +	result = ((Tgc_same & new_tag->canclose)
    +		  ? close_error
    +		  : close_NO);
    +    } else {
    +	result = ((stacked_tag->tagclass & new_tag->canclose)
    +		  ? close_error
    +		  : close_NO);
    +    }
    +    return result;
    +}
    +
    +static void do_close_stacked(HTStream *me)
    +{
    +    HTElement *stacked = me->element_stack;
    +    HTMLElement e;
    +
    +    if (!stacked)
    +	return;			/* stack was empty */
    +    if (me->inSELECT && !strcasecomp(stacked->tag->name, "SELECT")) {
    +	me->inSELECT = FALSE;
    +    }
    +    e = NORMAL_TAGNUM(TAGNUM_OF_TAGP(stacked->tag));
    +#ifdef USE_PRETTYSRC
    +    if (!psrc_view)		/* Don't actually pass call on if viewing psrc - kw */
    +#endif
    +	(*me->actions->end_element) (me->target,
    +				     (int) e,
    +				     &me->include);
    +    me->element_stack = stacked->next;
    +    pool_free(stacked);
    +    me->no_lynx_specialcodes =
    +	(BOOL) (me->element_stack
    +		? (me->element_stack->tag->flags & Tgf_nolyspcl)
    +		: NO);
    +}
    +
    +static int is_on_stack(HTStream *me, HTTag * old_tag)
    +{
    +    HTElement *stacked = me->element_stack;
    +    int i = 1;
    +
    +    for (; stacked; stacked = stacked->next, i++) {
    +	if (IsTagAlias(stacked->tag, old_tag) ||
    +	    stacked->tag == ALT_TAGP(old_tag))
    +	    return i;
    +    }
    +    return 0;
    +}
    +
    +/*	End element
    + *	-----------
    + */
    +static void end_element(HTStream *me, HTTag * old_tag)
    +{
    +    BOOL extra_action_taken = NO;
    +    canclose_t canclose_check = close_valid;
    +    int stackpos = is_on_stack(me, old_tag);
    +    BOOL direct_container = YES;
    +
    +    if (!Old_DTD) {
    +	if (old_tag->aliases) {
    +	    if (me->element_stack) {
    +		if (!element_really_within(old_tag,
    +					   me->element_stack->tag,
    +					   direct_container) &&
    +		    element_really_within(old_tag + 1,
    +					  me->element_stack->tag,
    +					  direct_container)) {
    +		    ++old_tag;
    +		}
    +	    }
    +	}
    +	while (canclose_check != close_NO &&
    +	       me->element_stack &&
    +	       (stackpos > 1 || (!extra_action_taken && stackpos == 0))) {
    +	    if (stackpos == 0 && (old_tag->flags & Tgf_startO) &&
    +		element_valid_within(old_tag, me->element_stack->tag, YES)) {
    +		CTRACE((tfp, "SGML: </%s> ignored\n", old_tag->name));
    +		return;
    +	    }
    +	    canclose_check = can_close(old_tag, me->element_stack->tag);
    +	    if (canclose_check != close_NO) {
    +		CTRACE((tfp, "SGML: End </%s> \t<- %s end </%s>\n",
    +			me->element_stack->tag->name,
    +			((canclose_check == close_valid)
    +			 ? "supplied,"
    +			 : "***forced by"),
    +			old_tag->name));
    +		do_close_stacked(me);
    +		extra_action_taken = YES;
    +		stackpos = is_on_stack(me, old_tag);
    +	    }
    +	}
    +
    +	if (stackpos == 0 && old_tag->contents != SGML_EMPTY) {
    +	    CTRACE((tfp, "SGML: Still open %s, ***no open %s for </%s>\n",
    +		    me->element_stack ?
    +		    me->element_stack->tag->name : "none",
    +		    old_tag->name,
    +		    old_tag->name));
    +	    return;
    +	}
    +	if (stackpos > 1) {
    +	    CTRACE((tfp,
    +		    "SGML: Nesting <%s>...<%s> \t<- ***invalid end </%s>\n",
    +		    old_tag->name,
    +		    me->element_stack ?
    +		    me->element_stack->tag->name : "none",
    +		    old_tag->name));
    +	    return;
    +	}
    +    }
    +    /* Now let the non-extended code deal with the rest. - kw */
    +
    +    /*
    +     * If we are in a SELECT block, ignore anything but a SELECT end tag.  - FM
    +     */
    +    if (me->inSELECT) {
    +	if (!strcasecomp(old_tag->name, "SELECT")) {
    +	    /*
    +	     * Turn off the inSELECT flag and fall through.  - FM
    +	     */
    +	    me->inSELECT = FALSE;
    +	} else {
    +	    /*
    +	     * Ignore the end tag.  - FM
    +	     */
    +	    CTRACE((tfp, "SGML: ***Ignoring end tag </%s> in SELECT block.\n",
    +		    old_tag->name));
    +	    return;
    +	}
    +    }
    +    /*
    +     * Handle the end tag.  - FM
    +     */
    +    CTRACE((tfp, "SGML: End </%s>\n", old_tag->name));
    +    if (old_tag->contents == SGML_EMPTY) {
    +	CTRACE((tfp, "SGML: ***Illegal end tag </%s> found.\n",
    +		old_tag->name));
    +	return;
    +    }
    +#ifdef WIND_DOWN_STACK
    +    while (me->element_stack)	/* Loop is error path only */
    +#else
    +    if (me->element_stack)	/* Substitute and remove one stack element */
    +#endif /* WIND_DOWN_STACK */
    +    {
    +	int status = HT_OK;
    +	HTMLElement e;
    +	HTElement *N = me->element_stack;
    +	HTTag *t = (N->tag != old_tag) ? NORMAL_TAGP(N->tag) : N->tag;
    +
    +	if (old_tag != t) {	/* Mismatch: syntax error */
    +	    if (me->element_stack->next) {	/* This is not the last level */
    +		CTRACE((tfp,
    +			"SGML: Found </%s> when expecting </%s>. </%s> ***assumed.\n",
    +			old_tag->name, t->name, t->name));
    +	    } else {		/* last level */
    +		CTRACE((tfp,
    +			"SGML: Found </%s> when expecting </%s>. </%s> ***Ignored.\n",
    +			old_tag->name, t->name, old_tag->name));
    +		return;		/* Ignore */
    +	    }
    +	}
    +
    +	e = NORMAL_TAGNUM(TAGNUM_OF_TAGP(t));
    +	CTRACE2(TRACE_SGML, (tfp, "tagnum(%p) = %d\n", (void *) t, (int) e));
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view)		/* Don't actually pass call on if viewing psrc - kw */
    +#endif
    +	    status = (*me->actions->end_element) (me->target,
    +						  (int) e,
    +						  &me->include);
    +	if (status == HT_PARSER_REOPEN_ELT) {
    +	    CTRACE((tfp, "SGML: Restart <%s>\n", t->name));
    +	    (*me->actions->start_element) (me->target,
    +					   (int) e,
    +					   NULL,
    +					   NULL,
    +					   me->current_tag_charset,
    +					   &me->include);
    +	} else if (status == HT_PARSER_OTHER_CONTENT) {
    +	    CTRACE((tfp, "SGML: Continue with other content model for <%s>\n", t->name));
    +	    me->element_stack->tag = ALT_TAGP_OF_TAGNUM(e);
    +	} else {
    +	    me->element_stack = N->next;	/* Remove from stack */
    +	    pool_free(N);
    +	}
    +	me->no_lynx_specialcodes =
    +	    (BOOL) (me->element_stack
    +		    ? (me->element_stack->tag->flags & Tgf_nolyspcl)
    +		    : NO);
    +#ifdef WIND_DOWN_STACK
    +	if (IsTagAlias(old_tag, t))
    +	    return;		/* Correct sequence */
    +#else
    +	return;
    +#endif /* WIND_DOWN_STACK */
    +
    +	/* Syntax error path only */
    +
    +    }
    +    CTRACE((tfp, "SGML: Extra end tag </%s> found and ignored.\n",
    +	    old_tag->name));
    +}
    +
    +/*	Start a element
    +*/
    +static void start_element(HTStream *me)
    +{
    +    int status;
    +    HTTag *new_tag = me->current_tag;
    +    HTMLElement e = TAGNUM_OF_TAGP(new_tag);
    +    BOOL ok = FALSE;
    +
    +    BOOL valid = YES;
    +    BOOL direct_container = YES;
    +    BOOL extra_action_taken = NO;
    +    canclose_t canclose_check = close_valid;
    +
    +    if (!Old_DTD) {
    +	if (new_tag->aliases) {
    +	    if (me->element_stack) {
    +		if (!element_really_within(new_tag,
    +					   me->element_stack->tag,
    +					   direct_container) &&
    +		    element_really_within(new_tag + 1,
    +					  me->element_stack->tag,
    +					  direct_container)) {
    +		    ++new_tag;
    +		}
    +	    }
    +	}
    +	while (me->element_stack &&
    +	       (canclose_check == close_valid ||
    +		(canclose_check == close_error &&
    +		 IsTagAlias(new_tag, me->element_stack->tag))) &&
    +	       !(valid = element_valid_within(new_tag,
    +					      me->element_stack->tag,
    +					      direct_container))) {
    +	    canclose_check = can_close(new_tag, me->element_stack->tag);
    +	    if (canclose_check != close_NO) {
    +		CTRACE((tfp, "SGML: End </%s> \t<- %s start <%s>\n",
    +			me->element_stack->tag->name,
    +			((canclose_check == close_valid)
    +			 ? "supplied,"
    +			 : "***forced by"),
    +			new_tag->name));
    +		do_close_stacked(me);
    +		extra_action_taken = YES;
    +		if (canclose_check == close_error)
    +		    direct_container = NO;
    +	    } else {
    +		CTRACE((tfp,
    +			"SGML: Still open %s \t<- ***invalid start <%s>\n",
    +			me->element_stack->tag->name,
    +			new_tag->name));
    +	    }
    +	}
    +	if (me->element_stack && !valid &&
    +	    (me->element_stack->tag->flags & Tgf_strict) &&
    +	    !(valid = element_valid_within(new_tag,
    +					   me->element_stack->tag,
    +					   direct_container))) {
    +	    CTRACE((tfp, "SGML: Still open %s \t<- ***ignoring start <%s>\n",
    +		    me->element_stack->tag->name,
    +		    new_tag->name));
    +	    return;
    +	}
    +
    +	if (me->element_stack &&
    +	    !extra_action_taken &&
    +	    (canclose_check == close_NO) &&
    +	    !valid && (new_tag->flags & Tgf_mafse)) {
    +	    BOOL has_attributes = NO;
    +	    int i = 0;
    +
    +	    for (; i < new_tag->number_of_attributes && !has_attributes; i++)
    +		has_attributes = me->present[i];
    +	    if (!has_attributes) {
    +		CTRACE((tfp,
    +			"SGML: Still open %s, ***converting invalid <%s> to </%s>\n",
    +			me->element_stack->tag->name,
    +			new_tag->name,
    +			new_tag->name));
    +		end_element(me, new_tag);
    +		return;
    +	    }
    +	}
    +
    +	if (me->element_stack &&
    +	    (canclose_check == close_error) &&
    +	    !element_valid_within(new_tag,
    +				  me->element_stack->tag,
    +				  direct_container)) {
    +	    CTRACE((tfp, "SGML: Still open %s \t<- ***invalid start <%s>\n",
    +		    me->element_stack->tag->name,
    +		    new_tag->name));
    +	}
    +    }
    +    /* Fall through to the non-extended code - kw */
    +
    +    /*
    +     * If we are not in a SELECT block, check if this is a SELECT start tag. 
    +     * Otherwise (i.e., we are in a SELECT block) accept only OPTION as valid,
    +     * terminate the SELECT block if it is any other form-related element, and
    +     * otherwise ignore it.  - FM
    +     */
    +    if (!me->inSELECT) {
    +	/*
    +	 * We are not in a SELECT block, so check if this starts one.  - FM
    +	 * (frequent case!)
    +	 */
    +	/* my_casecomp() - optimized by the first character */
    +	if (!my_casecomp(new_tag->name, "SELECT")) {
    +	    /*
    +	     * Set the inSELECT flag and fall through.  - FM
    +	     */
    +	    me->inSELECT = TRUE;
    +	}
    +    } else {
    +	/*
    +	 * We are in a SELECT block.  - FM
    +	 */
    +	if (strcasecomp(new_tag->name, "OPTION")) {
    +	    /*
    +	     * Ugh, it is not an OPTION.  - FM
    +	     */
    +	    switch (e) {
    +	    case HTML_INPUT:
    +	    case HTML_TEXTAREA:
    +	    case HTML_SELECT:
    +	    case HTML_BUTTON:
    +	    case HTML_FIELDSET:
    +	    case HTML_LABEL:
    +	    case HTML_LEGEND:
    +	    case HTML_FORM:
    +		ok = TRUE;
    +		break;
    +	    default:
    +		break;
    +	    }
    +	    if (ok) {
    +		/*
    +		 * It is another form-related start tag, so terminate the
    +		 * current SELECT block and fall through.  - FM
    +		 */
    +		CTRACE((tfp,
    +			"SGML: ***Faking SELECT end tag before <%s> start tag.\n",
    +			new_tag->name));
    +		end_element(me, SGMLFindTag(me->dtd, "SELECT"));
    +	    } else {
    +		/*
    +		 * Ignore the start tag.  - FM
    +		 */
    +		CTRACE((tfp,
    +			"SGML: ***Ignoring start tag <%s> in SELECT block.\n",
    +			new_tag->name));
    +		return;
    +	    }
    +	}
    +    }
    +    /*
    +     * Handle the start tag.  - FM
    +     */
    +    CTRACE((tfp, "SGML: Start <%s>\n", new_tag->name));
    +    status = (*me->actions->start_element) (me->target,
    +					    (int) TAGNUM_OF_TAGP(new_tag),
    +					    me->present,
    +					    (STRING2PTR) me->value,	/* coerce type for think c */
    +					    me->current_tag_charset,
    +					    &me->include);
    +    if (status == HT_PARSER_OTHER_CONTENT)
    +	new_tag = ALT_TAGP(new_tag);	/* this is only returned for OBJECT */
    +    if (new_tag->contents != SGML_EMPTY) {	/* i.e., tag not empty */
    +	HTElement *N = pool_alloc();
    +
    +	if (N == NULL)
    +	    outofmem(__FILE__, "start_element");
    +
    +	N->next = me->element_stack;
    +	N->tag = new_tag;
    +	me->element_stack = N;
    +	me->no_lynx_specialcodes = (BOOLEAN) (new_tag->flags & Tgf_nolyspcl);
    +
    +    } else if (e == HTML_META) {
    +	/*
    +	 * Check for result of META tag.  - KW & FM
    +	 */
    +	change_chartrans_handling(me);
    +    }
    +}
    +
    +/*		Find Tag in DTD tag list
    + *		------------------------
    + *
    + * On entry,
    + *	dtd	points to dtd structure including valid tag list
    + *	string	points to name of tag in question
    + *
    + * On exit,
    + *	returns:
    + *		NULL		tag not found
    + *		else		address of tag structure in dtd
    + */
    +HTTag *SGMLFindTag(const SGML_dtd * dtd,
    +		   const char *s)
    +{
    +    int high, low, i, diff;
    +    static HTTag *last[64] =
    +    {NULL};			/*optimize using the previous results */
    +    HTTag **res = last + (UCH(*s) % 64);	/*pointer arithmetic */
    +
    +    if (*res) {
    +	if ((*res)->name == NULL)
    +	    return NULL;
    +	if (!strcasecomp((*res)->name, s))
    +	    return *res;
    +    }
    +
    +    for (low = 0, high = dtd->number_of_tags;
    +	 high > low;
    +	 diff < 0 ? (low = i + 1) : (high = i)) {	/* Binary search */
    +	i = (low + (high - low) / 2);
    +	/* my_casecomp() - optimized by the first character, NOT_ASCII ok */
    +	diff = my_casecomp(dtd->tags[i].name, s);	/* Case insensitive */
    +	if (diff == 0) {	/* success: found it */
    +	    i -= dtd->tags[i].alias;
    +	    *res = &dtd->tags[i];
    +	    return *res;
    +	}
    +    }
    +    if (IsNmStart(*s)) {
    +	/*
    +	 * Unrecognized, but may be valid.  - KW
    +	 */
    +	return &HTTag_unrecognized;
    +    }
    +    return NULL;
    +}
    +
    +/*________________________________________________________________________
    + *			Public Methods
    + */
    +
    +/*	Could check that we are back to bottom of stack! @@  */
    +/*	Do check! - FM					     */
    +/*							     */
    +static void SGML_free(HTStream *me)
    +{
    +    int i;
    +    HTElement *cur;
    +    HTTag *t;
    +
    +    /*
    +     * Free the buffers.  - FM
    +     */
    +    FREE(me->recover);
    +    FREE(me->url);
    +    FREE(me->csi);
    +    FREE(me->include);
    +    FREE(me->active_include);
    +
    +    /*
    +     * Wind down stack if any elements are open.  - FM
    +     */
    +    while (me->element_stack) {
    +	cur = me->element_stack;
    +	t = cur->tag;
    +	me->element_stack = cur->next;	/* Remove from stack */
    +	pool_free(cur);
    +#ifdef USE_PRETTYSRC
    +	if (!psrc_view)		/* Don't actually call on target if viewing psrc - kw */
    +#endif
    +	    (*me->actions->end_element)
    +		(me->target,
    +		 (int) NORMAL_TAGNUM(TAGNUM_OF_TAGP(t)),
    +		 &me->include);
    +	FREE(me->include);
    +    }
    +
    +    /*
    +     * Finish off the target.  - FM
    +     */
    +    (*me->actions->_free) (me->target);
    +
    +    /*
    +     * Free the strings and context structure.  - FM
    +     */
    +    HTChunkFree(me->string);
    +    for (i = 0; i < MAX_ATTRIBUTES; i++)
    +	FREE_extra(me->value[i]);
    +    FREE(me);
    +
    +#ifdef USE_PRETTYSRC
    +    sgml_in_psrc_was_initialized = FALSE;
    +#endif
    +}
    +
    +static void SGML_abort(HTStream *me, HTError e)
    +{
    +    int i;
    +    HTElement *cur;
    +
    +    /*
    +     * Abort the target.  - FM
    +     */
    +    (*me->actions->_abort) (me->target, e);
    +
    +    /*
    +     * Free the buffers.  - FM
    +     */
    +    FREE(me->recover);
    +    FREE(me->include);
    +    FREE(me->active_include);
    +    FREE(me->url);
    +    FREE(me->csi);
    +
    +    /*
    +     * Free stack memory if any elements were left open.  - KW
    +     */
    +    while (me->element_stack) {
    +	cur = me->element_stack;
    +	me->element_stack = cur->next;	/* Remove from stack */
    +	pool_free(cur);
    +    }
    +
    +    /*
    +     * Free the strings and context structure.  - FM
    +     */
    +    HTChunkFree(me->string);
    +    for (i = 0; i < MAX_ATTRIBUTES; i++)
    +	FREE_extra(me->value[i]);
    +    FREE(me);
    +
    +#ifdef USE_PRETTYSRC
    +    sgml_in_psrc_was_initialized = FALSE;
    +#endif
    +}
    +
    +/*	Read and write user callback handle
    + *	-----------------------------------
    + *
    + *   The callbacks from the SGML parser have an SGML context parameter.
    + *   These calls allow the caller to associate his own context with a
    + *   particular SGML context.
    + */
    +
    +#ifdef CALLERDATA
    +void *SGML_callerData(HTStream *me)
    +{
    +    return me->callerData;
    +}
    +
    +void SGML_setCallerData(HTStream *me, void *data)
    +{
    +    me->callerData = data;
    +}
    +#endif /* CALLERDATA */
    +
    +#ifdef USE_PRETTYSRC
    +static void transform_tag(HTStream *me, HTChunk *string)
    +{
    +    if (!me->strict_xml) {
    +	if (tagname_transform != 1) {
    +	    if (tagname_transform == 0)
    +		LYLowerCase(string->data);
    +	    else
    +		LYUpperCase(string->data);
    +	}
    +    }
    +}
    +#endif /* USE_PRETTYSRC */
    +
    +static BOOL ignore_when_empty(HTTag * tag)
    +{
    +    BOOL result = FALSE;
    +
    +    if (!LYPreparsedSource
    +	&& LYxhtml_parsing
    +	&& tag->name != 0
    +	&& !(tag->flags & Tgf_mafse)
    +	&& tag->contents != SGML_EMPTY
    +	&& tag->tagclass != Tgc_Plike
    +	&& (tag->tagclass == Tgc_APPLETlike
    +	    || tag->tagclass == Tgc_SELECTlike
    +	    || (tag->contains && tag->icontains))) {
    +	result = TRUE;
    +    }
    +    CTRACE((tfp, "SGML Do%s ignore_when_empty:%s\n",
    +	    result ? "" : " not",
    +	    NonNull(tag->name)));
    +    return result;
    +}
    +
    +static void discard_empty(HTStream *me)
    +{
    +    static HTTag empty_tag;
    +
    +    CTRACE((tfp, "SGML discarding empty %s\n",
    +	    NonNull(me->current_tag->name)));
    +    CTRACE_FLUSH(tfp);
    +
    +    memset(&empty_tag, 0, sizeof(empty_tag));
    +    me->current_tag = &empty_tag;
    +    me->string->size = 0;
    +
    +    /* do not call end_element() if start_element() was not called */
    +}
    +
    +#ifdef USE_PRETTYSRC
    +static BOOL end_if_prettysrc(HTStream *me, HTChunk *string, int end_ch)
    +{
    +    BOOL result = psrc_view;
    +
    +    if (psrc_view) {
    +	if (attr_is_name) {
    +	    HTStartAnchor(me->target, string->data, NULL);
    +	    (*me->actions->end_element) (me->target,
    +					 HTML_A,
    +					 &me->include);
    +	} else if (attr_is_href) {
    +	    PSRCSTART(href);
    +	    HTStartAnchor(me->target, NULL, string->data);
    +	}
    +	PUTS_TR(string->data);
    +	if (attr_is_href) {
    +	    (*me->actions->end_element) (me->target,
    +					 HTML_A,
    +					 &me->include);
    +	    PSRCSTOP(href);
    +	}
    +	if (end_ch)
    +	    PUTC(end_ch);
    +	PSRCSTOP(attrval);
    +    }
    +    return result;
    +}
    +#endif
    +
    +static void SGML_character(HTStream *me, int c_in)
    +{
    +    const SGML_dtd *dtd = me->dtd;
    +    HTChunk *string = me->string;
    +    const char *EntityName;
    +    HTTag *testtag = NULL;
    +    BOOLEAN chk;		/* Helps (?) walk through all the else ifs... */
    +    UCode_t clong, uck = 0;	/* Enough bits for UCS4 ... */
    +    int testlast;
    +
    +    unsigned char c;
    +    unsigned char saved_char_in = '\0';
    +
    +    ++sgml_offset;
    +
    +    c = UCH(c_in);
    +    clong = UCH(c);
    +
    +    if (me->T.decode_utf8) {
    +	switch (HTDecodeUTF8(&(me->U), &c_in, &clong)) {
    +	case dUTF8_ok:
    +	    if (clong < 256) {
    +		c_in = FROMASCII(UCH(clong));
    +	    }
    +	    break;
    +	case dUTF8_err:
    +	    clong = UCS_REPL;
    +	    strcpy(me->U.utf_buf, "\357\277\275");
    +	    me->U.utf_buf_p = (me->U.utf_buf + 3);
    +	    break;
    +	case dUTF8_more:
    +	    return;
    +	}
    +
    +	c = UCH(c_in);
    +	if ((me->U.utf_buf_p - me->U.utf_buf) > 1) {
    +	    goto top1;
    +	}
    +    }
    +
    +    /*
    +     * If we want the raw input converted to Unicode, try that now.  - FM
    +     */
    +#ifdef USE_JAPANESEUTF8_SUPPORT
    +    /* Convert ISO-2022-JP to Unicode (charset=iso-2022-jp is unrecognized) */
    +#define IS_JIS7_HILO(c) (0x20<(c)&&(c)<0x7F)
    +    if (UTF8_TTY_ISO2022JP && (me->state == S_nonascii_text
    +			       || me->state == S_nonascii_text_sq
    +			       || me->state == S_nonascii_text_dq)) {
    +	/* end of ISO-2022-JP? || not in ISO-2022-JP range */
    +	if (TOASCII(c) == '\033' || !IS_JIS7_HILO(c)) {
    +	    me->kanji_buf = '\0';
    +	    goto top1;
    +	}
    +	if (me->kanji_buf == '\t') {	/* flag for single byte kana in "ESC(I" */
    +	    if (conv_jisx0201kana) {
    +		JISx0201TO0208_SJIS(c | 0200,
    +				    (unsigned char *) me->U.utf_buf,
    +				    (unsigned char *) me->U.utf_buf + 1);
    +		clong = UCTransJPToUni(me->U.utf_buf, 2,
    +				       UCGetLYhndl_byMIME("shift_jis"));
    +	    } else {
    +		clong = UCTransToUni(c | 0200, UCGetLYhndl_byMIME("shift_jis"));
    +	    }
    +	} else if (me->kanji_buf) {
    +	    me->U.utf_buf[0] = (char) (me->kanji_buf | 0200);	/* to EUC-JP */
    +	    me->U.utf_buf[1] = (char) (c | 0200);
    +	    clong = UCTransJPToUni(me->U.utf_buf, 2,
    +				   UCGetLYhndl_byMIME("euc-jp"));
    +	    me->kanji_buf = '\0';
    +	} else {
    +	    me->kanji_buf = c;
    +	    clong = ucNeedMore;
    +	}
    +	goto top1;
    +    }
    +#endif /* USE_JAPANESEUTF8_SUPPORT */
    +#ifdef USE_JAPANESEUTF8_SUPPORT
    +    if (me->T.trans_to_uni &&
    +	((strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "euc-jp") == 0) ||
    +	 (strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "shift_jis") == 0))) {
    +	if (strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "shift_jis") == 0) {
    +	    if (me->U.utf_count == 0) {
    +		if (IS_SJIS_HI1(c) ||
    +		    IS_SJIS_HI2(c)) {
    +		    me->U.utf_buf[0] = (char) c;
    +		    me->U.utf_count = 1;
    +		    clong = ucCannotConvert;
    +		} else if (IS_SJIS_X0201KANA(c)) {
    +		    if (conv_jisx0201kana) {
    +			JISx0201TO0208_SJIS(c,
    +					    (unsigned char *) me->U.utf_buf,
    +					    (unsigned char *) me->U.utf_buf + 1);
    +			clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		    } else {
    +			clong = UCTransToUni(c, me->inUCLYhndl);
    +		    }
    +		}
    +	    } else {
    +		if (IS_SJIS_LO(c)) {
    +		    me->U.utf_buf[1] = (char) c;
    +		    clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		}
    +		me->U.utf_count = 0;
    +	    }
    +	} else {
    +	    if (me->U.utf_count == 0) {
    +		if (IS_EUC_HI(c) || c == 0x8E) {
    +		    me->U.utf_buf[0] = (char) c;
    +		    me->U.utf_count = 1;
    +		    clong = ucCannotConvert;
    +		}
    +	    } else {
    +		if (IS_EUC_LOX(c)) {
    +		    me->U.utf_buf[1] = (char) c;
    +		    clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		}
    +		me->U.utf_count = 0;
    +	    }
    +	}
    +	goto top1;
    +    } else
    +#endif /* USE_JAPANESEUTF8_SUPPORT */
    +#ifdef EXP_CHINESEUTF8_SUPPORT
    +	if (me->T.trans_to_uni &&
    +	    ((strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "euc-cn") == 0))) {
    +	if (me->U.utf_count == 0) {
    +	    if (IS_GBK_HI(c)) {
    +		me->U.utf_buf[0] = (char) c;
    +		me->U.utf_count = 1;
    +		clong = ucCannotConvert;
    +		CTRACE((tfp, "Get EUC-CN: 0x%02X\n", UCH(c)));
    +	    }
    +	} else {
    +	    if (IS_GBK_LO(c)) {
    +		me->U.utf_buf[1] = (char) c;
    +		clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		if (clong > 0) {
    +		    CTRACE((tfp, "... second: [%02X%02X] U+%04lX\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		} else {
    +		    CTRACE((tfp, "... second: [%02X%02X] %ld\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		}
    +	    }
    +	    me->U.utf_count = 0;
    +	}
    +	goto top1;
    +    } else
    +#endif /* EXP_CHINESEUTF8_SUPPORT */
    +#ifdef EXP_CHINESEUTF8_SUPPORT
    +	if (me->T.trans_to_uni &&
    +	    ((strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "euc-kr") == 0))) {
    +	if (me->U.utf_count == 0) {
    +	    if (IS_EUC_HI(c)) {
    +		me->U.utf_buf[0] = (char) c;
    +		me->U.utf_count = 1;
    +		clong = ucCannotConvert;
    +		CTRACE((tfp, "Get EUC-KR: 0x%02X\n", UCH(c)));
    +	    }
    +	} else {
    +	    if (IS_EUC_LOS(c) ||
    +		IS_EUC_LOX(c)) {
    +		me->U.utf_buf[1] = (char) c;
    +		clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		if (clong > 0) {
    +		    CTRACE((tfp, "... second: [%02X%02X] U+%04lX\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		} else {
    +		    CTRACE((tfp, "... second: [%02X%02X] %ld\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		}
    +	    }
    +	    me->U.utf_count = 0;
    +	}
    +	goto top1;
    +    } else
    +#endif /* EXP_CHINESEUTF8_SUPPORT */
    +#ifdef EXP_CHINESEUTF8_SUPPORT
    +	if (me->T.trans_to_uni &&
    +	    ((strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "big5") == 0))) {
    +	if (me->U.utf_count == 0) {
    +	    if (IS_BIG5_HI(c)) {
    +		me->U.utf_buf[0] = (char) c;
    +		me->U.utf_count = 1;
    +		clong = ucCannotConvert;
    +		CTRACE((tfp, "Get BIG5: 0x%02X\n", UCH(c)));
    +	    }
    +	} else {
    +	    if (IS_BIG5_LOS(c) ||
    +		IS_BIG5_LOX(c)) {
    +		me->U.utf_buf[1] = (char) c;
    +		clong = UCTransJPToUni(me->U.utf_buf, 2, me->inUCLYhndl);
    +		if (clong > 0) {
    +		    CTRACE((tfp, "... second: [%02X%02X] U+%04lX\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		} else {
    +		    CTRACE((tfp, "... second: [%02X%02X] %ld\n",
    +			    UCH(me->U.utf_buf[0]),
    +			    UCH(me->U.utf_buf[1]),
    +			    clong));
    +		}
    +	    }
    +	    me->U.utf_count = 0;
    +	}
    +	goto top1;
    +    } else
    +#endif /* EXP_CHINESEUTF8_SUPPORT */
    +	if (me->T.trans_to_uni &&
    +	/* S/390 -- gil -- 0744 */
    +	    ((TOASCII(clong) >= LYlowest_eightbit[me->inUCLYhndl]) ||
    +	     (clong < ' ' && clong != 0 &&
    +	      me->T.trans_C0_to_uni))) {
    +	/*
    +	 * Convert the octet to Unicode.  - FM
    +	 */
    +	clong = UCTransToUni((char) c, me->inUCLYhndl);
    +	if (clong > 0) {
    +	    saved_char_in = c;
    +	    if (clong < 256) {
    +		c = FROMASCII(UCH(clong));
    +	    }
    +	}
    +	goto top1;
    +    } else if (clong < ' ' && clong != 0 &&	/* S/390 -- gil -- 0768 */
    +	       me->T.trans_C0_to_uni) {
    +	/*
    +	 * This else if may be too ugly to keep.  - KW
    +	 */
    +	if (me->T.trans_from_uni &&
    +	    (((clong = UCTransToUni((char) c, me->inUCLYhndl)) >= ' ') ||
    +	     (me->T.transp &&
    +	      (clong = UCTransToUni((char) c, me->inUCLYhndl)) > 0))) {
    +	    saved_char_in = c;
    +	    if (clong < 256) {
    +		c = FROMASCII(UCH(clong));
    +	    }
    +	    goto top1;
    +	} else {
    +	    uck = -1;
    +	    if (me->T.transp) {
    +		uck = UCTransCharStr(replace_buf, 60, (char) c,
    +				     me->inUCLYhndl,
    +				     me->inUCLYhndl, NO);
    +	    }
    +	    if (!me->T.transp || uck < 0) {
    +		uck = UCTransCharStr(replace_buf, 60, (char) c,
    +				     me->inUCLYhndl,
    +				     me->outUCLYhndl, YES);
    +	    }
    +	    if (uck == 0) {
    +		return;
    +	    } else if (uck < 0) {
    +		goto top0a;
    +	    }
    +	    c = UCH(replace_buf[0]);
    +	    if (c && replace_buf[1]) {
    +		if (me->state == S_text) {
    +		    PUTS(replace_buf);
    +		    return;
    +		}
    +		StrAllocCat(me->recover, replace_buf + 1);
    +	    }
    +	    goto top0a;
    +	}			/*  Next line end of ugly stuff for C0. - KW */
    +    } else {			/* end of me->T.trans_to_uni  S/390 -- gil -- 0791 */
    +	goto top0a;
    +    }
    +
    +    /*
    +     *  We jump up to here from below if we have
    +     *  stuff in the recover, insert, or csi buffers
    +     *  to process.      We zero saved_char_in, in effect
    +     *  as a flag that the octet is not that of the
    +     *  actual call to this function.  This may be OK
    +     *  for now, for the stuff this function adds to
    +     *  its recover buffer, but it might not be for
    +     *  stuff other functions added to the insert or
    +     *  csi buffer, so bear that in mind. - FM
    +     *  Stuff from the recover buffer is now handled
    +     *  as UTF-8 if we can expect that's what it is,
    +     *  and in that case we don't come back up here. - kw
    +     */
    +  top:
    +    saved_char_in = '\0';
    +    /*
    +     *  We jump to here from above when we don't have
    +     *  UTF-8 input, haven't converted to Unicode, and
    +     *  want clong set to the input octet (unsigned)
    +     *  without zeroing its saved_char_in copy (which
    +     *  is signed). - FM
    +     */
    +  top0a:
    +    *(me->U.utf_buf) = '\0';
    +    clong = UCH(c);
    +    /*
    +     *  We jump to here from above if we have converted
    +     *  the input, or a multibyte sequence across calls,
    +     *  to a Unicode value and loaded it into clong (to
    +     *  which unsign_c has been defined), and from below
    +     *  when we are recycling a character (e.g., because
    +     *  it terminated an entity but is not the standard
    +     *  semi-colon).  The character will already have
    +     *  been put through the Unicode conversions. - FM
    +     */
    +  top1:
    +    /*
    +     * Ignore low ISO 646 7-bit control characters if HTCJK is not set.  - FM
    +     */
    +    /*
    +     * Works for both ASCII and EBCDIC. -- gil
    +     * S/390 -- gil -- 0811
    +     */
    +    if (TOASCII(clong) < 32 &&
    +	c != '\t' && c != '\n' && c != '\r' &&
    +	!IS_CJK_TTY &&
    +	!(UTF8_TTY_ISO2022JP && (TOASCII(c) == '\033')))
    +	goto after_switch;
    +
    +    /*
    +     * Ignore 127 if we don't have HTPassHighCtrlRaw or HTCJK set.  - FM
    +     */
    +#define PASSHICTRL (me->T.transp || \
    +		    clong >= LYlowest_eightbit[me->inUCLYhndl])
    +    if (TOASCII(c) == 127 &&	/* S/390 -- gil -- 0830 */
    +	!(PASSHICTRL || IS_CJK_TTY))
    +	goto after_switch;
    +
    +    /*
    +     * Ignore 8-bit control characters 128 - 159 if neither HTPassHighCtrlRaw
    +     * nor HTCJK is set.  - FM
    +     */
    +    if (TOASCII(clong) > 127 && TOASCII(clong) < 160 &&		/* S/390 -- gil -- 0847 */
    +	!(PASSHICTRL || IS_CJK_TTY)) {
    +	/*
    +	 * If we happen to be reading from an "ISO-8859-1" or "US-ASCII"
    +	 * document, allow the cp-1252 codes, to accommodate the HTML5 draft
    +	 * recommendation for replacement encoding:
    +	 *
    +	 * http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#character-encodings-0
    +	 */
    +	if (AssumeCP1252(me)) {
    +	    clong = LYcp1252ToUnicode((UCode_t) c);
    +	    goto top1;
    +	}
    +	goto after_switch;
    +    }
    +
    +    /* Almost all CJK characters are double byte but only Japanese
    +     * JIS X0201 Kana is single byte. To prevent to fail SGML parsing
    +     * we have to take care of them here. -- TH
    +     */
    +    if ((HTCJK == JAPANESE) && (me->state == S_in_kanji) &&
    +	!IS_JAPANESE_2BYTE(me->kanji_buf, UCH(c))
    +#ifdef USE_JAPANESEUTF8_SUPPORT
    +	&& !me->T.decode_utf8
    +#endif
    +	) {
    +#ifdef CONV_JISX0201KANA_JISX0208KANA
    +	if (IS_SJIS_X0201KANA(me->kanji_buf)) {
    +	    unsigned char sjis_hi, sjis_lo;
    +
    +	    JISx0201TO0208_SJIS(me->kanji_buf, &sjis_hi, &sjis_lo);
    +	    PUTC(sjis_hi);
    +	    PUTC(sjis_lo);
    +	} else
    +#endif
    +	    PUTC(me->kanji_buf);
    +	me->state = S_text;
    +    }
    +
    +    /*
    +     * Handle character based on me->state.
    +     */
    +    CTRACE2(TRACE_SGML, (tfp, "SGML before %s|%.*s|%c|\n",
    +			 state_name(me->state),
    +			 string->size,
    +			 NonNull(string->data),
    +			 UCH(c)));
    +    switch (me->state) {
    +
    +    case S_in_kanji:
    +	/*
    +	 * Note that if we don't have a CJK input, then this is not the second
    +	 * byte of a CJK di-byte, and we're trashing the input.  That's why
    +	 * 8-bit characters followed by, for example, '<' can cause the tag to
    +	 * be treated as text, not markup.  We could try to deal with it by
    +	 * holding each first byte and then checking byte pairs, but that
    +	 * doesn't seem worth the overhead (see below).  - FM
    +	 */
    +	me->state = S_text;
    +	PUTC(me->kanji_buf);
    +	PUTC(c);
    +	break;
    +
    +    case S_tagname_slash:
    +	/*
    +	 * We had something link "<name/" so far, set state to S_text but keep
    +	 * me->slashedtag as a flag; except if we get '>' directly
    +	 * after the "<name/", and really have a tag for that name in
    +	 * me->slashedtag, in which case keep state as is and let code
    +	 * below deal with it.  - kw
    +	 */
    +	if (!(c == '>' && me->slashedtag && TOASCII(clong) < 127)) {
    +	    me->state = S_text;
    +	}
    +	/* FALLTHRU */
    +    case S_text:
    +#ifdef EXP_CHINESEUTF8_SUPPORT
    +	if (IS_CJK_TTY &&
    +	    (!strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "euc-cn") ||
    +	     !strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "big5") ||
    +	     !strcmp(LYCharSet_UC[me->inUCLYhndl].MIMEname, "euc-kr"))) {
    +	    /*
    +	     * Leave the case statement if we have not collected both of the
    +	     * bytes for the EUC-CN character.  If we have, then continue on
    +	     * to convert it to Unicode.
    +	     */
    +	    if (clong == ucCannotConvert) {
    +		break;
    +	    }
    +	} else
    +#endif
    +	    if (IS_CJK_TTY && ((TOASCII(c) & 0200) != 0)
    +#ifdef USE_JAPANESEUTF8_SUPPORT
    +		&& !me->T.decode_utf8
    +#endif
    +	    ) {			/* S/390 -- gil -- 0864 */
    +	    /*
    +	     * Setting up for Kanji multibyte handling (based on Takuya ASADA's
    +	     * (asada@three-a.co.jp) CJK Lynx).  Note that if the input is not
    +	     * in fact CJK, the next byte also will be mishandled, as explained
    +	     * above.  Toggle raw mode off in such cases, or select the "7 bit
    +	     * approximations" display character set, which is largely
    +	     * equivalent to having raw mode off with CJK.  - FM
    +	     */
    +	    me->state = S_in_kanji;
    +	    me->kanji_buf = c;
    +	    break;
    +	} else if ((IS_CJK_TTY || UTF8_TTY_ISO2022JP) && TOASCII(c) == '\033') {
    +	    /* S/390 -- gil -- 0881 */
    +	    /*
    +	     * Setting up for CJK escape sequence handling (based on Takuya
    +	     * ASADA's (asada@three-a.co.jp) CJK Lynx).  - FM
    +	     */
    +	    me->state = S_esc;
    +	    if (!UTF8_TTY_ISO2022JP)
    +		PUTC(c);
    +	    break;
    +	}
    +
    +	if (c == '&' || c == '<') {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {	/*there is nothing useful in the element_stack */
    +		testtag = me->current_tag;
    +	    } else
    +#endif
    +	    {
    +		testtag = me->element_stack ?
    +		    me->element_stack->tag : NULL;
    +	    }
    +	}
    +
    +	if (c == '&' && TOASCII(clong) < 127 &&		/* S/390 -- gil -- 0898 */
    +	    (!testtag ||
    +	     (testtag->contents == SGML_MIXED ||
    +	      testtag->contents == SGML_ELEMENT ||
    +	      testtag->contents == SGML_PCDATA ||
    +#ifdef USE_PRETTYSRC
    +	      testtag->contents == SGML_EMPTY ||
    +#endif
    +	      testtag->contents == SGML_RCDATA))) {
    +	    /*
    +	     * Setting up for possible entity, without the leading '&'.  - FM
    +	     */
    +	    string->size = 0;
    +	    me->state = S_ero;
    +	} else if (c == '<' && TOASCII(clong) < 127) {	/* S/390 -- gil -- 0915 */
    +	    /*
    +	     * Setting up for possible tag.  - FM
    +	     */
    +	    string->size = 0;
    +	    if (testtag && testtag->contents == SGML_PCDATA) {
    +		me->state = S_pcdata;
    +	    } else if (testtag && (testtag->contents == SGML_LITTERAL
    +				   || testtag->contents == SGML_CDATA)) {
    +		me->state = S_litteral;
    +	    } else if (testtag && (testtag->contents == SGML_SCRIPT)) {
    +		me->state = S_script;
    +	    } else {
    +		me->state = S_tag;
    +	    }
    +	    me->slashedtag = NULL;
    +	} else if (me->slashedtag &&
    +		   me->slashedtag->name &&
    +		   (c == '/' ||
    +		    (c == '>' && me->state == S_tagname_slash)) &&
    +		   TOASCII(clong) < 127) {
    +	    /*
    +	     * We got either the second slash of a pending "<NAME/blah blah/"
    +	     * shortref construct, or the '>' of a mere "<NAME/>".  In both
    +	     * cases generate a "</NAME>" end tag in the recover buffer for
    +	     * reparsing unless NAME is really an empty element.  - kw
    +	     */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(abracket);
    +		PUTC(c);
    +		PSRCSTOP(abracket);
    +	    } else
    +#endif
    +		if (me->slashedtag != me->unknown_tag &&
    +		    !ReallyEmptyTag(me->slashedtag)) {
    +		if (me->recover == NULL) {
    +		    StrAllocCopy(me->recover, "</");
    +		    me->recover_index = 0;
    +		} else {
    +		    StrAllocCat(me->recover, "</");
    +		}
    +		StrAllocCat(me->recover, me->slashedtag->name);
    +		StrAllocCat(me->recover, ">");
    +	    }
    +	    me->slashedtag = NULL;
    +
    +	} else if (me->element_stack &&
    +		   (me->element_stack->tag->flags & Tgf_frecyc)) {
    +	    /*
    +	     * The element stack says we are within the contents of an element
    +	     * that the next stage (HTML.c) may want to feed us back again (via
    +	     * the *include string).  So try to output text in UTF-8 if
    +	     * possible, using the same logic as for attribute values (which
    +	     * should be in line with what me->current_tag_charset
    +	     * indicates).  - kw
    +	     */
    +	    if (me->T.decode_utf8 &&
    +		*me->U.utf_buf) {
    +		PUTS(me->U.utf_buf);
    +		me->U.utf_buf_p = me->U.utf_buf;
    +		*(me->U.utf_buf_p) = '\0';
    +	    } else if (!IS_CJK_TTY &&
    +		       (me->T.output_utf8 ||
    +			me->T.trans_from_uni)) {
    +		if (LYIsASCII(clong)) {
    +		    PUTC(c);
    +		} else if (clong == UCS_REPL && saved_char_in &&
    +			   HTPassEightBitRaw &&
    +			   saved_char_in >=
    +			   LYlowest_eightbit[me->outUCLYhndl]) {
    +		    PUTUTF8((UCode_t) (0xf000 | saved_char_in));
    +		} else {
    +		    PUTUTF8(clong);
    +		}
    +	    } else if (saved_char_in && me->T.use_raw_char_in) {
    +		PUTC(saved_char_in);
    +	    } else {
    +		PUTC(c);
    +	    }
    +
    +#define PASS8859SPECL me->T.pass_160_173_raw
    +	    /*
    +	     * Convert 160 (nbsp) to Lynx special character if neither
    +	     * HTPassHighCtrlRaw nor HTCJK is set.  - FM
    +	     */
    +	} else if (clong == CH_NBSP &&	/* S/390 -- gil -- 0932 */
    +		   !me->no_lynx_specialcodes &&
    +		   !(PASS8859SPECL || IS_CJK_TTY)) {
    +	    PUTC(HT_NON_BREAK_SPACE);
    +	    /*
    +	     * Convert 173 (shy) to Lynx special character if neither
    +	     * HTPassHighCtrlRaw nor HTCJK is set.  - FM
    +	     */
    +	} else if (clong == CH_SHY &&	/* S/390 -- gil -- 0949 */
    +		   !me->no_lynx_specialcodes &&
    +		   !(PASS8859SPECL || IS_CJK_TTY)) {
    +	    PUTC(LY_SOFT_HYPHEN);
    +	    /*
    +	     * Handle the case in which we think we have a character which
    +	     * doesn't need further processing (e.g., a koi8-r input for a
    +	     * koi8-r output).  - FM
    +	     */
    +	} else if (me->T.use_raw_char_in && saved_char_in) {
    +	    /*
    +	     * Only if the original character is still in saved_char_in,
    +	     * otherwise we may be iterating from a goto top.  - KW
    +	     */
    +	    PUTC(saved_char_in);
    +	} else if ((chk = (BOOL) (me->T.trans_from_uni &&
    +				  TOASCII(clong) >= 160)) &&	/* S/390 -- gil -- 0968 */
    +		   (uck = UCTransUniChar(clong,
    +					 me->outUCLYhndl)) >= ' ' &&
    +		   uck < 256) {
    +	    CTRACE((tfp, "UCTransUniChar returned 0x%.2" PRI_UCode_t
    +		    ":'%c'.\n",
    +		    uck, FROMASCII((char)uck)));
    +	    /*
    +	     * We got one octet from the conversions, so use it.  - FM
    +	     */
    +	    PUTC(FROMASCII((char) uck));
    +	} else if ((chk &&
    +		    (uck == -4 ||
    +		     (me->T.repl_translated_C0 &&
    +		      uck > 0 && uck < 32))) &&
    +	    /*
    +	     * Not found; look for replacement string.  - KW
    +	     */
    +		   (uck = UCTransUniCharStr(replace_buf, 60, clong,
    +					    me->outUCLYhndl,
    +					    0) >= 0)) {
    +	    /*
    +	     * Got a replacement string.  No further tests for validity -
    +	     * assume that whoever defined replacement strings knew what she
    +	     * was doing.  - KW
    +	     */
    +	    PUTS(replace_buf);
    +	    /*
    +	     * If we're displaying UTF-8, try that now.  - FM
    +	     */
    +	} else if (me->T.output_utf8 && PUTUTF8(clong)) {
    +	    ;			/* do nothing more */
    +	    /*
    +	     * If it's any other (> 160) 8-bit character, and we have not set
    +	     * HTPassEightBitRaw nor HTCJK, nor have the "ISO Latin 1"
    +	     * character set selected, back translate for our character set.  -
    +	     * FM
    +	     */
    +#define IncludesLatin1Enc \
    +		(me->outUCLYhndl == LATIN1 || \
    +		 (me->outUCI && \
    +		  (me->outUCI->enc & (UCT_CP_SUPERSETOF_LAT1))))
    +
    +#define PASSHI8BIT (HTPassEightBitRaw || \
    +		    (me->T.do_8bitraw && !me->T.trans_from_uni))
    +
    +	} else if (clong > 160 && clong < 256 &&
    +		   !(PASSHI8BIT || IS_CJK_TTY) &&
    +		   !IncludesLatin1Enc) {
    +#ifdef USE_PRETTYSRC
    +	    int psrc_view_backup = 0;
    +#endif
    +
    +	    string->size = 0;
    +	    EntityName = HTMLGetEntityName((UCode_t) (clong - 160));
    +	    HTChunkPuts(string, EntityName);
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    /* we need to disable it temporarily */
    +	    if (psrc_view) {
    +		psrc_view_backup = 1;
    +		psrc_view = 0;
    +	    }
    +#endif
    +	    handle_entity(me, '\0');
    +#ifdef USE_PRETTYSRC
    +	    /* we need to disable it temporarily */
    +	    if (psrc_view_backup)
    +		psrc_view = TRUE;
    +#endif
    +
    +	    string->size = 0;
    +	    if (!FoundEntity)
    +		PUTC(';');
    +	    /*
    +	     * If we get to here and have an ASCII char, pass the character.  -
    +	     * KW
    +	     */
    +	} else if (TOASCII(clong) < 127 && clong > 0) {		/* S/390 -- gil -- 0987 */
    +	    PUTC(c);
    +	    /*
    +	     * If we get to here, and should have translated, translation has
    +	     * failed so far.  - KW
    +	     *
    +	     * We should have sent UTF-8 output to the parser already, but what
    +	     * the heck, try again.  - FM
    +	     */
    +	} else if (me->T.output_utf8 && *me->U.utf_buf) {
    +	    PUTS(me->U.utf_buf);
    +	    me->U.utf_buf_p = me->U.utf_buf;
    +	    *(me->U.utf_buf_p) = '\0';
    +	    /*
    +	     * If we don't actually want the character, make it safe and output
    +	     * that now.  - FM
    +	     */
    +	} else if (TOASCII(UCH(c)) <	/* S/390 -- gil -- 0997 */
    +		   LYlowest_eightbit[me->outUCLYhndl] ||
    +		   (me->T.trans_from_uni && !HTPassEightBitRaw)) {
    +	    /*
    +	     * If we get to here, pass the character.  - FM
    +	     */
    +	} else {
    +	    PUTC(c);
    +	}
    +	break;
    +
    +	/*
    +	 * Found '<' in SGML_PCDATA content; treat this mode nearly like
    +	 * S_litteral, but recognize '<!' and '<?' to filter out comments and
    +	 * processing instructions.  - kw
    +	 */
    +    case S_pcdata:
    +	if (!string->size && TOASCII(clong) < 127) {	/* first after '<' */
    +	    if (c == '!') {	/* <! */
    +		/*
    +		 * Terminate and set up for possible comment, identifier,
    +		 * declaration, or marked section as under S_tag.  - kw
    +		 */
    +		me->state = S_exclamation;
    +		me->lead_exclamation = TRUE;
    +		me->doctype_bracket = FALSE;
    +		me->first_bracket = FALSE;
    +		HTChunkPutc(string, c);
    +		break;
    +	    } else if (c == '?') {	/* <? - ignore as a PI until '>' - kw */
    +		CTRACE((tfp,
    +			"SGML: Found PI in PCDATA, junking it until '>'\n"));
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    PSRCSTART(abracket);
    +		    PUTS("<?");
    +		    PSRCSTOP(abracket);
    +		}
    +#endif
    +		me->state = S_pi;
    +		break;
    +	    }
    +	}
    +	goto case_S_litteral;
    +
    +	/*
    +	 * Found '<' in SGML_SCRIPT content; treat this mode nearly like
    +	 * S_litteral, but recognize '<!' to allow the content to be treated as
    +	 * a comment by lynx.
    +	 */
    +    case S_script:
    +	if (!string->size && TOASCII(clong) < 127) {	/* first after '<' */
    +	    if (c == '!') {	/* <! */
    +		/*
    +		 * Terminate and set up for possible comment, identifier,
    +		 * declaration, or marked section as under S_tag.  - kw
    +		 */
    +		me->state = S_exclamation;
    +		me->lead_exclamation = TRUE;
    +		me->doctype_bracket = FALSE;
    +		me->first_bracket = FALSE;
    +		HTChunkPutc(string, c);
    +		break;
    +	    }
    +	}
    +	goto case_S_litteral;
    +
    +	/*
    +	 * In literal mode, waits only for specific end tag (for compatibility
    +	 * with old servers, and for Lynx).  - FM
    +	 */
    +      case_S_litteral:
    +    case S_litteral:
    +	/*PSRC:this case not understood completely by HV, not done */
    +	HTChunkPutc(string, c);
    +#ifdef USE_PRETTYSRC
    +	if (psrc_view) {
    +	    /* there is nothing useful in the element_stack */
    +	    testtag = me->current_tag;
    +	} else
    +#endif
    +	    testtag = (me->element_stack
    +		       ? me->element_stack->tag
    +		       : NULL);
    +
    +	if (testtag == NULL || testtag->name == NULL) {
    +	    string->size--;
    +	    me->state = S_text;
    +	    goto top1;
    +	}
    +
    +	/*
    +	 * Normally when we get the closing ">",
    +	 *      testtag contains something like "TITLE"
    +	 *      string contains something like "/title>"
    +	 * so we decrement by 2 to compare the final character of each.
    +	 */
    +	testlast = string->size - 2 - me->trailing_spaces - me->leading_spaces;
    +
    +#ifdef USE_COLOR_STYLE
    +#define TagSize(p) ((p)->name_len)
    +#else
    +#define TagSize(p) (strlen((p)->name))
    +#endif
    +
    +	if (TOUPPER(c) != ((testlast < 0)
    +			   ? '/'
    +			   : ((testlast < (int) TagSize(testtag))
    +			      ? testtag->name[testlast]
    +			      : 0))) {
    +	    int i;
    +
    +	    /*
    +	     * If complete match, end literal.
    +	     */
    +	    if ((c == '>') &&
    +		testlast >= 0 && !testtag->name[testlast]) {
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    char *trailing = NULL;
    +
    +		    if (me->trailing_spaces) {
    +			StrAllocCopy(trailing,
    +				     string->data
    +				     + string->size
    +				     - 1
    +				     - me->trailing_spaces);
    +			trailing[me->trailing_spaces] = '\0';
    +		    }
    +
    +		    PSRCSTART(abracket);
    +		    PUTS("</");
    +		    PSRCSTOP(abracket);
    +		    PSRCSTART(tag);
    +
    +		    strcpy(string->data, me->current_tag->name);
    +		    transform_tag(me, string);
    +		    PUTS(string->data);
    +
    +		    if (trailing) {
    +			PUTS(trailing);
    +			FREE(trailing);
    +		    }
    +
    +		    PSRCSTOP(tag);
    +		    PSRCSTART(abracket);
    +		    PUTC('>');
    +		    PSRCSTOP(abracket);
    +
    +		    me->current_tag = NULL;
    +		} else
    +#endif
    +		    end_element(me, me->element_stack->tag);
    +
    +		string->size = 0;
    +		me->current_attribute_number = INVALID;
    +		me->state = S_text;
    +		me->leading_spaces = 0;
    +		me->trailing_spaces = 0;
    +		break;
    +	    }
    +
    +	    /*
    +	     * Allow whitespace between the "<" or ">" and the keyword, for
    +	     * error-recovery.
    +	     */
    +	    if (isspace(UCH(c))) {
    +		if (testlast == -1) {
    +		    me->leading_spaces += 1;
    +		    CTRACE2(TRACE_SGML, (tfp, "leading spaces: %d\n", me->leading_spaces));
    +		    break;
    +		} else if (testlast > 0) {
    +		    me->trailing_spaces += 1;
    +		    CTRACE2(TRACE_SGML, (tfp, "trailing spaces: %d\n", me->trailing_spaces));
    +		    break;
    +		}
    +	    }
    +
    +	    /*
    +	     * Mismatch - recover.
    +	     */
    +	    me->leading_spaces = 0;
    +	    me->trailing_spaces = 0;
    +	    if (((testtag->contents != SGML_LITTERAL &&
    +		  (testtag->flags & Tgf_strict)) ||
    +		 (me->state == S_pcdata &&
    +		  (testtag->flags & (Tgf_strict | Tgf_endO)))) &&
    +		(testlast > -1 &&
    +		 (c == '>' || testlast > 0 || IsNmStart(c)))) {
    +		me->state = S_end;
    +		string->size--;
    +		for (i = 0; i < string->size; i++)	/* remove '/' */
    +		    string->data[i] = string->data[i + 1];
    +		if ((string->size == 1) ? IsNmStart(c) : IsNmChar(c))
    +		    break;
    +		string->size--;
    +		goto top1;
    +	    }
    +	    if (me->state == S_pcdata &&
    +		(testtag->flags & (Tgf_strict | Tgf_endO)) &&
    +		(testlast < 0 && IsNmStart(c))) {
    +		me->state = S_tag;
    +		break;
    +	    }
    +	    /*
    +	     * If Mismatch:  recover string literally.
    +	     */
    +	    PUTC('<');
    +	    for (i = 0; i < string->size - 1; i++)	/* recover, except last c */
    +		PUTC(string->data[i]);
    +	    string->size = 0;
    +	    me->state = S_text;
    +	    goto top1;		/* to recover last c */
    +	}
    +	break;
    +
    +	/*
    +	 * Character reference (numeric entity) or named entity.
    +	 */
    +    case S_ero:
    +	if (c == '#') {
    +	    /*
    +	     * Setting up for possible numeric entity.
    +	     */
    +	    me->state = S_cro;	/* &# is Char Ref Open */
    +	    break;
    +	}
    +	me->state = S_entity;	/* Fall through! */
    +
    +	/*
    +	 * Handle possible named entity.
    +	 */
    +    case S_entity:
    +	if (TOASCII(clong) < 127 && (string->size ?	/* S/390 -- gil -- 1029 */
    +				     isalnum(UCH(c)) : isalpha(UCH(c)))) {
    +	    /* Should probably use IsNmStart/IsNmChar above (is that right?),
    +	       but the world is not ready for that - there's  : (note
    +	       colon!) and stuff around. */
    +	    /*
    +	     * Accept valid ASCII character.  - FM
    +	     */
    +	    HTChunkPutc(string, c);
    +	} else if (string->size == 0) {
    +	    /*
    +	     * It was an ampersand that's just text, so output the ampersand
    +	     * and recycle this character.  - FM
    +	     */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTART(badseq);
    +#endif
    +	    PUTC('&');
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTOP(badseq);
    +#endif
    +	    me->state = S_text;
    +	    goto top1;
    +	} else {
    +	    /*
    +	     * Terminate entity name and try to handle it.  - FM
    +	     */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    entity_string = string->data;
    +#endif
    +	    if (!strcmp(string->data, "zwnj") &&
    +		(!me->element_stack ||
    +		 (me->element_stack->tag &&
    +		  me->element_stack->tag->contents == SGML_MIXED))) {
    +		/*
    +		 * Handle zwnj (8204) as <WBR>.  - FM
    +		 */
    +		char temp[8];
    +
    +		CTRACE((tfp,
    +			"SGML_character: Handling 'zwnj' entity as 'WBR' element.\n"));
    +
    +		if (c != ';') {
    +		    sprintf(temp, "<WBR>%c", c);
    +		} else {
    +		    sprintf(temp, "<WBR>");
    +		}
    +		if (me->recover == NULL) {
    +		    StrAllocCopy(me->recover, temp);
    +		    me->recover_index = 0;
    +		} else {
    +		    StrAllocCat(me->recover, temp);
    +		}
    +		string->size = 0;
    +		me->state = S_text;
    +		break;
    +	    } else {
    +		handle_entity(me, '\0');
    +	    }
    +	    string->size = 0;
    +	    me->state = S_text;
    +	    /*
    +	     * Don't eat the terminator if we didn't find the entity name and
    +	     * therefore sent the raw string via handle_entity(), or if the
    +	     * terminator is not the "standard" semi-colon for HTML.  - FM
    +	     */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view && FoundEntity && c == ';') {
    +		PSRCSTART(entity);
    +		PUTC(c);
    +		PSRCSTOP(entity);
    +	    }
    +#endif
    +	    if (!FoundEntity || c != ';')
    +		goto top1;
    +	}
    +	break;
    +
    +	/*
    +	 * Check for a numeric entity.
    +	 */
    +    case S_cro:
    +	if (TOASCII(clong) < 127 && TOLOWER(UCH(c)) == 'x') {	/* S/390 -- gil -- 1060 */
    +	    me->isHex = TRUE;
    +	    me->state = S_incro;
    +	} else if (TOASCII(clong) < 127 && isdigit(UCH(c))) {
    +	    /*
    +	     * Accept only valid ASCII digits.  - FM
    +	     */
    +	    HTChunkPutc(string, c);	/* accumulate a character NUMBER */
    +	    me->isHex = FALSE;
    +	    me->state = S_incro;
    +	} else if (string->size == 0) {
    +	    /*
    +	     * No 'x' or digit following the "&#" so recover them and recycle
    +	     * the character.  - FM
    +	     */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTART(badseq);
    +#endif
    +	    PUTC('&');
    +	    PUTC('#');
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTOP(badseq);
    +#endif
    +	    me->state = S_text;
    +	    goto top1;
    +	}
    +	break;
    +
    +	/*
    +	 * Handle a numeric entity.
    +	 */
    +    case S_incro:
    +	/* S/390 -- gil -- 1075 */
    +	if ((TOASCII(clong) < 127) &&
    +	    (me->isHex
    +	     ? isxdigit(UCH(c))
    +	     : isdigit(UCH(c)))) {
    +	    /*
    +	     * Accept only valid hex or ASCII digits.  - FM
    +	     */
    +	    HTChunkPutc(string, c);	/* accumulate a character NUMBER */
    +	} else if (string->size == 0) {
    +	    /*
    +	     * No hex digit following the "&#x" so recover them and recycle the
    +	     * character.  - FM
    +	     */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTART(badseq);
    +#endif
    +	    PUTS("&#x");
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTOP(badseq);
    +#endif
    +	    me->isHex = FALSE;
    +	    me->state = S_text;
    +	    goto top1;
    +	} else {
    +	    /*
    +	     * Terminate the numeric entity and try to handle it.  - FM
    +	     */
    +	    UCode_t code;
    +	    int i;
    +
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    entity_string = string->data;
    +#endif
    +	    if (UCScanCode(&code, string->data, me->isHex)) {
    +
    +/* =============== work in ASCII below here ===============  S/390 -- gil -- 1092 */
    +		if (AssumeCP1252(me)) {
    +		    code = LYcp1252ToUnicode(code);
    +		}
    +		/*
    +		 * Check for special values.  - FM
    +		 */
    +		if ((code == 8204) &&
    +		    (!me->element_stack ||
    +		     (me->element_stack->tag &&
    +		      me->element_stack->tag->contents == SGML_MIXED))) {
    +		    /*
    +		     * Handle zwnj (8204) as <WBR>.  - FM
    +		     */
    +		    char temp[8];
    +
    +		    CTRACE((tfp,
    +			    "SGML_character: Handling '8204' (zwnj) reference as 'WBR' element.\n"));
    +
    +		    /*
    +		     * Include the terminator if it is not the standard
    +		     * semi-colon.  - FM
    +		     */
    +		    if (c != ';') {
    +			sprintf(temp, "<WBR>%c", c);
    +		    } else {
    +			sprintf(temp, "<WBR>");
    +		    }
    +		    /*
    +		     * Add the replacement string to the recover buffer for
    +		     * processing.  - FM
    +		     */
    +		    if (me->recover == NULL) {
    +			StrAllocCopy(me->recover, temp);
    +			me->recover_index = 0;
    +		    } else {
    +			StrAllocCat(me->recover, temp);
    +		    }
    +		    string->size = 0;
    +		    me->isHex = FALSE;
    +		    me->state = S_text;
    +		    break;
    +		} else if (put_special_unicodes(me, code)) {
    +		    /*
    +		     * We handled the value as a special character, so recycle
    +		     * the terminator or break.  - FM
    +		     */
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			PSRCSTART(entity);
    +			PUTS((me->isHex ? "&#x" : "&#"));
    +			PUTS(entity_string);
    +			if (c == ';')
    +			    PUTC(';');
    +			PSRCSTOP(entity);
    +		    }
    +#endif
    +		    string->size = 0;
    +		    me->isHex = FALSE;
    +		    me->state = S_text;
    +		    if (c != ';')
    +			goto top1;
    +		    break;
    +		}
    +		/*
    +		 * Seek a translation from the chartrans tables.
    +		 */
    +		if ((uck = UCTransUniChar(code,
    +					  me->outUCLYhndl)) >= 32 &&
    +		    uck < 256 &&
    +		    (uck < 127 ||
    +		     uck >= LYlowest_eightbit[me->outUCLYhndl])) {
    +#ifdef USE_PRETTYSRC
    +		    if (!psrc_view) {
    +#endif
    +			PUTC(FROMASCII((char) uck));
    +#ifdef USE_PRETTYSRC
    +		    } else {
    +			put_pretty_number(me);
    +		    }
    +#endif
    +		} else if ((uck == -4 ||
    +			    (me->T.repl_translated_C0 &&
    +			     uck > 0 && uck < 32)) &&
    +		    /*
    +		     * Not found; look for replacement string.
    +		     */
    +			   (uck = UCTransUniCharStr(replace_buf, 60, code,
    +						    me->outUCLYhndl,
    +						    0) >= 0)) {
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			put_pretty_number(me);
    +		    } else
    +#endif
    +			PUTS(replace_buf);
    +		    /*
    +		     * If we're displaying UTF-8, try that now.  - FM
    +		     */
    +		} else if (me->T.output_utf8 && PUTUTF8(code)) {
    +		    ;		/* do nothing more */
    +		    /*
    +		     * Ignore 8205 (zwj), 8206 (lrm), and 8207 (rln), if we get
    +		     * to here.  - FM
    +		     */
    +		} else if (code == 8205 ||
    +			   code == 8206 ||
    +			   code == 8207) {
    +		    if (TRACE) {
    +			string->size--;
    +			LYStrNCpy(replace_buf,
    +				  string->data,
    +				  (string->size < 64 ? string->size : 63));
    +			fprintf(tfp,
    +				"SGML_character: Ignoring '%s%s'.\n",
    +				(me->isHex ? "&#x" : "&#"),
    +				replace_buf);
    +		    }
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			PSRCSTART(badseq);
    +			PUTS((me->isHex ? "&#x" : "&#"));
    +			PUTS(entity_string);
    +			if (c == ';')
    +			    PUTC(';');
    +			PSRCSTOP(badseq);
    +		    }
    +#endif
    +		    string->size = 0;
    +		    me->isHex = FALSE;
    +		    me->state = S_text;
    +		    if (c != ';')
    +			goto top1;
    +		    break;
    +		    /*
    +		     * Show the numeric entity if we get to here and the value:
    +		     * (1) Is greater than 255 (but use ASCII characters for
    +		     * spaces or dashes).
    +		     * (2) Is less than 32, and not valid or we don't have
    +		     * HTCJK set.
    +		     * (3) Is 127 and we don't have HTPassHighCtrlRaw or HTCJK
    +		     * set.
    +		     * (4) Is 128 - 159 and we don't have HTPassHighCtrlNum
    +		     * set.
    +		     * - FM
    +		     */
    +		} else if ((code > 255) ||
    +			   (code < ' ' &&	/* S/390 -- gil -- 1140 */
    +			    code != '\t' && code != '\n' && code != '\r' &&
    +			    !IS_CJK_TTY) ||
    +			   (TOASCII(code) == 127 &&
    +			    !(HTPassHighCtrlRaw || IS_CJK_TTY)) ||
    +			   (TOASCII(code) > 127 && code < 160 &&
    +			    !HTPassHighCtrlNum)) {
    +		    /*
    +		     * Unhandled or illegal value.  Recover the "&#" or "&#x"
    +		     * and digit(s), and recycle the terminator.  - FM
    +		     */
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			PSRCSTART(badseq);
    +		    }
    +#endif
    +		    if (me->isHex) {
    +			PUTS("&#x");
    +			me->isHex = FALSE;
    +		    } else {
    +			PUTS("&#");
    +		    }
    +		    string->size--;
    +		    for (i = 0; i < string->size; i++)	/* recover */
    +			PUTC(string->data[i]);
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			PSRCSTOP(badseq);
    +		    }
    +#endif
    +		    string->size = 0;
    +		    me->isHex = FALSE;
    +		    me->state = S_text;
    +		    goto top1;
    +		} else if (TOASCII(code) < 161 ||	/* S/390 -- gil -- 1162 */
    +			   HTPassEightBitNum ||
    +			   IncludesLatin1Enc) {
    +		    /*
    +		     * No conversion needed.  - FM
    +		     */
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			put_pretty_number(me);
    +		    } else
    +#endif
    +			PUTC(FROMASCII((char) code));
    +		} else {
    +		    /*
    +		     * Handle as named entity.  - FM
    +		     */
    +		    code -= 160;
    +		    EntityName = HTMLGetEntityName(code);
    +		    if (EntityName && EntityName[0] != '\0') {
    +			string->size = 0;
    +			HTChunkPuts(string, EntityName);
    +			HTChunkTerminate(string);
    +			handle_entity(me, '\0');
    +			/*
    +			 * Add a semi-colon if something went wrong and
    +			 * handle_entity() sent the string.  - FM
    +			 */
    +			if (!FoundEntity) {
    +			    PUTC(';');
    +			}
    +		    } else {
    +			/*
    +			 * Our conversion failed, so recover the "&#" and
    +			 * digit(s), and recycle the terminator.  - FM
    +			 */
    +#ifdef USE_PRETTYSRC
    +			if (psrc_view)
    +			    PSRCSTART(badseq);
    +#endif
    +			if (me->isHex) {
    +			    PUTS("&#x");
    +			    me->isHex = FALSE;
    +			} else {
    +			    PUTS("&#");
    +			}
    +			string->size--;
    +			for (i = 0; i < string->size; i++)	/* recover */
    +			    PUTC(string->data[i]);
    +#ifdef USE_PRETTYSRC
    +			if (psrc_view)
    +			    PSRCSTOP(badseq);
    +#endif
    +			string->size = 0;
    +			me->isHex = FALSE;
    +			me->state = S_text;
    +			goto top1;
    +		    }
    +		}
    +		/*
    +		 * If we get to here, we succeeded.  Hoorah!!!  - FM
    +		 */
    +		string->size = 0;
    +		me->isHex = FALSE;
    +		me->state = S_text;
    +		/*
    +		 * Don't eat the terminator if it's not the "standard"
    +		 * semi-colon for HTML.  - FM
    +		 */
    +		if (c != ';') {
    +		    goto top1;
    +		}
    +	    } else {
    +		/*
    +		 * Not an entity, and don't know why not, so add the terminator
    +		 * to the string, output the "&#" or "&#x", and process the
    +		 * string via the recover element.  - FM
    +		 */
    +		string->size--;
    +		HTChunkPutc(string, c);
    +		HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view)
    +		    PSRCSTART(badseq);
    +#endif
    +		if (me->isHex) {
    +		    PUTS("&#x");
    +		    me->isHex = FALSE;
    +		} else {
    +		    PUTS("&#");
    +		}
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view)
    +		    PSRCSTOP(badseq);
    +#endif
    +		if (me->recover == NULL) {
    +		    StrAllocCopy(me->recover, string->data);
    +		    me->recover_index = 0;
    +		} else {
    +		    StrAllocCat(me->recover, string->data);
    +		}
    +		string->size = 0;
    +		me->isHex = FALSE;
    +		me->state = S_text;
    +		break;
    +	    }
    +	}
    +	break;
    +
    +	/*
    +	 * Tag
    +	 */
    +    case S_tag:		/* new tag */
    +	if (TOASCII(clong) < 127 && (string->size ?	/* S/390 -- gil -- 1179 */
    +				     IsNmChar(c) : IsNmStart(c))) {
    +	    /*
    +	     * Add valid ASCII character.  - FM
    +	     */
    +	    HTChunkPutc(string, c);
    +	} else if (c == '!' && !string->size) {		/* <! */
    +	    /*
    +	     * Terminate and set up for possible comment, identifier,
    +	     * declaration, or marked section.  - FM
    +	     */
    +	    me->state = S_exclamation;
    +	    me->lead_exclamation = TRUE;
    +	    me->doctype_bracket = FALSE;
    +	    me->first_bracket = FALSE;
    +	    HTChunkPutc(string, c);
    +	    break;
    +	} else if (!string->size &&
    +		   (TOASCII(clong) <= 160 &&	/* S/390 -- gil -- 1196 */
    +		    (c != '/' && c != '?' && c != '_' && c != ':'))) {
    +	    /*
    +	     * '<' must be followed by an ASCII letter to be a valid start tag. 
    +	     * Here it isn't, nor do we have a '/' for an end tag, nor one of
    +	     * some other characters with a special meaning for SGML or which
    +	     * are likely to be legal Name Start characters in XML or some
    +	     * other extension.  So recover the '<' and following character as
    +	     * data.  - FM & KW
    +	     */
    +	    me->state = S_text;
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTART(badseq);
    +#endif
    +	    PUTC('<');
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view)
    +		PSRCSTOP(badseq);
    +#endif
    +	    goto top1;
    +	} else {		/* End of tag name */
    +	    /*
    +	     * Try to handle tag.  - FM
    +	     */
    +	    HTTag *t;
    +
    +	    if (c == '/') {
    +		if (string->size == 0) {
    +		    me->state = S_end;
    +		    break;
    +		}
    +		CTRACE((tfp, "SGML: `<%.*s/' found!\n", string->size, string->data));
    +	    }
    +	    HTChunkTerminate(string);
    +
    +	    t = SGMLFindTag(dtd, string->data);
    +	    if (t == me->unknown_tag &&
    +		((c == ':' &&
    +		  string->size == 4 && 0 == strcasecomp(string->data, "URL")) ||
    +		 (string->size > 4 && 0 == strncasecomp(string->data, "URL:", 4)))) {
    +		/*
    +		 * Treat <URL:  as text rather than a junk tag, so we display
    +		 * it and the URL (Lynxism 8-).  - FM
    +		 */
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view)
    +		    PSRCSTART(badseq);
    +#endif
    +		PUTC('<');
    +		PUTS(string->data);	/* recover */
    +		PUTC(c);
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view)
    +		    PSRCSTOP(badseq);
    +#endif
    +		CTRACE((tfp, "SGML: Treating <%s%c as text\n",
    +			string->data, c));
    +		string->size = 0;
    +		me->state = S_text;
    +		break;
    +	    }
    +	    if (c == '/' && t) {
    +		/*
    +		 * Element name was ended by '/'.  Remember the tag that ended
    +		 * thusly, we'll interpret this as either an indication of an
    +		 * empty element (if '>' follows directly) or do some
    +		 * SGMLshortref-ish treatment.  - kw
    +		 */
    +		me->slashedtag = t;
    +	    }
    +	    if (!t) {
    +		if (c == '?' && string->size <= 1) {
    +		    CTRACE((tfp, "SGML: Found PI, looking for '>'\n"));
    +#ifdef USE_PRETTYSRC
    +		    if (psrc_view) {
    +			PSRCSTART(abracket);
    +			PUTS("<?");
    +			PSRCSTOP(abracket);
    +		    }
    +#endif
    +		    string->size = 0;
    +		    me->state = S_pi;
    +		    HTChunkPutc(string, c);
    +		    break;
    +		}
    +		CTRACE((tfp, "SGML: *** Invalid element %s\n",
    +			string->data));
    +
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    PSRCSTART(abracket);
    +		    PUTC('<');
    +		    PSRCSTOP(abracket);
    +		    PSRCSTART(badtag);
    +		    transform_tag(me, string);
    +		    PUTS(string->data);
    +		    if (c == '>') {
    +			PSRCSTOP(badtag);
    +			PSRCSTART(abracket);
    +			PUTC('>');
    +			PSRCSTOP(abracket);
    +		    } else {
    +			PUTC(c);
    +		    }
    +		}
    +#endif
    +		me->state = (c == '>') ? S_text : S_junk_tag;
    +		break;
    +	    } else if (t == me->unknown_tag) {
    +		CTRACE((tfp, "SGML: *** Unknown element \"%s\"\n",
    +			string->data));
    +		/*
    +		 * Fall through and treat like valid tag for attribute parsing. 
    +		 * - KW
    +		 */
    +
    +	    }
    +	    me->current_tag = t;
    +
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(abracket);
    +		PUTC('<');
    +		PSRCSTOP(abracket);
    +		if (t != me->unknown_tag)
    +		    PSRCSTART(tag);
    +		else
    +		    PSRCSTART(badtag);
    +		transform_tag(me, string);
    +		PUTS(string->data);
    +		if (t != me->unknown_tag)
    +		    PSRCSTOP(tag);
    +		else
    +		    PSRCSTOP(badtag);
    +	    }
    +	    if (!psrc_view)	/*don't waste time */
    +#endif
    +	    {
    +		/*
    +		 * Clear out attributes.
    +		 */
    +		memset((void *) me->present, 0, sizeof(BOOL) *
    +		         (unsigned) (me->current_tag->number_of_attributes));
    +	    }
    +
    +	    string->size = 0;
    +	    me->current_attribute_number = INVALID;
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		if (c == '>' || c == '<' || (c == '/' && me->slashedtag)) {
    +		    if (c != '<') {
    +			PSRCSTART(abracket);
    +			PUTC(c);
    +			PSRCSTOP(abracket);
    +			me->state = (c == '>') ? S_text : S_tagname_slash;
    +		    } else {
    +			me->state = S_tag;
    +		    }
    +		} else {
    +		    if (!WHITE(c))
    +			PUTC(c);
    +		    me->state = S_tag_gap;
    +		}
    +	    } else
    +#endif
    +	    if (c == '>' || c == '<' || (c == '/' && me->slashedtag)) {
    +		if (me->current_tag->name)
    +		    start_element(me);
    +		me->state = (c == '>') ? S_text :
    +		    (c == '<') ? S_tag : S_tagname_slash;
    +	    } else {
    +		me->state = S_tag_gap;
    +	    }
    +	}
    +	break;
    +
    +    case S_exclamation:
    +	if (me->lead_exclamation && c == '-') {
    +	    /*
    +	     * Set up for possible comment.  - FM
    +	     */
    +	    me->lead_exclamation = FALSE;
    +	    me->first_dash = TRUE;
    +	    HTChunkPutc(string, c);
    +	    break;
    +	}
    +	if (me->lead_exclamation && c == '[') {
    +	    /*
    +	     * Set up for possible marked section.  - FM
    +	     */
    +	    me->lead_exclamation = FALSE;
    +	    me->first_bracket = TRUE;
    +	    me->second_bracket = FALSE;
    +	    HTChunkPutc(string, c);
    +	    me->state = S_marked;
    +	    break;
    +	}
    +	if (me->first_dash && c == '-') {
    +	    /*
    +	     * Set up to handle comment.  - FM
    +	     */
    +	    me->lead_exclamation = FALSE;
    +	    me->first_dash = FALSE;
    +	    me->end_comment = FALSE;
    +	    HTChunkPutc(string, c);
    +	    me->state = S_comment;
    +	    break;
    +	}
    +	me->lead_exclamation = FALSE;
    +	me->first_dash = FALSE;
    +	if (c == '>') {
    +	    /*
    +	     * Try to handle identifier.  - FM
    +	     */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_identifier(me);
    +	    string->size = 0;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	if (WHITE(c)) {
    +	    if (string->size == 8 &&
    +		!strncasecomp(string->data, "!DOCTYPE", 8)) {
    +		/*
    +		 * Set up for DOCTYPE declaration.  - FM
    +		 */
    +		HTChunkPutc(string, c);
    +		me->doctype_bracket = FALSE;
    +		me->state = S_doctype;
    +		break;
    +	    }
    +	    if (string->size == 7 &&
    +		!strncasecomp(string->data, "!ENTITY", 7)) {
    +		/*
    +		 * Set up for ENTITY declaration.  - FM
    +		 */
    +		HTChunkPutc(string, c);
    +		me->first_dash = FALSE;
    +		me->end_comment = TRUE;
    +		me->state = S_sgmlent;
    +		break;
    +	    }
    +	    if (string->size == 8 &&
    +		!strncasecomp(string->data, "!ELEMENT", 8)) {
    +		/*
    +		 * Set up for ELEMENT declaration.  - FM
    +		 */
    +		HTChunkPutc(string, c);
    +		me->first_dash = FALSE;
    +		me->end_comment = TRUE;
    +		me->state = S_sgmlele;
    +		break;
    +	    }
    +	    if (string->size == 8 &&
    +		!strncasecomp(string->data, "!ATTLIST", 8)) {
    +		/*
    +		 * Set up for ATTLIST declaration.  - FM
    +		 */
    +		HTChunkPutc(string, c);
    +		me->first_dash = FALSE;
    +		me->end_comment = TRUE;
    +		me->state = S_sgmlatt;
    +		break;
    +	    }
    +	}
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_comment:		/* Expecting comment. - FM */
    +	if (historical_comments) {
    +	    /*
    +	     * Any '>' terminates.  - FM
    +	     */
    +	    if (c == '>') {
    +		HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    PSRCSTART(comm);
    +		    PUTC('<');
    +		    PUTS_TR(string->data);
    +		    PUTC('>');
    +		    PSRCSTOP(comm);
    +		} else
    +#endif
    +		    handle_comment(me);
    +		string->size = 0;
    +		me->end_comment = FALSE;
    +		me->first_dash = FALSE;
    +		me->state = S_text;
    +		break;
    +	    }
    +	    goto S_comment_put_c;
    +	}
    +	if (!me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = TRUE;
    +	    break;
    +	}
    +	if (me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = FALSE;
    +	    if (!me->end_comment)
    +		me->end_comment = TRUE;
    +	    else if (!minimal_comments)
    +		/*
    +		 * Validly treat '--' pairs as successive comments (for
    +		 * minimal, any "--WHITE>" terminates).  - FM
    +		 */
    +		me->end_comment = FALSE;
    +	    break;
    +	}
    +	if (me->end_comment && c == '>') {
    +	    /*
    +	     * Terminate and handle the comment.  - FM
    +	     */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(comm);
    +		PUTC('<');
    +		PUTS_TR(string->data);
    +		PUTC('>');
    +		PSRCSTOP(comm);
    +	    } else
    +#endif
    +		handle_comment(me);
    +	    string->size = 0;
    +	    me->end_comment = FALSE;
    +	    me->first_dash = FALSE;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	me->first_dash = FALSE;
    +	if (me->end_comment && !isspace(UCH(c)))
    +	    me->end_comment = FALSE;
    +
    +      S_comment_put_c:
    +	if (me->T.decode_utf8 &&
    +	    *me->U.utf_buf) {
    +	    HTChunkPuts(string, me->U.utf_buf);
    +	    me->U.utf_buf_p = me->U.utf_buf;
    +	    *(me->U.utf_buf_p) = '\0';
    +	} else if (!IS_CJK_TTY &&
    +		   (me->T.output_utf8 ||
    +		    me->T.trans_from_uni)) {
    +	    if (clong == UCS_REPL && saved_char_in &&
    +		HTPassEightBitRaw &&
    +		saved_char_in >=
    +		LYlowest_eightbit[me->outUCLYhndl]) {
    +		(HTChunkPutUtf8Char) (string,
    +				      (UCode_t) (0xf000 | saved_char_in));
    +	    } else {
    +		HTChunkPutUtf8Char(string, clong);
    +	    }
    +	} else if (saved_char_in && me->T.use_raw_char_in) {
    +	    HTChunkPutc(string, saved_char_in);
    +	} else {
    +	    HTChunkPutc(string, c);
    +	}
    +	break;
    +
    +    case S_doctype:		/* Expecting DOCTYPE. - FM */
    +	if (me->doctype_bracket) {
    +	    HTChunkPutc(string, c);
    +	    if (c == ']')
    +		me->doctype_bracket = FALSE;
    +	    break;
    +	}
    +	if (c == '[' && WHITE(string->data[string->size - 1])) {
    +	    HTChunkPutc(string, c);
    +	    me->doctype_bracket = TRUE;
    +	    break;
    +	}
    +	if (c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_doctype(me);
    +	    string->size = 0;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_marked:		/* Expecting marked section. - FM */
    +	if (me->first_bracket && c == '[') {
    +	    HTChunkPutc(string, c);
    +	    me->first_bracket = FALSE;
    +	    me->second_bracket = TRUE;
    +	    break;
    +	}
    +	if (me->second_bracket && c == ']' &&
    +	    string->data[string->size - 1] == ']') {
    +	    HTChunkPutc(string, c);
    +	    me->second_bracket = FALSE;
    +	    break;
    +	}
    +	if (!me->second_bracket && c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_marked(me);
    +	    string->size = 0;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_sgmlent:		/* Expecting ENTITY. - FM */
    +	if (!me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = TRUE;
    +	    break;
    +	}
    +	if (me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = FALSE;
    +	    if (!me->end_comment)
    +		me->end_comment = TRUE;
    +	    else
    +		me->end_comment = FALSE;
    +	    break;
    +	}
    +	if (me->end_comment && c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_sgmlent(me);
    +	    string->size = 0;
    +	    me->end_comment = FALSE;
    +	    me->first_dash = FALSE;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	me->first_dash = FALSE;
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_sgmlele:		/* Expecting ELEMENT. - FM */
    +	if (!me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = TRUE;
    +	    break;
    +	}
    +	if (me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = FALSE;
    +	    if (!me->end_comment)
    +		me->end_comment = TRUE;
    +	    else
    +		me->end_comment = FALSE;
    +	    break;
    +	}
    +	if (me->end_comment && c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_sgmlele(me);
    +	    string->size = 0;
    +	    me->end_comment = FALSE;
    +	    me->first_dash = FALSE;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	me->first_dash = FALSE;
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_sgmlatt:		/* Expecting ATTLIST. - FM */
    +	if (!me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = TRUE;
    +	    break;
    +	}
    +	if (me->first_dash && c == '-') {
    +	    HTChunkPutc(string, c);
    +	    me->first_dash = FALSE;
    +	    if (!me->end_comment)
    +		me->end_comment = TRUE;
    +	    else
    +		me->end_comment = FALSE;
    +	    break;
    +	}
    +	if (me->end_comment && c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(sgmlspecial);
    +		PUTC('<');
    +		PUTS(string->data);
    +		PUTC('>');
    +		PSRCSTOP(sgmlspecial);
    +	    } else
    +#endif
    +		handle_sgmlatt(me);
    +	    string->size = 0;
    +	    me->end_comment = FALSE;
    +	    me->first_dash = FALSE;
    +	    me->state = S_text;
    +	    break;
    +	}
    +	me->first_dash = FALSE;
    +	HTChunkPutc(string, c);
    +	break;
    +
    +    case S_tag_gap:		/* Expecting attribute or '>' */
    +	if (WHITE(c)) {
    +	    /* PUTC(c); - no, done as special case */
    +	    break;		/* Gap between attributes */
    +	}
    +	if (c == '>') {		/* End of tag */
    +#ifdef USE_PRETTYSRC
    +	    if (!psrc_view)
    +#endif
    +		if (me->current_tag->name)
    +		    start_element(me);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(abracket);
    +		PUTC('>');
    +		PSRCSTOP(abracket);
    +	    }
    +#endif
    +	    me->state = S_text;
    +	    break;
    +	}
    +	HTChunkPutc(string, c);
    +	me->state = S_attr;	/* Get attribute */
    +	break;
    +
    +	/* accumulating value */
    +    case S_attr:
    +	if (WHITE(c) || (c == '>') || (c == '=')) {	/* End of word */
    +	    if ((c == '>')
    +		&& (string->size >= 1)
    +		&& (string->data[string->size - 1] == '/')) {
    +		if ((LYxhtml_parsing || me->extended_html)
    +		    && ignore_when_empty(me->current_tag)) {
    +		    discard_empty(me);
    +		}
    +	    } else {
    +		HTChunkTerminate(string);
    +		handle_attribute_name(me, string->data);
    +	    }
    +#ifdef USE_PRETTYSRC
    +	    if (!psrc_view) {
    +#endif
    +		string->size = 0;
    +		if (c == '>') {	/* End of tag */
    +		    if (me->current_tag->name)
    +			start_element(me);
    +		    me->state = S_text;
    +		    break;
    +		}
    +#ifdef USE_PRETTYSRC
    +	    } else {
    +		PUTC(' ');
    +		if (me->current_attribute_number == INVALID)
    +		    PSRCSTART(badattr);
    +		else
    +		    PSRCSTART(attrib);
    +		if (attrname_transform != 1) {
    +		    if (attrname_transform == 0)
    +			LYLowerCase(string->data);
    +		    else
    +			LYUpperCase(string->data);
    +		}
    +		PUTS(string->data);
    +		if (c == '=' || WHITE(c))
    +		    PUTC(c);
    +		if (c == '=' || c == '>') {
    +		    if (me->current_attribute_number == INVALID) {
    +			PSRCSTOP(badattr);
    +		    } else {
    +			PSRCSTOP(attrib);
    +		    }
    +		}
    +		if (c == '>') {
    +		    PSRCSTART(abracket);
    +		    PUTC('>');
    +		    PSRCSTOP(abracket);
    +		    me->state = S_text;
    +		    break;
    +		}
    +		string->size = 0;
    +	    }
    +#endif
    +	    me->state = (c == '=' ? S_equals : S_attr_gap);
    +	} else {
    +	    HTChunkPutc(string, c);
    +	}
    +	break;
    +
    +    case S_attr_gap:		/* Expecting attribute or '=' or '>' */
    +	if (WHITE(c)) {
    +	    PRETTYSRC_PUTC(c);
    +	    break;		/* Gap after attribute */
    +	}
    +	if (c == '>') {		/* End of tag */
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		if (me->current_attribute_number == INVALID) {
    +		    PSRCSTOP(badattr);
    +		} else {
    +		    PSRCSTOP(attrib);
    +		}
    +		PSRCSTART(abracket);
    +		PUTC('>');
    +		PSRCSTOP(abracket);
    +	    } else
    +#endif
    +	    if (me->current_tag->name)
    +		start_element(me);
    +	    me->state = S_text;
    +	    break;
    +	} else if (c == '=') {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PUTC('=');
    +		if (me->current_attribute_number == INVALID) {
    +		    PSRCSTOP(badattr);
    +		} else {
    +		    PSRCSTOP(attrib);
    +		}
    +	    }
    +#endif
    +	    me->state = S_equals;
    +	    break;
    +	}
    +	HTChunkPutc(string, c);
    +	me->state = S_attr;	/* Get next attribute */
    +	break;
    +
    +    case S_equals:		/* After attr = */
    +	if (WHITE(c)) {
    +	    PRETTYSRC_PUTC(c);
    +	    break;		/* Before attribute value */
    +	}
    +	if (c == '>') {		/* End of tag */
    +	    CTRACE((tfp, "SGML: found = but no value\n"));
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(abracket);
    +		PUTC('>');
    +		PSRCSTOP(abracket);
    +	    } else
    +#endif
    +	    if (me->current_tag->name)
    +		start_element(me);
    +	    me->state = S_text;
    +	    break;
    +
    +	} else if (c == '\'') {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(attrval);
    +		PUTC(c);
    +	    }
    +#endif
    +	    me->state = S_squoted;
    +	    break;
    +
    +	} else if (c == '"') {
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PSRCSTART(attrval);
    +		PUTC(c);
    +	    }
    +#endif
    +	    me->state = S_dquoted;
    +	    break;
    +	}
    +#ifdef USE_PRETTYSRC
    +	if (psrc_view)
    +	    PSRCSTART(attrval);
    +#endif
    +	me->state = S_value;
    +	/* FALLTHRU */
    +
    +    case S_value:
    +	if (WHITE(c) || (c == '>')) {	/* End of word */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (!end_if_prettysrc(me, string, 0))
    +#endif
    +	    {
    +#ifdef CJK_EX			/* Quick hack. - JH7AYN */
    +		if (IS_CJK_TTY) {
    +		    if (string->data[0] == '$') {
    +			if (string->data[1] == 'B' || string->data[1] == '@') {
    +			    char *jis_buf = 0;
    +
    +			    HTSprintf0(&jis_buf, "\033%s", string->data);
    +			    TO_EUC((const unsigned char *) jis_buf,
    +				   (unsigned char *) string->data);
    +			    FREE(jis_buf);
    +			}
    +		    }
    +		}
    +#endif
    +		handle_attribute_value(me, string->data);
    +	    }
    +	    string->size = 0;
    +	    if (c == '>') {	/* End of tag */
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    PSRCSTART(abracket);
    +		    PUTC('>');
    +		    PSRCSTOP(abracket);
    +		} else
    +#endif
    +		if (me->current_tag->name)
    +		    start_element(me);
    +		me->state = S_text;
    +		break;
    +	    } else
    +		me->state = S_tag_gap;
    +	} else if (me->T.decode_utf8 &&
    +		   *me->U.utf_buf) {
    +	    HTChunkPuts(string, me->U.utf_buf);
    +	    me->U.utf_buf_p = me->U.utf_buf;
    +	    *(me->U.utf_buf_p) = '\0';
    +	} else if (!IS_CJK_TTY &&
    +		   (me->T.output_utf8 ||
    +		    me->T.trans_from_uni)) {
    +	    if (clong == UCS_REPL && saved_char_in &&
    +		HTPassEightBitRaw &&
    +		saved_char_in >=
    +		LYlowest_eightbit[me->outUCLYhndl]) {
    +		(HTChunkPutUtf8Char) (string,
    +				      (UCode_t) (0xf000 | saved_char_in));
    +	    } else {
    +		HTChunkPutUtf8Char(string, clong);
    +	    }
    +	} else if (saved_char_in && me->T.use_raw_char_in) {
    +	    HTChunkPutc(string, saved_char_in);
    +	} else {
    +	    HTChunkPutc(string, c);
    +	}
    +	break;
    +
    +    case S_squoted:		/* Quoted attribute value */
    +	if (c == '\'') {	/* End of attribute value */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (!end_if_prettysrc(me, string, '\''))
    +#endif
    +		handle_attribute_value(me, string->data);
    +	    string->size = 0;
    +	    me->state = S_tag_gap;
    +	} else if (TOASCII(c) == '\033') {	/* S/390 -- gil -- 1213 */
    +	    /*
    +	     * Setting up for possible single quotes in CJK escape sequences. 
    +	     * - Takuya ASADA (asada@three-a.co.jp)
    +	     */
    +	    me->state = S_esc_sq;
    +	    if (!UTF8_TTY_ISO2022JP)
    +		HTChunkPutc(string, c);
    +	} else if (me->T.decode_utf8 &&
    +		   *me->U.utf_buf) {
    +	    HTChunkPuts(string, me->U.utf_buf);
    +	    me->U.utf_buf_p = me->U.utf_buf;
    +	    *(me->U.utf_buf_p) = '\0';
    +	} else if (!IS_CJK_TTY &&
    +		   (me->T.output_utf8 ||
    +		    me->T.trans_from_uni)) {
    +	    if (clong == UCS_REPL && saved_char_in &&
    +		HTPassEightBitRaw &&
    +		saved_char_in >=
    +		LYlowest_eightbit[me->outUCLYhndl]) {
    +		(HTChunkPutUtf8Char) (string,
    +				      (UCode_t) (0xf000 | saved_char_in));
    +	    } else {
    +		HTChunkPutUtf8Char(string, clong);
    +	    }
    +	} else if (saved_char_in && me->T.use_raw_char_in) {
    +	    HTChunkPutc(string, saved_char_in);
    +	} else {
    +	    HTChunkPutc(string, c);
    +	}
    +	break;
    +
    +    case S_dquoted:		/* Quoted attribute value */
    +	if (c == '"' ||		/* Valid end of attribute value */
    +	    (soft_dquotes &&	/*  If emulating old Netscape bug, treat '>' */
    +	     c == '>')) {	/*  as a co-terminator of dquoted and tag    */
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (!end_if_prettysrc(me, string, (char) c))
    +#endif
    +		handle_attribute_value(me, string->data);
    +	    string->size = 0;
    +	    me->state = S_tag_gap;
    +	    if (c == '>')	/* We emulated the Netscape bug, so we go  */
    +		goto top1;	/* back and treat it as the tag terminator */
    +	} else if (TOASCII(c) == '\033') {	/* S/390 -- gil -- 1230 */
    +	    /*
    +	     * Setting up for possible double quotes in CJK escape sequences. 
    +	     * - Takuya ASADA (asada@three-a.co.jp)
    +	     */
    +	    me->state = S_esc_dq;
    +	    if (!UTF8_TTY_ISO2022JP)
    +		HTChunkPutc(string, c);
    +	} else if (me->T.decode_utf8 &&
    +		   *me->U.utf_buf) {
    +	    HTChunkPuts(string, me->U.utf_buf);
    +	    me->U.utf_buf_p = me->U.utf_buf;
    +	    *(me->U.utf_buf_p) = '\0';
    +	} else if (!IS_CJK_TTY &&
    +		   (me->T.output_utf8 ||
    +		    me->T.trans_from_uni)) {
    +	    if (clong == UCS_REPL && saved_char_in &&
    +		HTPassEightBitRaw &&
    +		saved_char_in >=
    +		LYlowest_eightbit[me->outUCLYhndl]) {
    +		(HTChunkPutUtf8Char) (string,
    +				      (UCode_t) (0xf000 | saved_char_in));
    +	    } else {
    +		HTChunkPutUtf8Char(string, clong);
    +	    }
    +	} else if (saved_char_in && me->T.use_raw_char_in) {
    +	    HTChunkPutc(string, saved_char_in);
    +	} else {
    +	    HTChunkPutc(string, c);
    +	}
    +	break;
    +
    +    case S_end:		/* </ */
    +	if (TOASCII(clong) < 127 && (string->size ?	/* S/390 -- gil -- 1247 */
    +				     IsNmChar(c) : IsNmStart(c))) {
    +	    HTChunkPutc(string, c);
    +	} else {		/* End of end tag name */
    +	    HTTag *t = 0;
    +
    +#ifdef USE_PRETTYSRC
    +	    BOOL psrc_tagname_processed = FALSE;
    +#endif
    +
    +	    HTChunkTerminate(string);
    +	    if (!*string->data) {	/* Empty end tag */
    +		if (me->element_stack)
    +		    t = me->element_stack->tag;
    +	    } else {
    +		t = SGMLFindTag(dtd, string->data);
    +	    }
    +	    if (!t || t == me->unknown_tag) {
    +		CTRACE((tfp, "Unknown end tag </%s>\n", string->data));
    +#ifdef USE_PRETTYSRC
    +		if (psrc_view) {
    +		    PSRCSTART(abracket);
    +		    PUTS("</");
    +		    PSRCSTOP(abracket);
    +		    PSRCSTART(badtag);
    +		    transform_tag(me, string);
    +		    PUTS(string->data);
    +		    if (c != '>') {
    +			PUTC(c);
    +		    } else {
    +			PSRCSTOP(badtag);
    +			PSRCSTART(abracket);
    +			PUTC('>');
    +			PSRCSTOP(abracket);
    +		    }
    +		    psrc_tagname_processed = TRUE;
    +		}
    +	    } else if (psrc_view) {
    +#endif
    +	    } else {
    +		BOOL tag_OK = (BOOL) (c == '>' || WHITE(c));
    +		HTMLElement e = TAGNUM_OF_TAGP(t);
    +		int branch = 2;	/* it can be 0,1,2 */
    +
    +		me->current_tag = t;
    +		if (HAS_ALT_TAGNUM(TAGNUM_OF_TAGP(t)) &&
    +		    me->element_stack &&
    +		    ALT_TAGP(t) == me->element_stack->tag)
    +		    me->element_stack->tag = NORMAL_TAGP(me->element_stack->tag);
    +
    +		if (tag_OK && Old_DTD) {
    +		    switch (e) {
    +		    case HTML_DD:
    +		    case HTML_DT:
    +		    case HTML_LI:
    +		    case HTML_LH:
    +		    case HTML_TD:
    +		    case HTML_TH:
    +		    case HTML_TR:
    +		    case HTML_THEAD:
    +		    case HTML_TFOOT:
    +		    case HTML_TBODY:
    +		    case HTML_COLGROUP:
    +			branch = 0;
    +			break;
    +
    +		    case HTML_A:
    +		    case HTML_B:
    +		    case HTML_BLINK:
    +		    case HTML_CITE:
    +		    case HTML_EM:
    +		    case HTML_FONT:
    +		    case HTML_FORM:
    +		    case HTML_I:
    +		    case HTML_P:
    +		    case HTML_STRONG:
    +		    case HTML_TT:
    +		    case HTML_U:
    +			branch = 1;
    +			break;
    +		    default:
    +			break;
    +		    }
    +		}
    +
    +		/*
    +		 * Just handle ALL end tags normally :-) - kw
    +		 */
    +		if (!Old_DTD) {
    +		    end_element(me, me->current_tag);
    +		} else if (tag_OK && (branch == 0)) {
    +		    /*
    +		     * Don't treat these end tags as invalid, nor act on them. 
    +		     * - FM
    +		     */
    +		    CTRACE((tfp, "SGML: `</%s%c' found!  Ignoring it.\n",
    +			    string->data, c));
    +		    string->size = 0;
    +		    me->current_attribute_number = INVALID;
    +		    if (c != '>') {
    +			me->state = S_junk_tag;
    +		    } else {
    +			me->current_tag = NULL;
    +			me->state = S_text;
    +		    }
    +		    break;
    +		} else if (tag_OK && (branch == 1)) {
    +		    /*
    +		     * Handle end tags for container elements declared as
    +		     * SGML_EMPTY to prevent "expected tag substitution" but
    +		     * still processed via HTML_end_element() in HTML.c with
    +		     * checks there to avoid throwing the HTML.c stack out of
    +		     * whack (Ugh, what a hack!  8-).  - FM
    +		     */
    +		    if (me->inSELECT) {
    +			/*
    +			 * We are in a SELECT block.  - FM
    +			 */
    +			if (strcasecomp(string->data, "FORM")) {
    +			    /*
    +			     * It is not at FORM end tag, so ignore it.  - FM
    +			     */
    +			    CTRACE((tfp,
    +				    "SGML: ***Ignoring end tag </%s> in SELECT block.\n",
    +				    string->data));
    +			} else {
    +			    /*
    +			     * End the SELECT block and then handle the FORM
    +			     * end tag.  - FM
    +			     */
    +			    CTRACE((tfp,
    +				    "SGML: ***Faking SELECT end tag before </%s> end tag.\n",
    +				    string->data));
    +			    end_element(me,
    +					SGMLFindTag(me->dtd, "SELECT"));
    +			    CTRACE((tfp, "SGML: End </%s>\n", string->data));
    +
    +#ifdef USE_PRETTYSRC
    +			    if (!psrc_view)	/* Don't actually call if viewing psrc - kw */
    +#endif
    +				(*me->actions->end_element)
    +				    (me->target,
    +				     (int) TAGNUM_OF_TAGP(me->current_tag),
    +				     &me->include);
    +			}
    +		    } else if (!strcasecomp(string->data, "P")) {
    +			/*
    +			 * Treat a P end tag like a P start tag (Ugh, what a
    +			 * hack!  8-).  - FM
    +			 */
    +			CTRACE((tfp,
    +				"SGML: `</%s%c' found!  Treating as '<%s%c'.\n",
    +				string->data, c, string->data, c));
    +			{
    +			    int i;
    +
    +			    for (i = 0;
    +				 i < me->current_tag->number_of_attributes;
    +				 i++) {
    +				me->present[i] = NO;
    +			    }
    +			}
    +			if (me->current_tag->name)
    +			    start_element(me);
    +		    } else {
    +			CTRACE((tfp, "SGML: End </%s>\n", string->data));
    +
    +#ifdef USE_PRETTYSRC
    +			if (!psrc_view)		/* Don't actually call if viewing psrc - kw */
    +#endif
    +			    (*me->actions->end_element)
    +				(me->target,
    +				 (int) TAGNUM_OF_TAGP(me->current_tag),
    +				 &me->include);
    +		    }
    +		    string->size = 0;
    +		    me->current_attribute_number = INVALID;
    +		    if (c != '>') {
    +			me->state = S_junk_tag;
    +		    } else {
    +			me->current_tag = NULL;
    +			me->state = S_text;
    +		    }
    +		    break;
    +		} else {
    +		    /*
    +		     * Handle all other end tags normally.  - FM
    +		     */
    +		    end_element(me, me->current_tag);
    +		}
    +	    }
    +
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view && !psrc_tagname_processed) {
    +		PSRCSTART(abracket);
    +		PUTS("</");
    +		PSRCSTOP(abracket);
    +		PSRCSTART(tag);
    +		if (tagname_transform != 1) {
    +		    if (tagname_transform == 0)
    +			LYLowerCase(string->data);
    +		    else
    +			LYUpperCase(string->data);
    +		}
    +		PUTS(string->data);
    +		PSRCSTOP(tag);
    +		if (c != '>') {
    +		    PSRCSTART(badtag);
    +		    PUTC(c);
    +		} else {
    +		    PSRCSTART(abracket);
    +		    PUTC('>');
    +		    PSRCSTOP(abracket);
    +		}
    +	    }
    +#endif
    +
    +	    string->size = 0;
    +	    me->current_attribute_number = INVALID;
    +	    if (c != '>') {
    +		if (!WHITE(c))
    +		    CTRACE((tfp, "SGML: `</%s%c' found!\n", string->data, c));
    +		me->state = S_junk_tag;
    +	    } else {
    +		me->current_tag = NULL;
    +		me->state = S_text;
    +	    }
    +	}
    +	break;
    +
    +    case S_esc:		/* Expecting '$'or '(' following CJK ESC. */
    +	if (c == '$') {
    +	    me->state = S_dollar;
    +	} else if (c == '(') {
    +	    me->state = S_paren;
    +	} else {
    +	    me->state = S_text;
    +	    if (UTF8_TTY_ISO2022JP)
    +		goto top1;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    PUTC(c);
    +	break;
    +
    +    case S_dollar:		/* Expecting '@', 'B', 'A' or '(' after CJK "ESC$". */
    +	if (c == '@' || c == 'B' || c == 'A') {
    +	    me->state = S_nonascii_text;
    +	} else if (c == '(') {
    +	    me->state = S_dollar_paren;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    PUTC(c);
    +	break;
    +
    +    case S_dollar_paren:	/* Expecting 'C' after CJK "ESC$(". */
    +	if (c == 'C') {
    +	    me->state = S_nonascii_text;
    +	} else {
    +	    me->state = S_text;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		PUTS("$(");
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    PUTC(c);
    +	break;
    +
    +    case S_paren:		/* Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(". */
    +	if (c == 'B' || c == 'J' || c == 'T') {
    +	    me->state = S_text;
    +	} else if (c == 'I') {
    +	    me->state = S_nonascii_text;
    +	    if (UTF8_TTY_ISO2022JP)
    +		me->kanji_buf = '\t';	/* flag for single byte katakana */
    +	} else {
    +	    me->state = S_text;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		PUTC('(');
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    PUTC(c);
    +	break;
    +
    +    case S_nonascii_text:	/* Expecting CJK ESC after non-ASCII text. */
    +	if (TOASCII(c) == '\033') {	/* S/390 -- gil -- 1264 */
    +	    me->state = S_esc;
    +	} else if (c < 32) {
    +	    me->state = S_text;
    +	}
    +	if (UTF8_TTY_ISO2022JP) {
    +	    if (TOASCII(c) != '\033')
    +		PUTUTF8(clong);
    +	} else
    +	    PUTC(c);
    +	break;
    +
    +    case S_esc_sq:		/* Expecting '$'or '(' following CJK ESC. */
    +	if (c == '$') {
    +	    me->state = S_dollar_sq;
    +	} else if (c == '(') {
    +	    me->state = S_paren_sq;
    +	} else {
    +	    me->state = S_squoted;
    +	    if (UTF8_TTY_ISO2022JP)
    +		goto top1;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_dollar_sq:		/* Expecting '@', 'B', 'A' or '(' after CJK "ESC$". */
    +	if (c == '@' || c == 'B' || c == 'A') {
    +	    me->state = S_nonascii_text_sq;
    +	} else if (c == '(') {
    +	    me->state = S_dollar_paren_sq;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_dollar_paren_sq:	/* Expecting 'C' after CJK "ESC$(". */
    +	if (c == 'C') {
    +	    me->state = S_nonascii_text_sq;
    +	} else {
    +	    me->state = S_squoted;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		HTChunkPuts(string, "$(");
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_paren_sq:		/* Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(". */
    +	if (c == 'B' || c == 'J' || c == 'T') {
    +	    me->state = S_squoted;
    +	} else if (c == 'I') {
    +	    me->state = S_nonascii_text_sq;
    +	    if (UTF8_TTY_ISO2022JP)
    +		me->kanji_buf = '\t';	/* flag for single byte katakana */
    +	} else {
    +	    me->state = S_squoted;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		HTChunkPutc(string, '(');
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_nonascii_text_sq:	/* Expecting CJK ESC after non-ASCII text. */
    +	if (TOASCII(c) == '\033') {	/* S/390 -- gil -- 1281 */
    +	    me->state = S_esc_sq;
    +	}
    +	if (UTF8_TTY_ISO2022JP) {
    +	    if (TOASCII(c) != '\033')
    +		HTChunkPutUtf8Char(string, clong);
    +	} else
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_esc_dq:		/* Expecting '$'or '(' following CJK ESC. */
    +	if (c == '$') {
    +	    me->state = S_dollar_dq;
    +	} else if (c == '(') {
    +	    me->state = S_paren_dq;
    +	} else {
    +	    me->state = S_dquoted;
    +	    if (UTF8_TTY_ISO2022JP)
    +		goto top1;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_dollar_dq:		/* Expecting '@', 'B', 'A' or '(' after CJK "ESC$". */
    +	if (c == '@' || c == 'B' || c == 'A') {
    +	    me->state = S_nonascii_text_dq;
    +	} else if (c == '(') {
    +	    me->state = S_dollar_paren_dq;
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_dollar_paren_dq:	/* Expecting 'C' after CJK "ESC$(". */
    +	if (c == 'C') {
    +	    me->state = S_nonascii_text_dq;
    +	} else {
    +	    me->state = S_dquoted;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		HTChunkPuts(string, "$(");
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_paren_dq:		/* Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(". */
    +	if (c == 'B' || c == 'J' || c == 'T') {
    +	    me->state = S_dquoted;
    +	} else if (c == 'I') {
    +	    me->state = S_nonascii_text_dq;
    +	    if (UTF8_TTY_ISO2022JP)
    +		me->kanji_buf = '\t';	/* flag for single byte katakana */
    +	} else {
    +	    me->state = S_dquoted;
    +	    if (UTF8_TTY_ISO2022JP) {
    +		HTChunkPutc(string, '(');
    +		goto top1;
    +	    }
    +	}
    +	if (!UTF8_TTY_ISO2022JP)
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_nonascii_text_dq:	/* Expecting CJK ESC after non-ASCII text. */
    +	if (TOASCII(c) == '\033') {	/* S/390 -- gil -- 1298 */
    +	    me->state = S_esc_dq;
    +	}
    +	if (UTF8_TTY_ISO2022JP) {
    +	    if (TOASCII(c) != '\033')
    +		HTChunkPutUtf8Char(string, clong);
    +	} else
    +	    HTChunkPutc(string, c);
    +	break;
    +
    +    case S_junk_tag:
    +    case S_pi:
    +	if (c == '>') {
    +	    HTChunkTerminate(string);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		if (me->state == S_junk_tag) {
    +		    PSRCSTOP(badtag);
    +		}
    +		PSRCSTART(abracket);
    +		PUTC('>');
    +		PSRCSTOP(abracket);
    +	    }
    +#endif
    +	    if (me->state == S_pi)
    +		handle_processing_instruction(me);
    +	    string->size = 0;
    +	    me->current_tag = NULL;
    +	    me->state = S_text;
    +	} else {
    +	    HTChunkPutc(string, c);
    +#ifdef USE_PRETTYSRC
    +	    if (psrc_view) {
    +		PUTC(c);
    +	    }
    +#endif
    +	}
    +
    +    }				/* switch on me->state */
    +    CTRACE2(TRACE_SGML, (tfp, "SGML after  %s|%.*s|%c|\n",
    +			 state_name(me->state),
    +			 string->size,
    +			 NonNull(string->data),
    +			 UCH(c)));
    +
    +  after_switch:
    +    /*
    +     * Check whether an external function has added anything to the include
    +     * buffer.  If so, move the new stuff to the beginning of active_include. 
    +     * - kw
    +     */
    +    if (me->include != NULL) {
    +	if (me->include[0] == '\0') {
    +	    FREE(me->include);
    +	} else {
    +	    if (me->active_include &&
    +		me->active_include[me->include_index] != '\0')
    +		StrAllocCat(me->include,
    +			    me->active_include + me->include_index);
    +	    FREE(me->active_include);
    +	    me->active_include = me->include;
    +	    me->include_index = 0;
    +	    me->include = NULL;
    +	}
    +    }
    +
    +    /*
    +     * Check whether we've added anything to the recover buffer.  - FM
    +     */
    +    if (me->recover != NULL) {
    +	if (me->recover[me->recover_index] == '\0') {
    +	    FREE(me->recover);
    +	    me->recover_index = 0;
    +	} else {
    +	    c = UCH(me->recover[me->recover_index]);
    +	    me->recover_index++;
    +	    goto top;
    +	}
    +    }
    +
    +    /*
    +     * Check whether an external function had added anything to the include
    +     * buffer; it should now be in active_include.  - FM / kw
    +     */
    +    if (me->active_include != NULL) {
    +	if (me->active_include[me->include_index] == '\0') {
    +	    FREE(me->active_include);
    +	    me->include_index = 0;
    +	} else {
    +	    if (me->current_tag_charset == UTF8_handle ||
    +		me->T.trans_from_uni) {
    +		/*
    +		 * If it looks like we would have fed UTF-8 to the next
    +		 * processing stage, assume that whatever we were fed back is
    +		 * in UTF-8 form, too.  This won't be always true for all uses
    +		 * of the include buffer, but it's a start.  - kw
    +		 */
    +		const char *puni = me->active_include + me->include_index;
    +
    +		c = UCH(*puni);
    +		clong = UCGetUniFromUtf8String(&puni);
    +		if (clong < 256 && clong >= 0) {
    +		    c = UCH((clong & 0xff));
    +		}
    +		saved_char_in = '\0';
    +		me->include_index = (int) (puni
    +					   - me->active_include
    +					   + 1);
    +		goto top1;
    +	    } else {
    +		/*
    +		 * Otherwise assume no UTF-8 - do charset-naive processing and
    +		 * hope for the best.  - kw
    +		 */
    +		c = UCH(me->active_include[me->include_index]);
    +		me->include_index++;
    +		goto top;
    +	    }
    +	}
    +    }
    +
    +    /*
    +     * Check whether an external function has added anything to the csi buffer. 
    +     * - FM
    +     */
    +    if (me->csi != NULL) {
    +	if (me->csi[me->csi_index] == '\0') {
    +	    FREE(me->csi);
    +	    me->csi_index = 0;
    +	} else {
    +	    c = UCH(me->csi[me->csi_index]);
    +	    me->csi_index++;
    +	    goto top;
    +	}
    +    }
    +}				/* SGML_character */
    +
    +static void InferUtfFromBom(HTStream *me, int chndl)
    +{
    +    HTAnchor_setUCInfoStage(me->node_anchor, chndl,
    +			    UCT_STAGE_PARSER,
    +			    UCT_SETBY_PARSER);
    +    change_chartrans_handling(me);
    +}
    +
    +/*
    + * Avoid rewrite of SGML_character() to handle hypothetical case of UTF-16
    + * webpages, by pretending that the data is UTF-8.
    + */
    +static void SGML_widechar(HTStream *me, int ch)
    +{
    +    if (!UCPutUtf8_charstring(me, SGML_character, (UCode_t) ch)) {
    +	SGML_character(me, ch);
    +    }
    +}
    +
    +static void SGML_write(HTStream *me, const char *str, int l)
    +{
    +    const char *p;
    +    const char *e = str + l;
    +
    +    if (sgml_offset == 0) {
    +	if (l > 3
    +	    && !MemCmp(str, "\357\273\277", 3)) {
    +	    CTRACE((tfp, "SGML_write found UTF-8 BOM\n"));
    +	    InferUtfFromBom(me, UTF8_handle);
    +	    str += 3;
    +	} else if (l > 2) {
    +	    if (!MemCmp(str, "\377\376", 2)) {
    +		CTRACE((tfp, "SGML_write found UCS-2 LE BOM\n"));
    +		InferUtfFromBom(me, UTF8_handle);
    +		str += 2;
    +		me->T.ucs_mode = -1;
    +	    } else if (!MemCmp(str, "\376\377", 2)) {
    +		CTRACE((tfp, "SGML_write found UCS-2 BE BOM\n"));
    +		InferUtfFromBom(me, UTF8_handle);
    +		str += 2;
    +		me->T.ucs_mode = 1;
    +	    }
    +	}
    +    }
    +    switch (me->T.ucs_mode) {
    +    case -1:
    +	for (p = str; p < e; p += 2)
    +	    SGML_widechar(me, (UCH(p[1]) << 8) | UCH(p[0]));
    +	break;
    +    case 1:
    +	for (p = str; p < e; p += 2)
    +	    SGML_widechar(me, (UCH(p[0]) << 8) | UCH(p[1]));
    +	break;
    +    default:
    +	for (p = str; p < e; p++)
    +	    SGML_character(me, *p);
    +	break;
    +    }
    +}
    +
    +static void SGML_string(HTStream *me, const char *str)
    +{
    +    SGML_write(me, str, (int) strlen(str));
    +}
    +
    +/*_______________________________________________________________________
    +*/
    +
    +/*	Structured Object Class
    + *	-----------------------
    + */
    +const HTStreamClass SGMLParser =
    +{
    +    "SGMLParser",
    +    SGML_free,
    +    SGML_abort,
    +    SGML_character,
    +    SGML_string,
    +    SGML_write,
    +};
    +
    +/*	Create SGML Engine
    + *	------------------
    + *
    + * On entry,
    + *	dtd		represents the DTD, along with
    + *	actions		is the sink for the data as a set of routines.
    + *
    + */
    +
    +HTStream *SGML_new(const SGML_dtd * dtd,
    +		   HTParentAnchor *anchor,
    +		   HTStructured * target,
    +		   int extended_html)
    +{
    +    HTStream *me = typecalloc(struct _HTStream);
    +
    +    if (!me)
    +	outofmem(__FILE__, "SGML_begin");
    +
    +    me->isa = &SGMLParser;
    +    me->string = HTChunkCreate(128);	/* Grow by this much */
    +    me->dtd = dtd;
    +    me->target = target;
    +    me->actions = (const HTStructuredClass *) (((HTStream *) target)->isa);
    +    /* Ugh: no OO */
    +    me->unknown_tag = &HTTag_unrecognized;
    +    me->current_tag = me->slashedtag = NULL;
    +    me->state = S_text;
    +#ifdef CALLERDATA
    +    me->callerData = (void *) callerData;
    +#endif /* CALLERDATA */
    +
    +    me->node_anchor = anchor;	/* Could be NULL? */
    +    me->U.utf_buf_p = me->U.utf_buf;
    +    UCTransParams_clear(&me->T);
    +    me->inUCLYhndl = HTAnchor_getUCLYhndl(anchor,
    +					  UCT_STAGE_PARSER);
    +    if (me->inUCLYhndl < 0) {
    +	HTAnchor_copyUCInfoStage(anchor,
    +				 UCT_STAGE_PARSER,
    +				 UCT_STAGE_MIME,
    +				 -1);
    +	me->inUCLYhndl = HTAnchor_getUCLYhndl(anchor,
    +					      UCT_STAGE_PARSER);
    +    }
    +#ifdef CAN_SWITCH_DISPLAY_CHARSET	/* Allow a switch to a more suitable display charset */
    +    else if (anchor->UCStages
    +	     && anchor->UCStages->s[UCT_STAGE_PARSER].LYhndl >= 0
    +	     && anchor->UCStages->s[UCT_STAGE_PARSER].LYhndl != current_char_set) {
    +	int o = anchor->UCStages->s[UCT_STAGE_PARSER].LYhndl;
    +
    +	anchor->UCStages->s[UCT_STAGE_PARSER].LYhndl = -1;	/* Force reset */
    +	HTAnchor_resetUCInfoStage(anchor, o, UCT_STAGE_PARSER,
    +	/* Preserve change this: */
    +				  anchor->UCStages->s[UCT_STAGE_PARSER].lock);
    +    }
    +#endif
    +
    +    me->inUCI = HTAnchor_getUCInfoStage(anchor,
    +					UCT_STAGE_PARSER);
    +    set_chartrans_handling(me, anchor, -1);
    +
    +    me->recover = NULL;
    +    me->recover_index = 0;
    +    me->include = NULL;
    +    me->active_include = NULL;
    +    me->include_index = 0;
    +    me->url = NULL;
    +    me->csi = NULL;
    +    me->csi_index = 0;
    +
    +#ifdef USE_PRETTYSRC
    +    if (psrc_view) {
    +	psrc_view = FALSE;
    +	mark_htext_as_source = TRUE;
    +	SGML_string(me,
    +		    "<HTML><HEAD><TITLE>source
    ");
    +	psrc_view = TRUE;
    +	psrc_convert_string = FALSE;
    +	sgml_in_psrc_was_initialized = TRUE;
    +    }
    +#endif
    +    if (extended_html)
    +    {
    +        me->extended_html = TRUE;
    +    }
    +
    +    sgml_offset = 0;
    +    return me;
    +}
    +
    +/*
    + * Return the offset within the document where we're parsing.  This is used
    + * to help identify anchors which shift around while reparsing.
    + */
    +int SGML_offset(void)
    +{
    +    int result = sgml_offset;
    +
    +#ifdef USE_PRETTYSRC
    +    result += psrc_view;
    +#endif
    +    return result;
    +}
    +
    +/*		Asian character conversion functions
    + *		====================================
    + *
    + *	Added 24-Mar-96 by FM, based on:
    + *
    + ////////////////////////////////////////////////////////////////////////
    +Copyright (c) 1993 Electrotechnical Laboratory (ETL)
    +
    +Permission to use, copy, modify, and distribute this material
    +for any purpose and without fee is hereby granted, provided
    +that the above copyright notice and this permission notice
    +appear in all copies, and that the name of ETL not be
    +used in advertising or publicity pertaining to this
    +material without the specific, prior written permission
    +of an authorized representative of ETL.
    +ETL MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
    +OF THIS MATERIAL FOR ANY PURPOSE.  IT IS PROVIDED "AS IS",
    +WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
    +/////////////////////////////////////////////////////////////////////////
    +Content-Type:	program/C; charset=US-ASCII
    +Program:	SJIS.c
    +Author:		Yutaka Sato 
    +Description:
    +History:
    +	930923	extracted from codeconv.c of cosmos
    +///////////////////////////////////////////////////////////////////////
    +*/
    +
    +static int TREAT_SJIS = 1;
    +
    +void JISx0201TO0208_EUC(unsigned IHI,
    +			unsigned ILO,
    +			unsigned char *OHI,
    +			unsigned char *OLO)
    +{
    +    static const char *table[] =
    +    {
    +	"\241\243",		/* A1,A3 */
    +	"\241\326",		/* A1,D6 */
    +	"\241\327",		/* A1,D7 */
    +	"\241\242",		/* A1,A2 */
    +	"\241\246",		/* A1,A6 */
    +	"\245\362",		/* A5,F2 */
    +	"\245\241",		/* A5,A1 */
    +	"\245\243",		/* A5,A3 */
    +	"\245\245",		/* A5,A5 */
    +	"\245\247",		/* A5,A7 */
    +	"\245\251",		/* A5,A9 */
    +	"\245\343",		/* A5,E3 */
    +	"\245\345",		/* A5,E5 */
    +	"\245\347",		/* A5,E7 */
    +	"\245\303",		/* A5,C3 */
    +	"\241\274",		/* A1,BC */
    +	"\245\242",		/* A5,A2 */
    +	"\245\244",		/* A5,A4 */
    +	"\245\246",		/* A5,A6 */
    +	"\245\250",		/* A5,A8 */
    +	"\245\252",		/* A5,AA */
    +	"\245\253",		/* A5,AB */
    +	"\245\255",		/* A5,AD */
    +	"\245\257",		/* A5,AF */
    +	"\245\261",		/* A5,B1 */
    +	"\245\263",		/* A5,B3 */
    +	"\245\265",		/* A5,B5 */
    +	"\245\267",		/* A5,B7 */
    +	"\245\271",		/* A5,B9 */
    +	"\245\273",		/* A5,BB */
    +	"\245\275",		/* A5,BD */
    +	"\245\277",		/* A5,BF */
    +	"\245\301",		/* A5,C1 */
    +	"\245\304",		/* A5,C4 */
    +	"\245\306",		/* A5,C6 */
    +	"\245\310",		/* A5,C8 */
    +	"\245\312",		/* A5,CA */
    +	"\245\313",		/* A5,CB */
    +	"\245\314",		/* A5,CC */
    +	"\245\315",		/* A5,CD */
    +	"\245\316",		/* A5,CE */
    +	"\245\317",		/* A5,CF */
    +	"\245\322",		/* A5,D2 */
    +	"\245\325",		/* A5,D5 */
    +	"\245\330",		/* A5,D8 */
    +	"\245\333",		/* A5,DB */
    +	"\245\336",		/* A5,DE */
    +	"\245\337",		/* A5,DF */
    +	"\245\340",		/* A5,E0 */
    +	"\245\341",		/* A5,E1 */
    +	"\245\342",		/* A5,E2 */
    +	"\245\344",		/* A5,E4 */
    +	"\245\346",		/* A5,E6 */
    +	"\245\350",		/* A5,E8 */
    +	"\245\351",		/* A5,E9 */
    +	"\245\352",		/* A5,EA */
    +	"\245\353",		/* A5,EB */
    +	"\245\354",		/* A5,EC */
    +	"\245\355",		/* A5,ED */
    +	"\245\357",		/* A5,EF */
    +	"\245\363",		/* A5,F3 */
    +	"\241\253",		/* A1,AB */
    +	"\241\254"		/* A1,AC */
    +    };
    +
    +    if ((IHI == 0x8E) && (ILO >= 0xA1) && (ILO <= 0xDF)) {
    +	*OHI = UCH(table[ILO - 0xA1][0]);
    +	*OLO = UCH(table[ILO - 0xA1][1]);
    +    } else {
    +	*OHI = UCH(IHI);
    +	*OLO = UCH(ILO);
    +    }
    +}
    +
    +static int IS_SJIS_STR(const unsigned char *str)
    +{
    +    const unsigned char *s;
    +    unsigned char ch;
    +    int is_sjis = 0;
    +
    +    s = str;
    +    while ((ch = *s++) != '\0') {
    +	if (ch & 0x80)
    +	    if (IS_SJIS(ch, *s, is_sjis))
    +		return 1;
    +    }
    +    return 0;
    +}
    +
    +unsigned char *SJIS_TO_JIS1(unsigned HI,
    +			    unsigned LO,
    +			    unsigned char *JCODE)
    +{
    +    HI = UCH(HI - (unsigned) UCH((HI <= 0x9F) ? 0x71 : 0xB1));
    +    HI = UCH((HI << 1) + 1);
    +    if (0x7F < LO)
    +	LO--;
    +    if (0x9E <= LO) {
    +	LO = UCH(LO - UCH(0x7D));
    +	HI++;
    +    } else {
    +	LO = UCH(LO - UCH(0x1F));
    +    }
    +    JCODE[0] = UCH(HI);
    +    JCODE[1] = UCH(LO);
    +    return JCODE;
    +}
    +
    +unsigned char *JIS_TO_SJIS1(unsigned HI,
    +			    unsigned LO,
    +			    unsigned char *SJCODE)
    +{
    +    if (HI & 1)
    +	LO = UCH(LO + UCH(0x1F));
    +    else
    +	LO = UCH(LO + UCH(0x7D));
    +    if (0x7F <= LO)
    +	LO++;
    +
    +    HI = UCH(((HI - 0x21) >> 1) + 0x81);
    +    if (0x9F < HI)
    +	HI = UCH(HI + UCH(0x40));
    +    SJCODE[0] = UCH(HI);
    +    SJCODE[1] = UCH(LO);
    +    return SJCODE;
    +}
    +
    +unsigned char *EUC_TO_SJIS1(unsigned HI,
    +			    unsigned LO,
    +			    unsigned char *SJCODE)
    +{
    +    unsigned char HI_data[2];
    +    unsigned char LO_data[2];
    +
    +    HI_data[0] = UCH(HI);
    +    LO_data[0] = UCH(LO);
    +    if (HI == 0x8E) {
    +	JISx0201TO0208_EUC(HI, LO, HI_data, LO_data);
    +    }
    +    JIS_TO_SJIS1(UCH(HI_data[0] & 0x7F), UCH(LO_data[0] & 0x7F), SJCODE);
    +    return SJCODE;
    +}
    +
    +void JISx0201TO0208_SJIS(unsigned I,
    +			 unsigned char *OHI,
    +			 unsigned char *OLO)
    +{
    +    unsigned char SJCODE[2];
    +
    +    JISx0201TO0208_EUC(0x8E, I, OHI, OLO);
    +    JIS_TO_SJIS1(UCH(*OHI & 0x7F), UCH(*OLO & 0x7F), SJCODE);
    +    *OHI = SJCODE[0];
    +    *OLO = SJCODE[1];
    +}
    +
    +unsigned char *SJIS_TO_EUC1(unsigned HI,
    +			    unsigned LO,
    +			    unsigned char *data)
    +{
    +    SJIS_TO_JIS1(HI, LO, data);
    +    data[0] |= 0x80;
    +    data[1] |= 0x80;
    +    return data;
    +}
    +
    +unsigned char *SJIS_TO_EUC(unsigned char *src,
    +			   unsigned char *dst)
    +{
    +    unsigned char hi, lo, *sp, *dp;
    +    int in_sjis = 0;
    +
    +    in_sjis = IS_SJIS_STR(src);
    +    for (sp = src, dp = dst; (hi = sp[0]) != '\0';) {
    +	lo = sp[1];
    +	if (TREAT_SJIS && IS_SJIS(hi, lo, in_sjis)) {
    +	    SJIS_TO_JIS1(hi, lo, dp);
    +	    dp[0] |= 0x80;
    +	    dp[1] |= 0x80;
    +	    dp += 2;
    +	    sp += 2;
    +	} else
    +	    *dp++ = *sp++;
    +    }
    +    *dp = 0;
    +    return dst;
    +}
    +
    +unsigned char *EUC_TO_SJIS(unsigned char *src,
    +			   unsigned char *dst)
    +{
    +    unsigned char *sp, *dp;
    +
    +    for (sp = src, dp = dst; *sp;) {
    +	if (*sp & 0x80) {
    +	    if (sp[1] && (sp[1] & 0x80)) {
    +		JIS_TO_SJIS1(UCH(sp[0] & 0x7F), UCH(sp[1] & 0x7F), dp);
    +		dp += 2;
    +		sp += 2;
    +	    } else {
    +		sp++;
    +	    }
    +	} else {
    +	    *dp++ = *sp++;
    +	}
    +    }
    +    *dp = 0;
    +    return dst;
    +}
    +
    +#define Strcpy(a,b)	(strcpy((char*)a,(const char*)b),&a[strlen((const char*)a)])
    +
    +unsigned char *EUC_TO_JIS(unsigned char *src,
    +			  unsigned char *dst,
    +			  const char *toK,
    +			  const char *toA)
    +{
    +    unsigned char kana_mode = 0;
    +    unsigned char cch;
    +    unsigned char *sp = src;
    +    unsigned char *dp = dst;
    +    int is_JIS = 0;
    +
    +    while ((cch = *sp++) != '\0') {
    +	if (cch & 0x80) {
    +	    if (!IS_EUC(cch, *sp)) {
    +		if (cch == 0xA0 && is_JIS)	/* ignore NBSP */
    +		    continue;
    +		is_JIS++;
    +		*dp++ = cch;
    +		continue;
    +	    }
    +	    if (!kana_mode) {
    +		kana_mode = UCH(~kana_mode);
    +		dp = Strcpy(dp, toK);
    +	    }
    +	    if (*sp & 0x80) {
    +		*dp++ = UCH(cch & ~0x80);
    +		*dp++ = UCH(*sp++ & ~0x80);
    +	    }
    +	} else {
    +	    if (kana_mode) {
    +		kana_mode = UCH(~kana_mode);
    +		dp = Strcpy(dp, toA);
    +	    }
    +	    *dp++ = cch;
    +	}
    +    }
    +    if (kana_mode)
    +	dp = Strcpy(dp, toA);
    +
    +    if (dp)
    +	*dp = 0;
    +    return dst;
    +}
    +
    +#define	IS_JIS7(c1,c2)	(0x20<(c1)&&(c1)<0x7F && 0x20<(c2)&&(c2)<0x7F)
    +#define SO		('N'-0x40)
    +#define SI		('O'-0x40)
    +
    +static int repair_JIS = 0;
    +
    +static const unsigned char *repairJIStoEUC(const unsigned char *src,
    +					   unsigned char **dstp)
    +{
    +    const unsigned char *s;
    +    unsigned char *d, ch1, ch2;
    +
    +    d = *dstp;
    +    s = src;
    +    while ((ch1 = s[0]) && (ch2 = s[1])) {
    +	s += 2;
    +	if (ch1 == '(')
    +	    if (ch2 == 'B' || ch2 == 'J') {
    +		*dstp = d;
    +		return s;
    +	    }
    +	if (!IS_JIS7(ch1, ch2))
    +	    return 0;
    +
    +	*d++ = UCH(0x80 | ch1);
    +	*d++ = UCH(0x80 | ch2);
    +    }
    +    return 0;
    +}
    +
    +unsigned char *TO_EUC(const unsigned char *jis,
    +		      unsigned char *euc)
    +{
    +    const unsigned char *s;
    +    unsigned char c, jis_stat;
    +    unsigned char *d;
    +    int to1B, to2B;
    +    int in_sjis = 0;
    +    static int nje;
    +    int n8bits;
    +    int is_JIS;
    +
    +    nje++;
    +    n8bits = 0;
    +    s = jis;
    +    d = euc;
    +    jis_stat = 0;
    +    to2B = TO_2BCODE;
    +    to1B = TO_1BCODE;
    +    in_sjis = IS_SJIS_STR(jis);
    +    is_JIS = 0;
    +
    +    while ((c = *s++) != '\0') {
    +	if (c == 0x80)
    +	    continue;		/* ignore it */
    +	if (c == 0xA0 && is_JIS)
    +	    continue;		/* ignore Non-breaking space */
    +
    +	if (c == to2B && jis_stat == 0 && repair_JIS) {
    +	    if (*s == 'B' || *s == '@') {
    +		const unsigned char *ts;
    +
    +		if ((ts = repairJIStoEUC(s + 1, &d)) != NULL) {
    +		    s = ts;
    +		    continue;
    +		}
    +	    }
    +	}
    +	if (c == CH_ESC) {
    +	    if (*s == to2B) {
    +		if ((s[1] == 'B') || (s[1] == '@')) {
    +		    jis_stat = 0x80;
    +		    s += 2;
    +		    is_JIS++;
    +		    continue;
    +		}
    +		jis_stat = 0;
    +	    } else if (*s == to1B) {
    +		jis_stat = 0;
    +		if ((s[1] == 'B') || (s[1] == 'J') || (s[1] == 'H')) {
    +		    s += 2;
    +		    continue;
    +		}
    +	    } else if (*s == ',') {	/* MULE */
    +		jis_stat = 0;
    +	    }
    +	}
    +	if (c & 0x80)
    +	    n8bits++;
    +
    +	if (IS_SJIS(c, *s, in_sjis)) {
    +	    SJIS_TO_EUC1(c, *s, d);
    +	    d += 2;
    +	    s++;
    +	    is_JIS++;
    +	} else if (jis_stat) {
    +	    if (c <= 0x20 || 0x7F <= c) {
    +		*d++ = c;
    +		if (c == '\n')
    +		    jis_stat = 0;
    +	    } else {
    +		if (IS_JIS7(c, *s)) {
    +		    *d++ = jis_stat | c;
    +		    *d++ = jis_stat | *s++;
    +		} else
    +		    *d++ = c;
    +	    }
    +	} else {
    +	    if (n8bits == 0 && (c == SI || c == SO)) {
    +	    } else {
    +		*d++ = c;
    +	    }
    +	}
    +    }
    +    *d = 0;
    +    return euc;
    +}
    +
    +#define non94(ch) ((ch) <= 0x20 || (ch) == 0x7F)
    +
    +static int is_EUC_JP(unsigned char *euc)
    +{
    +    unsigned char *cp;
    +    int ch1, ch2;
    +
    +    for (cp = euc; (ch1 = *cp) != '\0'; cp++) {
    +	if (ch1 & 0x80) {
    +	    ch2 = cp[1] & 0xFF;
    +	    if ((ch2 & 0x80) == 0) {
    +		/* sv1log("NOT_EUC1[%x][%x]\n",ch1,ch2); */
    +		return 0;
    +	    }
    +	    if (non94(ch1 & 0x7F) || non94(ch2 & 0x7F)) {
    +		/* sv1log("NOT_EUC2[%x][%x]\n",ch1,ch2); */
    +		return 0;
    +	    }
    +	    cp++;
    +	}
    +    }
    +    return 1;
    +}
    +
    +void TO_SJIS(const unsigned char *arg,
    +	     unsigned char *sjis)
    +{
    +    unsigned char *euc;
    +
    +    euc = typeMallocn(unsigned char, strlen((const char *) arg) + 1);
    +
    +#ifdef CJK_EX
    +    if (!euc)
    +	outofmem(__FILE__, "TO_SJIS");
    +#endif
    +    TO_EUC(arg, euc);
    +    if (is_EUC_JP(euc))
    +	EUC_TO_SJIS(euc, sjis);
    +    else
    +	strcpy((char *) sjis, (const char *) arg);
    +    free(euc);
    +}
    +
    +void TO_JIS(const unsigned char *arg,
    +	    unsigned char *jis)
    +{
    +    unsigned char *euc;
    +
    +    if (arg[0] == 0) {
    +	jis[0] = 0;
    +	return;
    +    }
    +    euc = typeMallocn(unsigned char, strlen((const char *)arg) + 1);
    +#ifdef CJK_EX
    +    if (!euc)
    +	outofmem(__FILE__, "TO_JIS");
    +#endif
    +    TO_EUC(arg, euc);
    +    EUC_TO_JIS(euc, jis, TO_KANJI, TO_ASCII);
    +
    +    free(euc);
    +}
    diff --git a/WWW/Library/Implementation/SGML.h b/WWW/Library/Implementation/SGML.h
    new file mode 100644
    index 0000000..4525fda
    --- /dev/null
    +++ b/WWW/Library/Implementation/SGML.h
    @@ -0,0 +1,287 @@
    +/*
    + * $LynxId: SGML.h,v 1.50 2023/01/05 22:23:44 tom Exp $
    + *			       SGML parse and stream definition for libwww
    + *                             SGML AND STRUCTURED STREAMS
    + *
    + * The SGML parser is a state machine.	It is called for every character
    + * of the input stream.	 The DTD data structure contains pointers
    + * to functions which are called to implement the actual effect of the
    + * text read. When these functions are called, the attribute structures pointed to by the
    + * DTD are valid, and the function is passed a pointer to the current tag structure, and an
    + * "element stack" which represents the state of nesting within SGML elements.
    + *
    + * The following aspects are from Dan Connolly's suggestions:  Binary search,
    + * Structured object scheme basically, SGML content enum type.
    + *
    + * (c) Copyright CERN 1991 - See Copyright.html
    + *
    + */
    +#ifndef SGML_H
    +#define SGML_H
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +/*
    + *
    + * SGML content types
    + *
    + */ typedef enum {
    +	SGML_EMPTY,		/* No content. */
    +	SGML_LITTERAL,		/* Literal character data.  Recognize exact close tag only.
    +				   Old www server compatibility only!  Not SGML */
    +	SGML_CDATA,		/* Character data.  Recognize 
    +#endif
    +
    +#ifndef UCDEFS_H
    +#include 
    +#endif /* UCDEFS_H */
    +
    +#ifndef HTSTREAM_H
    +#include 
    +#endif /* HTSTREAM_H */
    +
    +#ifndef UCMAP_H
    +#include 
    +#endif /* UCMAP_H */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +    extern BOOL UCCanUniTranslateFrom(int from);
    +    extern BOOL UCCanTranslateUniTo(int to);
    +    extern BOOL UCCanTranslateFromTo(int from, int to);
    +    extern BOOL UCNeedNotTranslate(int from,
    +				   int to);
    +
    +    struct _UCTransParams {
    +	BOOL transp;
    +	BOOL do_cjk;
    +	BOOL decode_utf8;
    +	BOOL output_utf8;
    +	BOOL use_raw_char_in;
    +	BOOL strip_raw_char_in;
    +	BOOL pass_160_173_raw;
    +	BOOL do_8bitraw;
    +	BOOL trans_to_uni;
    +	BOOL trans_C0_to_uni;
    +	BOOL repl_translated_C0;
    +	BOOL trans_from_uni;
    +	int ucs_mode;
    +    };
    +    typedef struct _UCTransParams UCTransParams;
    +
    +    typedef struct {
    +	char utf_count;
    +	UCode_t utf_char;
    +	char utf_buf[8];
    +	char *utf_buf_p;
    +    } UTFDecodeState;
    +
    +    typedef enum {
    +	dUTF8_ok,
    +	dUTF8_err,
    +	dUTF8_more
    +    } dUTF8;
    +
    +    extern dUTF8 HTDecodeUTF8(UTFDecodeState * me, int *c_in_out, UCode_t *result);
    +
    +    extern void UCSetTransParams(UCTransParams * pT, int cs_in,
    +				 const LYUCcharset *p_in,
    +				 int cs_out,
    +				 const LYUCcharset *p_out);
    +
    +    extern void UCTransParams_clear(UCTransParams * pT);
    +
    +    extern void UCSetBoxChars(int cset,
    +			      int *pvert_out,
    +			      int *phori_out,
    +			      int vert_in,
    +			      int hori_in);
    +
    +    typedef void putc_func_t (HTStream *me,
    +			      int ch);
    +
    +    extern BOOL UCPutUtf8_charstring(HTStream *target,
    +				     putc_func_t *actions,
    +				     UCode_t code);
    +
    +    extern BOOL UCConvertUniToUtf8(UCode_t code,
    +				   char *buffer);
    +
    +    extern UCode_t UCGetUniFromUtf8String(const char **ppuni);
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* UCAUX_H */
    diff --git a/WWW/Library/Implementation/UCDefs.h b/WWW/Library/Implementation/UCDefs.h
    new file mode 100644
    index 0000000..4eb7c56
    --- /dev/null
    +++ b/WWW/Library/Implementation/UCDefs.h
    @@ -0,0 +1,106 @@
    +/*
    + * $LynxId: UCDefs.h,v 1.18 2021/06/29 00:21:51 tom Exp $
    + *
    + * Definitions for Unicode character-translations
    + */
    +
    +#ifndef UCDEFS_H
    +#define UCDEFS_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +typedef struct _LYUCcharset {
    +    int UChndl;			/* -1 for "old" charsets, >= 0 for chartrans tables */
    +
    +    const char *MIMEname;
    +    int enc;
    +    int codepage;		/* if positive, an IBM OS/2 specific number;
    +				   if negative, flag for no table translation */
    +
    +    /* parameters below are not used by chartrans mechanism, */
    +    /* they describe some relationships against built-in Latin1 charset... */
    +    int repertoire;		/* unused */
    +    int codepoints;		/* subset/superset of Latin1 ? */
    +    int cpranges;		/* unused, obsolete by LYlowest_eightbit;
    +				   "which ranges have valid displayable chars
    +				   (including nbsp and shy)" */
    +    int like8859;		/* currently used for nbsp and shy only
    +				   (but UCT_R_8859SPECL assumed for any UCT_R_8BIT...);
    +				   "for which ranges is it like 8859-1" */
    +} LYUCcharset;
    +
    +typedef enum {
    +    UCT_ENC_7BIT,
    +    UCT_ENC_8BIT,
    +    UCT_ENC_8859,		/* no displayable chars in 0x80-0x9F */
    +    UCT_ENC_8BIT_C0,		/* 8-bit + some chars in C0 control area */
    +    UCT_ENC_MAYBE2022,
    +    UCT_ENC_CJK,
    +    UCT_ENC_16BIT,
    +    UCT_ENC_UTF8
    +} eUCT_ENC;
    +
    +#define UCT_REP_SUBSETOF_LAT1 0x01
    +#define UCT_REP_SUPERSETOF_LAT1 0x02
    +#define UCT_REP_IS_LAT1 UCT_REP_SUBSETOF_LAT1 | UCT_REP_SUPERSETOF_LAT1
    +/*
    + *  Assume everything we deal with is included in the UCS2 repertoire,
    + *  so a flag for _REP_SUBSETOF_UCS2 would be redundant.
    + */
    +
    +/*
    + *  More general description how the code points relate to 8859-1 and UCS:
    + */
    +#define UCT_CP_SUBSETOF_LAT1 0x01	/* implies UCT_CP_SUBSETOF_UCS2 */
    +#define UCT_CP_SUPERSETOF_LAT1 0x02
    +#define UCT_CP_SUBSETOF_UCS2 0x04
    +
    +#define UCT_CP_IS_LAT1 UCT_CP_SUBSETOF_LAT1 | UCT_CP_SUPERSETOF_LAT1
    +
    +/*
    + *  More specific bitflags for practically important code point ranges:
    + */
    +#define UCT_R_LOWCTRL 0x08	/* 0x00-0x1F, for completeness */
    +#define UCT_R_7BITINV 0x10	/* invariant???, displayable 7bit chars */
    +#define UCT_R_7BITNAT 0x20	/* displayable 7bit, national??? */
    +#define UCT_R_HIGHCTRL 0x40	/* chars in 0x80-0x9F range */
    +#define UCT_R_8859SPECL 0x80	/* special chars in 8859-x sets: nbsp and shy */
    +#define UCT_R_HIGH8BIT 0x100	/* rest of 0xA0-0xFF range */
    +
    +#define UCT_R_ASCII UCT_R_7BITINV | UCT_R_7BITNAT	/* displayable US-ASCII */
    +#define UCT_R_LAT1  UCT_R_ASCII   | UCT_R_8859SPECL | UCT_R_HIGH8BIT
    +#define UCT_R_8BIT  UCT_R_LAT1    | UCT_R_HIGHCTRL	/* full 8bit range */
    +
    +/*
    + *  For the following some comments are in HTAnchor.c.
    + */
    +typedef enum {
    +    UCT_STAGE_MIME,
    +    UCT_STAGE_PARSER,		/* What the parser (SGML.c) gets to see */
    +    UCT_STAGE_STRUCTURED,	/* What the structured stream (HTML) gets fed */
    +    UCT_STAGE_HTEXT,		/* What gets fed to the HText_* functions */
    +    UCT_STAGEMAX
    +} eUCT_STAGE;
    +
    +typedef enum {
    +    UCT_SETBY_NONE,
    +    UCT_SETBY_DEFAULT,
    +    UCT_SETBY_LINK,		/* set by A or LINK CHARSET= hint */
    +    UCT_SETBY_STRUCTURED,	/* structured stream stage (HTML.c) */
    +    UCT_SETBY_PARSER,		/* set by SGML parser or similar */
    +    UCT_SETBY_MIME		/* set explicitly by MIME charset parameter */
    +} eUCT_SETBY;
    +
    +typedef struct _UCStageInfo {
    +    int lock;			/* by what it has been set */
    +    int LYhndl;
    +    LYUCcharset C;
    +} UCStageInfo;
    +
    +typedef struct _UCAnchorInfo {
    +    struct _UCStageInfo s[UCT_STAGEMAX];
    +} UCAnchorInfo;
    +
    +#endif /* UCDEFS_H */
    diff --git a/WWW/Library/Implementation/UCMap.h b/WWW/Library/Implementation/UCMap.h
    new file mode 100644
    index 0000000..0c88969
    --- /dev/null
    +++ b/WWW/Library/Implementation/UCMap.h
    @@ -0,0 +1,114 @@
    +/*
    + * $LynxId: UCMap.h,v 1.30 2023/01/05 09:17:15 tom Exp $
    + */
    +#ifndef UCMAP_H
    +#define UCMAP_H
    +
    +#ifndef HTUTILS_H
    +#include 
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +#define UCS_HIDE 0xffff
    +#define UCS_REPL 0xfffd
    +
    +    typedef enum {
    +	ucError = -1,
    +	ucZeroWidth = -2,
    +	ucInvalidHash = -3,
    +	ucNotFound = -4,
    +	ucNeedMore = -10,
    +	ucCannotConvert = -11,
    +	ucCannotOutput = -12,
    +	ucBufferTooSmall = -13,
    +	ucUnknown = -14
    +    } UCStatus;
    +
    +    typedef long UCode_t;
    +
    +    extern BOOL UCScanCode(UCode_t *, const char *, BOOL);
    +
    +    extern int UCTransUniChar(UCode_t unicode,
    +			      int charset_out);
    +    extern int UCTransUniCharStr(char *outbuf,
    +				 int buflen,
    +				 UCode_t unicode,
    +				 int charset_out,
    +				 int chk_single_flag);
    +    extern int UCTransChar(int ch_in,
    +			   int charset_in,
    +			   int charset_out);
    +    extern int UCReverseTransChar(int ch_out,
    +				  int charset_in,
    +				  int charset_out);
    +    extern int UCTransCharStr(char *outbuf,
    +			      int buflen,
    +			      int ch_in,
    +			      int charset_in,
    +			      int charset_out,
    +			      int chk_single_flag);
    +#ifdef EXP_CHINESEUTF8_SUPPORT
    +    extern UCode_t UCTransJPToUni(char *inbuf,
    +				  int buflen,
    +				  int charset_in);
    +#endif
    +    extern UCode_t UCTransToUni(int ch_in,
    +				int charset_in);
    +    extern int UCGetRawUniMode_byLYhndl(int i);
    +    extern int UCGetLYhndl_byMIME(const char *p);	/* returns -1 if name not recognized */
    +    extern int safeUCGetLYhndl_byMIME(const char *p);	/* returns LATIN1 if name not recognized */
    +
    +#ifdef USE_LOCALE_CHARSET
    +    extern void LYFindLocaleCharset(void);
    +#endif
    +
    +    extern int UCLYhndl_for_unspec;
    +    extern int UCLYhndl_for_unrec;
    +    extern int UCLYhndl_HTFile_for_unspec;
    +    extern int UCLYhndl_HTFile_for_unrec;
    +
    +/* easy to type: */
    +    extern int LATIN1;		/* UCGetLYhndl_byMIME("iso-8859-1") */
    +    extern int US_ASCII;	/* UCGetLYhndl_byMIME("us-ascii")   */
    +    extern int UTF8_handle;	/* UCGetLYhndl_byMIME("utf-8")      */
    +
    +#undef TRANSPARENT		/* defined on Solaris in  */
    +    extern int TRANSPARENT;	/* UCGetLYhndl_byMIME("x-transparent")  */
    +
    +/*
    +In general, Lynx translates letters from document charset to display charset.
    +If document charset is not specified or not recognized by Lynx, we fall back
    +to different assumptions below, read also lynx.cfg for info.
    +
    +UCLYhndl_for_unspec -  assume this as charset for documents that don't
    +                       specify a charset parameter in HTTP headers or via META
    +                       this corresponds to "assume_charset"
    +
    +UCLYhndl_HTFile_for_unspec -  assume this as charset of local file
    +                       this corresponds to "assume_local_charset"
    +
    +UCLYhndl_for_unrec  -  in case a charset parameter is not recognized;
    +                       this corresponds to "assume_unrec_charset"
    +
    +UCLYhndl_HTFile_for_unrec  - the same but only for local files,
    +                             currently not used.
    +
    +current_char_set  -	this corresponds to "display charset",
    +			declared in LYCharSets.c and really important.
    +
    +All external charset information is available in so called MIME format.
    +For internal needs Lynx uses charset handlers as integers
    +from UCGetLYhndl_byMIME().  However, there is no way to recover
    +from user's error in configuration file lynx.cfg or command line switches,
    +those unrecognized MIME names are assumed as LATIN1 (via safeUCGetLYhndl...).
    +*/
    +
    +#define UCTRANS_NOTFOUND (-4)
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* UCMAP_H */
    diff --git a/WWW/Library/Implementation/Version.make b/WWW/Library/Implementation/Version.make
    new file mode 100644
    index 0000000..4b4b380
    --- /dev/null
    +++ b/WWW/Library/Implementation/Version.make
    @@ -0,0 +1 @@
    +VC = 2.14
    diff --git a/WWW/Library/Implementation/dtd_util.c b/WWW/Library/Implementation/dtd_util.c
    new file mode 100644
    index 0000000..cf88998
    --- /dev/null
    +++ b/WWW/Library/Implementation/dtd_util.c
    @@ -0,0 +1,1722 @@
    +/*
    + * $LynxId: dtd_util.c,v 1.89 2022/09/29 23:51:16 tom Exp $
    + *
    + * Given a SGML_dtd structure, write a corresponding flat file, or "C" source.
    + * Given the flat-file, write the "C" source.
    + *
    + * TODO: use symbols for HTMLA_NORMAL, etc.
    + */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +/*
    + * Tweaks to build standalone.
    + */
    +#undef exit
    +
    +BOOLEAN WWW_TraceFlag = FALSE;
    +FILE *TraceFP(void)
    +{
    +    return stderr;
    +}
    +
    +/*
    + * Begin the actual utility.
    + */
    +#define GETOPT "chl:o:tsx"
    +
    +#define NOTE(message) fprintf(output, message "\n");
    +/* *INDENT-OFF* */
    +#ifdef USE_PRETTYSRC
    +# define N HTMLA_NORMAL
    +# define i HTMLA_ANAME
    +# define h HTMLA_HREF
    +# define c HTMLA_CLASS
    +# define x HTMLA_AUXCLASS
    +# define T(t) , t
    +#else
    +# define T(t)			/*nothing */
    +#endif
    +
    +#define ATTR_TYPE(name) { #name, name##_attr_list }
    +
    +#define MY_LIMIT		1024
    +#define FMT_WS			"%[ \t\n]"
    +#define FMT_NUM_ATTR_TYPES	"%d attr_types"
    +#define FMT_NUM_ATTRS		"%d attributes:"
    +#define FMT_ONE_ATTR		"%d:%d:%s"
    +#define NUM_ONE_ATTR		4
    +
    +static const char alias_codes[] = "!@#$%^&*";
    +
    +#define DATA(name) { #name, Tgc_##name }
    +static const struct {
    +    const char *name;
    +    TagClass tagclass;
    +} class_list[] = {
    +	DATA(APPLETlike),
    +	DATA(Alike),
    +	DATA(BODYlike),
    +	DATA(BRlike),
    +	DATA(DELlike),
    +	DATA(DIVlike),
    +	DATA(EMlike),
    +	DATA(FONTlike),
    +	DATA(FORMlike),
    +	DATA(HEADstuff),
    +	DATA(HRlike),
    +	DATA(LIlike),
    +	DATA(MAPlike),
    +	DATA(MATHlike),
    +	DATA(Plike),
    +	DATA(SELECTlike),
    +	DATA(TRlike),
    +	DATA(ULlike),
    +	DATA(formula),
    +	DATA(outer),
    +	DATA(same)
    +};
    +
    +static const attr core_attr_list[] = {
    +	{ "CLASS"         T(c) },
    +	{ "ID"            T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr i18n_attr_list[] = {
    +	{ "DIR"           T(N) },
    +	{ "LANG"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr events_attr_list[] = {
    +	{ "ONCLICK"       T(N) },
    +	{ "ONDBLCLICK"    T(N) },
    +	{ "ONKEYDOWN"     T(N) },
    +	{ "ONKEYPRESS"    T(N) },
    +	{ "ONKEYUP"       T(N) },
    +	{ "ONMOUSEDOWN"   T(N) },
    +	{ "ONMOUSEMOVE"   T(N) },
    +	{ "ONMOUSEOUT"    T(N) },
    +	{ "ONMOUSEOVER"   T(N) },
    +	{ "ONMOUSEUP"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr align_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr cellalign_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr bgcolor_attr_list[] = {
    +	{ "BGCOLOR"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +#undef T
    +/* *INDENT-ON* */
    +
    +static const char *input_filename;
    +static int input_lineno;
    +
    +static void failed(const char *s)
    +{
    +    if (input_lineno) {
    +	int save = errno;
    +
    +	fprintf(stderr, "%s:%d ", input_filename, input_lineno);
    +	errno = save;
    +    }
    +    perror(s);
    +    exit(EXIT_FAILURE);
    +}
    +
    +static void warning(const char *, ...) GCC_PRINTFLIKE(1, 2);
    +
    +static void warning(const char *fmt, ...)
    +{
    +    va_list ap;
    +
    +    va_start(ap, fmt);
    +    fprintf(stderr, "%s:%d: ", input_filename, input_lineno);
    +    vfprintf(stderr, fmt, ap);
    +    fputc('\n', stderr);
    +    va_end(ap);
    +}
    +
    +static void usage(void)
    +{
    +    static const char *tbl[] =
    +    {
    +	"Usage: dtd_util [options]",
    +	"",
    +	"Options:",
    +	"  -c           generate C-source"
    +	"  -h           generate C-header"
    +	"  -l           load",
    +	"  -o filename  specify output (default: stdout)",
    +	"  -s           strict (HTML DTD 0)",
    +	"  -t           tagsoup (HTML DTD 1)",
    +	"  -x           cross-check contains/contained data (repeat for more)"
    +    };
    +    unsigned n;
    +
    +    for (n = 0; n < TABLESIZE(tbl); ++n) {
    +	fprintf(stderr, "%s\n", tbl[n]);
    +    }
    +    exit(EXIT_FAILURE);
    +}
    +
    +static const char *SGMLContent2s(SGMLContent contents)
    +{
    +    char *value = "?";
    +
    +    switch (contents) {
    +    case SGML_EMPTY:
    +	value = "SGML_EMPTY";
    +	break;
    +    case SGML_LITTERAL:
    +	value = "SGML_LITTERAL";
    +	break;
    +    case SGML_CDATA:
    +	value = "SGML_CDATA";
    +	break;
    +    case SGML_SCRIPT:
    +	value = "SGML_SCRIPT";
    +	break;
    +    case SGML_RCDATA:
    +	value = "SGML_RCDATA";
    +	break;
    +    case SGML_MIXED:
    +	value = "SGML_MIXED";
    +	break;
    +    case SGML_ELEMENT:
    +	value = "SGML_ELEMENT";
    +	break;
    +    case SGML_PCDATA:
    +	value = "SGML_PCDATA";
    +	break;
    +    }
    +    return value;
    +}
    +
    +static SGMLContent s2SGMLContent(const char *value)
    +{
    +    static SGMLContent table[] =
    +    {
    +	SGML_EMPTY,
    +	SGML_LITTERAL,
    +	SGML_CDATA,
    +	SGML_SCRIPT,
    +	SGML_RCDATA,
    +	SGML_MIXED,
    +	SGML_ELEMENT,
    +	SGML_PCDATA
    +    };
    +    unsigned n;
    +    SGMLContent result = SGML_EMPTY;
    +
    +    for (n = 0; n < TABLESIZE(table); ++n) {
    +	if (!strcmp(SGMLContent2s(table[n]), value)) {
    +	    result = table[n];
    +	    break;
    +	}
    +    }
    +    return result;
    +}
    +
    +static void PrintF(FILE *, int, const char *, ...) GCC_PRINTFLIKE(3, 4);
    +
    +static void PrintF(FILE *output, int width, const char *fmt, ...)
    +{
    +    char buffer[BUFSIZ];
    +    va_list ap;
    +
    +    va_start(ap, fmt);
    +    vsprintf(buffer, fmt, ap);
    +    va_end(ap);
    +
    +    fprintf(output, "%-*s", width, buffer);
    +}
    +
    +static char *get_line(FILE *input)
    +{
    +    char temp[MY_LIMIT];
    +    char *result = 0;
    +
    +    if (fgets(temp, (int) sizeof(temp), input) != 0) {
    +	result = strdup(temp);
    +	++input_lineno;
    +    }
    +    return result;
    +}
    +
    +static int read_num_attr_types(FILE *input)
    +{
    +    char *next = get_line(input);
    +    int count;
    +    int code;
    +    char trail[MY_LIMIT];
    +
    +    if (next == 0
    +	|| (code = sscanf(next, FMT_NUM_ATTR_TYPES FMT_WS, &count, trail)) < 2
    +	|| !count) {
    +	warning("expected attr_types");
    +    }
    +    return count;
    +}
    +
    +static int same_AttrList(AttrList a, AttrList b)
    +{
    +    int result = 1;
    +
    +    if (a && b) {
    +	while (a->name && b->name) {
    +	    if (strcmp(a->name, b->name)) {
    +		result = 0;
    +		break;
    +	    }
    +	    ++a, ++b;
    +	}
    +	if (a->name || b->name)
    +	    result = 0;
    +    } else {
    +	result = 0;
    +    }
    +    return result;
    +}
    +
    +static int first_attrs(const SGML_dtd * dtd, int which)
    +{
    +    int check;
    +    int result = TRUE;
    +
    +    for (check = 0; check < which; ++check) {
    +	if (dtd->tags[check].attributes == dtd->tags[which].attributes) {
    +	    result = FALSE;
    +	    break;
    +	} else if (same_AttrList(dtd->tags[check].attributes,
    +				 dtd->tags[which].attributes)) {
    +	    result = FALSE;
    +	    dtd->tags[which].attributes = dtd->tags[check].attributes;
    +	    break;
    +	}
    +    }
    +    return result;
    +}
    +
    +static char *no_dashes(char *target, const char *source)
    +{
    +    int j;
    +
    +    for (j = 0; (target[j] = source[j]) != '\0'; ++j) {
    +	if (!isalnum(target[j]))
    +	    target[j] = '_';
    +    }
    +    return target;
    +}
    +
    +/* the second "OBJECT" is treated specially */
    +static int first_object(const SGML_dtd * dtd, int which)
    +{
    +    int check;
    +
    +    for (check = 0; check <= which; ++check) {
    +	if (!strcmp(dtd->tags[check].name, "OBJECT"))
    +	    break;
    +    }
    +    return (check == which);
    +}
    +
    +static const char *NameOfAttrs(const SGML_dtd * dtd, int which)
    +{
    +    int check;
    +    const char *result = dtd->tags[which].name;
    +
    +    for (check = 0; check < which; ++check) {
    +	if (dtd->tags[check].attributes == dtd->tags[which].attributes) {
    +	    result = dtd->tags[check].name;
    +	    break;
    +	}
    +    }
    +    /* special cases to match existing headers */
    +    if (!strcmp(result, "ABBR"))
    +	result = "GEN";
    +    else if (!strcmp(result, "ARTICLE"))
    +	result = "GEN5";
    +    else if (!strcmp(result, "BLOCKQUOTE"))
    +	result = "BQ";
    +    else if (!strcmp(result, "BASEFONT"))
    +	result = "FONT";
    +    else if (!strcmp(result, "CENTER"))
    +	result = "DIV";
    +    else if (!strcmp(result, "DIR"))
    +	result = "UL";
    +    else if (!strcmp(result, "H1"))
    +	result = "H";
    +    else if (!strcmp(result, "TBODY"))
    +	result = "TR";
    +    return result;
    +}
    +
    +static const char *DEF_name(const SGML_dtd * dtd, int which, unsigned alias)
    +{
    +    const char *result = dtd->tags[which].name;
    +
    +    if (!strcmp(result, "OBJECT") && !first_object(dtd, which)) {
    +	result = "OBJECT_PCDATA";
    +    } else if (alias) {
    +	char buffer[MY_LIMIT];
    +
    +	sprintf(buffer, "%s_%d", result, alias + 1);
    +	result = strdup(buffer);
    +    }
    +    return result;
    +}
    +
    +static const char *EXT_name(HTTag * tag)
    +{
    +    const char *result = tag->name;
    +
    +    if (tag->alias) {
    +	char buffer[MY_LIMIT];
    +
    +	sprintf(buffer, "%s%c", result, alias_codes[tag->alias - 1]);
    +	result = strdup(buffer);
    +    }
    +    return result;
    +}
    +
    +typedef struct {
    +    const char *name;
    +    const attr *attrs;
    +    int count;
    +    int which;
    +} AttrInfo;
    +
    +static int compare_attr_types(const void *a, const void *b)
    +{
    +    const AttrType *p = (const AttrType *) a;
    +    const AttrType *q = (const AttrType *) b;
    +    int result = 0;
    +
    +    /* keep lowercase AttrType lists before uppercase, since latter are derived */
    +    if (isupper(p->name[0]) ^ isupper(q->name[0])) {
    +	if (isupper(p->name[0])) {
    +	    result = 1;
    +	} else {
    +	    result = -1;
    +	}
    +    } else {
    +	result = strcmp(p->name, q->name);
    +    }
    +    return result;
    +}
    +
    +static int len_AttrTypes(const AttrType * data)
    +{
    +    int result = 0;
    +
    +    for (result = 0; data[result].name != 0; ++result) {
    +	;
    +    }
    +    return result;
    +}
    +
    +static AttrType *sorted_AttrTypes(const AttrType * source)
    +{
    +    AttrType *result = 0;
    +    unsigned number = len_AttrTypes(source);
    +
    +    if (number != 0) {
    +	result = typecallocn(AttrType, number + 1);
    +	if (result != 0) {
    +	    MemCpy(result, source, number * sizeof(*result));
    +	    qsort(result, number, sizeof(*result), compare_attr_types);
    +	}
    +    }
    +
    +    return result;
    +}
    +
    +static int compare_attr(const void *a, const void *b)
    +{
    +    const AttrInfo *p = (const AttrInfo *) a;
    +    const AttrInfo *q = (const AttrInfo *) b;
    +
    +    return strcmp(p->name, q->name);
    +}
    +
    +static int len_AttrList(AttrList data)
    +{
    +    int result = 0;
    +
    +    for (result = 0; data[result].name != 0; ++result) {
    +	;
    +    }
    +    return result;
    +}
    +
    +static void sort_uniq_AttrList(attr * data)
    +{
    +    unsigned have = len_AttrList(data);
    +    unsigned j, k;
    +
    +    qsort(data, have, sizeof(*data), compare_attr);
    +    /*
    +     * Eliminate duplicates
    +     */
    +    for (j = 0; j < have; ++j) {
    +	for (k = j; data[k].name; ++k) {
    +	    if (data[k + 1].name == 0)
    +		break;
    +	    if (strcmp(data[j].name, data[k + 1].name)) {
    +		break;
    +	    }
    +	}
    +	data[j] = data[k];
    +    }
    +    memset(data + j, 0, sizeof(data[0]));
    +}
    +
    +static attr *copy_AttrList(AttrList data)
    +{
    +    unsigned need = len_AttrList(data);
    +    unsigned n;
    +
    +    attr *result = (attr *) calloc(need + 1, sizeof(attr));
    +
    +    for (n = 0; n < need; ++n)
    +	result[n] = data[n];
    +    sort_uniq_AttrList(result);
    +    return result;
    +}
    +
    +static attr *merge_AttrLists(const AttrType * data)
    +{
    +    const AttrType *at;
    +    attr *result = 0;
    +    unsigned need = 1;
    +    unsigned have = 0;
    +    unsigned j;
    +
    +    for (at = data; at->name; ++at) {
    +	need += len_AttrList(at->list);
    +    }
    +    result = (attr *) calloc(need + 1, sizeof(attr));
    +    for (at = data; at->name; ++at) {
    +	if (!strcmp(at->name, "events")) {
    +	    ;			/* lynx does not use events */
    +	} else {
    +	    for (j = 0; at->list[j].name; ++j) {
    +		result[have++] = at->list[j];
    +	    }
    +	}
    +    }
    +    sort_uniq_AttrList(result);
    +    return result;
    +}
    +
    +static int clean_AttrList(attr * target, AttrList source)
    +{
    +    int result = 0;
    +    int j, k;
    +
    +    for (j = 0; target[j].name != 0; ++j) {
    +	for (k = 0; source[k].name != 0; ++k) {
    +	    if (!strcmp(target[j].name, source[k].name)) {
    +		k = j--;
    +		for (;;) {
    +		    target[k] = target[k + 1];
    +		    if (target[k++].name == 0)
    +			break;
    +		}
    +		++result;
    +		break;
    +	    }
    +	}
    +    }
    +    return result;
    +}
    +
    +/*
    + * Actually COUNT the number of attributes, to make it possible to edit a
    + * attribute-table in src0_HTMLDTD.h and have all of the files updated by
    + * just doing a "make sources".
    + */
    +static int AttrCount(HTTag * tag)
    +{
    +    return len_AttrList(tag->attributes);
    +}
    +
    +static AttrInfo *sorted_attrs(const SGML_dtd * dtd, unsigned *countp)
    +{
    +    int j;
    +
    +    AttrInfo *data = (AttrInfo *) calloc(dtd->number_of_tags, sizeof(AttrInfo));
    +    unsigned count = 0;
    +
    +    /* get the attribute-data */
    +    for (j = 0; j < dtd->number_of_tags; ++j) {
    +	if (first_attrs(dtd, j)) {
    +	    data[count].name = NameOfAttrs(dtd, j);
    +	    data[count].attrs = dtd->tags[j].attributes;
    +	    data[count].count = AttrCount(&(dtd->tags[j]));
    +	    data[count].which = j;
    +	    ++count;
    +	}
    +    }
    +    /* sort the data by the name of their associated tag */
    +    qsort(data, count, sizeof(*data), compare_attr);
    +    *countp = count;
    +    return data;
    +}
    +
    +static void dump_src_HTTag_Defines(FILE *output, const SGML_dtd * dtd, int which)
    +{
    +    HTTag *tag = &(dtd->tags[which]);
    +
    +#define myFMT "0x%05X"
    +    fprintf(output,
    +	    "#define T_%-13s "
    +	    myFMT "," myFMT "," myFMT "," myFMT "," myFMT "," myFMT
    +	    "," myFMT "\n",
    +	    DEF_name(dtd, which, tag->alias),
    +	    tag->tagclass,
    +	    tag->contains,
    +	    tag->icontains,
    +	    tag->contained,
    +	    tag->icontained,
    +	    tag->canclose,
    +	    tag->flags);
    +}
    +
    +static void dump_AttrItem(FILE *output, const attr * data)
    +{
    +    char buffer[BUFSIZ];
    +    char pretty = 'N';
    +
    +    sprintf(buffer, "\"%s\"", data->name);
    +#ifdef USE_PRETTYSRC
    +    switch (data->type) {
    +    case HTMLA_NORMAL:
    +	pretty = 'N';
    +	break;
    +    case HTMLA_ANAME:
    +	pretty = 'i';
    +	break;
    +    case HTMLA_HREF:
    +	pretty = 'h';
    +	break;
    +    case HTMLA_CLASS:
    +	pretty = 'c';
    +	break;
    +    case HTMLA_AUXCLASS:
    +	pretty = 'x';
    +	break;
    +    }
    +#endif
    +    fprintf(output, "\t{ %-15s T(%c) },\n", buffer, pretty);
    +}
    +
    +static void dump_AttrItem0(FILE *output)
    +{
    +    fprintf(output, "\t{ 0               T(N) }\t/* Terminate list */\n");
    +}
    +
    +static void dump_src_AttrType(FILE *output, const char *name, AttrList data, const char **from)
    +{
    +    int n;
    +
    +    fprintf(output, "static const attr %s_attr_list[] = {\n", name);
    +    if (data != 0) {
    +	for (n = 0; data[n].name != 0; ++n) {
    +	    dump_AttrItem(output, data + n);
    +	}
    +    }
    +    fprintf(output, "\t{ 0               T(N) }	/* Terminate list */\n");
    +    fprintf(output, "};\n");
    +    NOTE("");
    +    fprintf(output, "static const AttrType %s_attr_type[] = {\n", name);
    +    if (from != 0) {
    +	while (*from != 0) {
    +	    fprintf(output, "\t{ ATTR_TYPE(%s) },\n", *from);
    +	    ++from;
    +	}
    +    } else {
    +	fprintf(output, "\t{ ATTR_TYPE(%s) },\n", name);
    +    }
    +    fprintf(output, "\t{ 0, 0 },\n");
    +    fprintf(output, "};\n");
    +    NOTE("");
    +}
    +
    +static void dump_src_HTTag_Attrs(FILE *output, const SGML_dtd * dtd, int which)
    +{
    +    HTTag *tag = &(dtd->tags[which]);
    +    attr *list = merge_AttrLists(tag->attr_types);
    +    char buffer[BUFSIZ];
    +    int n;
    +    int limit = len_AttrList(list);
    +
    +    sprintf(buffer, "static const attr %s_attr[] = {", NameOfAttrs(dtd, which));
    +    fprintf(output,
    +	    "%-40s/* %s attributes */\n", buffer, tag->name);
    +    for (n = 0; n < limit; ++n) {
    +	dump_AttrItem(output, list + n);
    +    }
    +    dump_AttrItem0(output);
    +    fprintf(output, "};\n");
    +    NOTE("");
    +    free(list);
    +}
    +
    +static void dump_src_HTTag(FILE *output, const SGML_dtd * dtd, int which)
    +{
    +    HTTag *tag = &(dtd->tags[which]);
    +    char *P_macro = "P";
    +
    +#ifdef USE_JUSTIFY_ELTS
    +    if (!tag->can_justify)
    +	P_macro = "P0";
    +#endif
    +    PrintF(output, 19, " { %s(%s),", P_macro, tag->name);
    +    PrintF(output, 24, "ATTR_DATA(%s), ", NameOfAttrs(dtd, which));
    +    PrintF(output, 14, "%s,", SGMLContent2s(tag->contents));
    +    fprintf(output, "T_%s", DEF_name(dtd, which, tag->alias));
    +    fprintf(output, ", %d", tag->alias);
    +    fprintf(output, ", %d", tag->aliases);
    +    fprintf(output, "},\n");
    +}
    +
    +static void dump_source(FILE *output, const SGML_dtd * dtd, int dtd_version)
    +{
    +    static AttrType generic_types[] =
    +    {
    +	ATTR_TYPE(core),
    +	ATTR_TYPE(i18n),
    +	ATTR_TYPE(events),
    +	ATTR_TYPE(align),
    +	ATTR_TYPE(cellalign),
    +	ATTR_TYPE(bgcolor),
    +	{0, 0}
    +    };
    +    AttrType *gt;
    +    TagAlias aliases;
    +
    +    const char *marker = "src_HTMLDTD_H";
    +    int j;
    +    int inalias;
    +
    +    unsigned count = 0;
    +    AttrInfo *data = sorted_attrs(dtd, &count);
    +
    +    fprintf(output, "/* %cLynxId%c */\n", '$', '$');
    +    fprintf(output, "#ifndef %s%d\n", marker, dtd_version);
    +    fprintf(output, "#define %s%d 1\n\n", marker, dtd_version);
    +
    +    /*
    +     * If we ifdef this for once, and make the table names distinct, we can
    +     * #include the strict- and tagsoup-output directly in HTMLDTD.c
    +     */
    +    NOTE("#ifndef once_HTMLDTD");
    +    NOTE("#define once_HTMLDTD 1");
    +    NOTE("");
    +
    +    /* construct TagClass-define's */
    +    for (j = 0; j <= dtd->number_of_tags; ++j) {
    +	dump_src_HTTag_Defines(output, dtd, j);
    +    }
    +    NOTE("#define T__UNREC_	0x00000,0x00000,0x00000,0x00000,0x00000,0x00000,0x00000");
    +
    +    /* construct attribute-tables */
    +    NOTE("#ifdef USE_PRETTYSRC");
    +    NOTE("# define N HTMLA_NORMAL");
    +    NOTE("# define i HTMLA_ANAME");
    +    NOTE("# define h HTMLA_HREF");
    +    NOTE("# define c HTMLA_CLASS");
    +    NOTE("# define x HTMLA_AUXCLASS");
    +    NOTE("# define T(t) , t");
    +    NOTE("#else");
    +    NOTE("# define T(t)			/*nothing */");
    +    NOTE("#endif");
    +    NOTE("/* *INDENT-OFF* */");
    +    NOTE("");
    +    NOTE("#define ATTR_TYPE(name) #name, name##_attr_list");
    +    NOTE("");
    +    NOTE("/* generic attributes, used in different tags */");
    +    for (gt = generic_types; gt->name != 0; ++gt) {
    +	dump_src_AttrType(output, gt->name, gt->list, 0);
    +    }
    +    NOTE("");
    +    NOTE("/* tables defining attributes per-tag in terms of generic attributes (editable) */");
    +    for (j = 0; j < (int) count; ++j) {
    +	int which = data[j].which;
    +
    +	if (first_attrs(dtd, which)) {
    +	    HTTag *tag = &(dtd->tags[which]);
    +	    const AttrType *types = tag->attr_types;
    +	    const char *name = NameOfAttrs(dtd, which);
    +	    attr *list = 0;
    +	    const char *from_attr[10];
    +	    int from_size = 0;
    +
    +	    while (types->name != 0) {
    +		from_attr[from_size++] = types->name;
    +		if (!strcmp(types->name, name)) {
    +		    list = copy_AttrList(types->list);
    +		    for (gt = generic_types; gt->name != 0; ++gt) {
    +			if (clean_AttrList(list, gt->list)) {
    +			    int k;
    +			    int found = 0;
    +
    +			    for (k = 0; k < from_size; ++k) {
    +				if (!strcmp(from_attr[k], gt->name)) {
    +				    found = 1;
    +				    break;
    +				}
    +			    }
    +			    if (!found)
    +				from_attr[from_size++] = gt->name;
    +			    break;
    +			}
    +		    }
    +		}
    +		++types;
    +	    }
    +	    from_attr[from_size] = 0;
    +
    +	    if (list != 0) {
    +		/* FIXME check if each remaining attribute is defined in XXX */
    +
    +		dump_src_AttrType(output, name, list, from_attr);
    +		free(list);
    +	    }
    +	}
    +    }
    +    NOTE("");
    +    NOTE("/* attribute lists for the runtime (generated by dtd_util) */");
    +    for (j = 0; j < (int) count; ++j) {
    +	dump_src_HTTag_Attrs(output, dtd, data[j].which);
    +    }
    +    NOTE("/* *INDENT-ON* */");
    +    NOTE("");
    +    NOTE("/* justification-flags */");
    +    NOTE("#undef N");
    +    NOTE("#undef i");
    +    NOTE("#undef h");
    +    NOTE("#undef c");
    +    NOTE("#undef x");
    +    NOTE("");
    +    NOTE("#undef T");
    +    NOTE("");
    +    NOTE("/* tag-names */");
    +    /* these may be needed as parameters to macros */
    +    for (j = 0; j <= dtd->number_of_tags; ++j) {
    +	fprintf(output, "#undef %s\n", DEF_name(dtd, j, 0));
    +    }
    +    NOTE("");
    +    NOTE("/* these definitions are used in the tags-tables */");
    +    NOTE("#undef P");
    +    NOTE("#undef P_");
    +    NOTE("#ifdef USE_COLOR_STYLE");
    +    NOTE("#define P_(x) #x, (sizeof #x) -1");
    +    NOTE("#define NULL_HTTag_ NULL, 0");
    +    NOTE("#else");
    +    NOTE("#define P_(x) #x");
    +    NOTE("#define NULL_HTTag_ NULL");
    +    NOTE("#endif");
    +    NOTE("");
    +    NOTE("#ifdef USE_JUSTIFY_ELTS");
    +    NOTE("#define P(x) P_(x), 1");
    +    NOTE("#define P0(x) P_(x), 0");
    +    NOTE("#define NULL_HTTag NULL_HTTag_,0");
    +    NOTE("#else");
    +    NOTE("#define P(x) P_(x)");
    +    NOTE("#define P0(x) P_(x)");
    +    NOTE("#define NULL_HTTag NULL_HTTag_");
    +    NOTE("#endif");
    +    NOTE("");
    +    NOTE("#define ATTR_DATA(name) name##_attr, HTML_##name##_ATTRIBUTES, name##_attr_type");
    +    NOTE("");
    +    NOTE("#endif /* once_HTMLDTD */");
    +    NOTE("/* *INDENT-OFF* */");
    +
    +    /* construct the tags table */
    +    fprintf(output,
    +	    "static const HTTag tags_table%d[HTML_ALL_ELEMENTS] = {\n",
    +	    dtd_version);
    +    aliases = 0;
    +    inalias = -1;
    +    for (j = dtd->number_of_tags - 1; j >= 0; --j) {
    +	dtd->tags[j].aliases = aliases;
    +	if (aliases != 0) {
    +	    if ((inalias - aliases) >= j) {
    +		aliases = 0;
    +		inalias = -1;
    +	    }
    +	} else if ((aliases = dtd->tags[j].alias) != 0) {
    +	    inalias = j;
    +	    dtd->tags[j].aliases = aliases;
    +	}
    +    }
    +    for (j = 0; j <= dtd->number_of_tags; ++j) {
    +	if (j == dtd->number_of_tags) {
    +	    NOTE("/* additional (alternative variants), not counted in HTML_ELEMENTS: */");
    +	    NOTE("/* This one will be used as a temporary substitute within the parser when");
    +	    NOTE("   it has been signalled to parse OBJECT content as MIXED. - kw */");
    +	}
    +	dump_src_HTTag(output, dtd, j);
    +    }
    +    fprintf(output, "};\n");
    +
    +    NOTE("/* *INDENT-ON* */");
    +    NOTE("");
    +    fprintf(output, "#endif /* %s%d */\n", marker, dtd_version);
    +
    +    free(data);
    +}
    +
    +static void dump_hdr_attr(FILE *output, AttrInfo * data)
    +{
    +    int j;
    +    char buffer[BUFSIZ];
    +
    +    for (j = 0; j < data->count; ++j) {
    +	PrintF(output, 33, "#define HTML_%s_%s",
    +	       data->name,
    +	       no_dashes(buffer, data->attrs[j].name));
    +	fprintf(output, "%2d\n", j);
    +    }
    +    PrintF(output, 33, "#define HTML_%s_ATTRIBUTES", data->name);
    +    fprintf(output, "%2d\n", data->count);
    +    fprintf(output, "\n");
    +}
    +
    +static void dump_header(FILE *output, const SGML_dtd * dtd)
    +{
    +    const char *marker = "hdr_HTMLDTD_H";
    +    int j;
    +
    +    unsigned count = 0;
    +    AttrInfo *data = sorted_attrs(dtd, &count);
    +
    +    fprintf(output, "/* %cLynxId%c */\n", '$', '$');
    +    fprintf(output, "#ifndef %s\n", marker);
    +    fprintf(output, "#define %s 1\n\n", marker);
    +
    +    NOTE("#ifdef __cplusplus");
    +    NOTE("extern \"C\" {");
    +    NOTE("#endif");
    +
    +    NOTE("/*");
    +    NOTE("");
    +    NOTE("   Element Numbers");
    +    NOTE("");
    +    NOTE("   Must Match all tables by element!");
    +    NOTE("   These include tables in HTMLDTD.c");
    +    NOTE("   and code in HTML.c.");
    +    NOTE("");
    +    NOTE(" */");
    +
    +    fprintf(output, "    typedef enum {\n");
    +    for (j = 0; j < dtd->number_of_tags; ++j) {
    +	if (dtd->tags[j].alias) {
    +	    fprintf(output, "\tHTML_%s_%d,\n", dtd->tags[j].name,
    +		    dtd->tags[j].alias + 1);
    +	} else {
    +	    fprintf(output, "\tHTML_%s,\n", dtd->tags[j].name);
    +	}
    +    }
    +    NOTE("\tHTML_ALT_OBJECT");
    +    NOTE("    } HTMLElement;\n");
    +    NOTE("/* Notes: HTML.c uses a different extension of the");
    +    NOTE("          HTML_ELEMENTS space privately, see");
    +    NOTE("          HTNestedList.h.");
    +    NOTE("");
    +    NOTE("   Do NOT replace HTML_ELEMENTS with");
    +    NOTE("   TABLESIZE(mumble_dtd.tags).");
    +    NOTE("");
    +    NOTE("   Keep the following defines in synch with");
    +    NOTE("   the above enum!");
    +    NOTE(" */");
    +    NOTE("");
    +    NOTE("/* # of elements generally visible to Lynx code */");
    +    fprintf(output, "#define HTML_ELEMENTS %d\n", dtd->number_of_tags);
    +    NOTE("");
    +    NOTE("/* # of elements visible to SGML parser */");
    +    fprintf(output, "#define HTML_ALL_ELEMENTS %d\n", dtd->number_of_tags + 1);
    +    NOTE("");
    +    NOTE("/*");
    +    NOTE("");
    +    NOTE("   Attribute numbers");
    +    NOTE("");
    +    NOTE("   Identifier is HTML__.");
    +    NOTE("   These must match the tables in HTML.c!");
    +    NOTE("");
    +    NOTE(" */");
    +
    +    /* output the sorted list */
    +    for (j = 0; j < (int) count; ++j) {
    +	dump_hdr_attr(output, data + j);
    +    }
    +    free(data);
    +
    +    NOTE("#ifdef __cplusplus");
    +    NOTE("}");
    +    NOTE("#endif");
    +
    +    fprintf(output, "#endif\t\t\t\t/* %s */\n", marker);
    +}
    +
    +static void dump_flat_attrs(FILE *output,
    +			    const attr * attributes,
    +			    int number_of_attributes)
    +{
    +    int n;
    +
    +    fprintf(output, "\t\t" FMT_NUM_ATTRS "\n", number_of_attributes);
    +    for (n = 0; n < number_of_attributes; ++n) {
    +	fprintf(output, "\t\t\t" FMT_ONE_ATTR "\n", n,
    +#ifdef USE_PRETTYSRC
    +		attributes[n].type,
    +#else
    +		0,		/* need placeholder for source-compat */
    +#endif
    +		attributes[n].name
    +	    );
    +    }
    +}
    +
    +static void dump_flat_attr_types(FILE *output, const AttrType * attr_types)
    +{
    +    const AttrType *p = sorted_AttrTypes(attr_types);
    +    int number = len_AttrTypes(attr_types);
    +
    +    fprintf(output, "\t\t" FMT_NUM_ATTR_TYPES "\n", number);
    +
    +    if (p != 0) {
    +	while (p->name != 0) {
    +	    fprintf(output, "\t\t\t%s\n", p->name);
    +	    ++p;
    +	}
    +    }
    +}
    +
    +static void dump_flat_SGMLContent(FILE *output, const char *name, SGMLContent contents)
    +{
    +    fprintf(output, "\t\t%s: %s\n", name, SGMLContent2s(contents));
    +}
    +
    +#define DUMP(name) \
    +	if (theClass & Tgc_##name) {\
    +	    fprintf(output, " " #name); \
    +	    theClass &= ~(Tgc_##name); \
    +	}
    +
    +static void dump_flat_TagClass(FILE *output, const char *name, TagClass theClass)
    +{
    +    fprintf(output, "\t\t%s:", name);
    +    DUMP(FONTlike);
    +    DUMP(EMlike);
    +    DUMP(MATHlike);
    +    DUMP(Alike);
    +    DUMP(formula);
    +    DUMP(TRlike);
    +    DUMP(SELECTlike);
    +    DUMP(FORMlike);
    +    DUMP(Plike);
    +    DUMP(DIVlike);
    +    DUMP(LIlike);
    +    DUMP(ULlike);
    +    DUMP(BRlike);
    +    DUMP(APPLETlike);
    +    DUMP(HRlike);
    +    DUMP(MAPlike);
    +    DUMP(outer);
    +    DUMP(BODYlike);
    +    DUMP(HEADstuff);
    +    DUMP(same);
    +    DUMP(DELlike);
    +    if (theClass)
    +	fprintf(output, " OOPS:%#x", theClass);
    +    fprintf(output, "\n");
    +}
    +
    +#undef DUMP
    +
    +#define DUMP(name) \
    +	if (theFlags & Tgf_##name) {\
    +	    fprintf(output, " " #name); \
    +	    theFlags &= ~(Tgf_##name); \
    +	}
    +
    +static void dump_flat_TagFlags(FILE *output, const char *name, TagFlags theFlags)
    +{
    +    fprintf(output, "\t\t%s:", name);
    +    DUMP(endO);
    +    DUMP(startO);
    +    DUMP(mafse);
    +    DUMP(strict);
    +    DUMP(nreie);
    +    DUMP(frecyc);
    +    DUMP(nolyspcl);
    +    if (theFlags)
    +	fprintf(output, " OOPS:%#x", theFlags);
    +    fprintf(output, "\n");
    +}
    +
    +#undef DUMP
    +
    +static void dump_flat_HTTag(FILE *output, unsigned n, HTTag * tag)
    +{
    +    fprintf(output, "\t%u:%s\n", n, EXT_name(tag));
    +#ifdef USE_JUSTIFY_ELTS
    +    fprintf(output, "\t\t%s\n", tag->can_justify ? "justify" : "nojustify");
    +#endif
    +    dump_flat_attrs(output, tag->attributes, AttrCount(tag));
    +    dump_flat_attr_types(output, tag->attr_types);
    +    dump_flat_SGMLContent(output, "contents", tag->contents);
    +    dump_flat_TagClass(output, "tagclass", tag->tagclass);
    +    dump_flat_TagClass(output, "contains", tag->contains);
    +    dump_flat_TagClass(output, "icontains", tag->icontains);
    +    dump_flat_TagClass(output, "contained", tag->contained);
    +    dump_flat_TagClass(output, "icontained", tag->icontained);
    +    dump_flat_TagClass(output, "canclose", tag->canclose);
    +    dump_flat_TagFlags(output, "flags", tag->flags);
    +}
    +
    +static int count_attr_types(AttrType * attr_types, HTTag * tag)
    +{
    +    int count = 0;
    +    const AttrType *p;
    +    AttrType *q;
    +
    +    if ((p = tag->attr_types) != 0) {
    +	while (p->name != 0) {
    +	    if ((q = attr_types) != 0) {
    +		while (q->name != 0) {
    +		    if (!strcmp(q->name, p->name)) {
    +			--count;
    +			break;
    +		    }
    +		    ++q;
    +		}
    +		*q = *p;
    +	    }
    +	    ++count;
    +	    ++p;
    +	}
    +    }
    +    return count;
    +}
    +
    +static void dump_flatfile(FILE *output, const SGML_dtd * dtd)
    +{
    +    AttrType *attr_types = 0;
    +    int pass;
    +    unsigned count = 0;
    +    unsigned n;
    +
    +    /* merge all of the attr_types data */
    +    for (pass = 0; pass < 2; ++pass) {
    +	for (n = 0; (int) n < dtd->number_of_tags; ++n) {
    +	    count += count_attr_types(attr_types, &(dtd->tags[n]));
    +	}
    +	if (pass == 0) {
    +	    attr_types = typecallocn(AttrType, count + 1);
    +	    count = 0;
    +	} else {
    +	    count = len_AttrTypes(attr_types);
    +	    qsort(attr_types, count, sizeof(*attr_types), compare_attr_types);
    +	    fprintf(output, FMT_NUM_ATTR_TYPES "\n", count);
    +	    for (n = 0; n < count; ++n) {
    +		fprintf(output, "\t%d:%s\n", n, attr_types[n].name);
    +		dump_flat_attrs(output, attr_types[n].list,
    +				len_AttrList(attr_types[n].list));
    +	    }
    +	}
    +    }
    +
    +    fprintf(output, "%d tags\n", dtd->number_of_tags);
    +    for (n = 0; (int) n < dtd->number_of_tags; ++n) {
    +	dump_flat_HTTag(output, n, &(dtd->tags[n]));
    +    }
    +#if 0
    +    fprintf(output, "%d entities\n", dtd->number_of_entities);
    +    for (n = 0; n < dtd->number_of_entities; ++n) {
    +    }
    +#endif
    +}
    +
    +#define LOAD(name) \
    +	if (!strcmp(data, #name)) {\
    +	    *theClass |= Tgc_##name; \
    +	    continue; \
    +	}
    +
    +static int load_flat_TagClass(FILE *input, const char *name, TagClass * theClass)
    +{
    +    char prefix[80];
    +    char *next = get_line(input);
    +    char *data;
    +    int result = 0;
    +
    +    *theClass = 0;
    +    if (next != 0) {
    +	sprintf(prefix, "\t\t%s:", name);
    +	data = strtok(next, "\n ");
    +
    +	if (data != 0 && !strcmp(data, prefix)) {
    +	    result = 1;
    +
    +	    while ((data = strtok(NULL, "\n ")) != 0) {
    +
    +		LOAD(FONTlike);
    +		LOAD(EMlike);
    +		LOAD(MATHlike);
    +		LOAD(Alike);
    +		LOAD(formula);
    +		LOAD(TRlike);
    +		LOAD(SELECTlike);
    +		LOAD(FORMlike);
    +		LOAD(Plike);
    +		LOAD(DIVlike);
    +		LOAD(LIlike);
    +		LOAD(ULlike);
    +		LOAD(BRlike);
    +		LOAD(APPLETlike);
    +		LOAD(HRlike);
    +		LOAD(MAPlike);
    +		LOAD(outer);
    +		LOAD(BODYlike);
    +		LOAD(HEADstuff);
    +		LOAD(same);
    +		LOAD(DELlike);
    +
    +		warning("Unexpected TagClass '%s'", data);
    +		result = 0;
    +		break;
    +	    }
    +	} else if (data) {
    +	    warning("load_flat_TagClass: '%s' vs '%s'", data, prefix);
    +	}
    +	free(next);
    +    } else {
    +	warning("Did not find contents");
    +    }
    +    return result;
    +}
    +
    +#undef LOAD
    +
    +#define LOAD(name) \
    +	if (!strcmp(data, #name)) {\
    +	    *flags |= Tgf_##name; \
    +	    continue; \
    +	}
    +
    +static int load_flat_TagFlags(FILE *input, const char *name, TagFlags * flags)
    +{
    +    char prefix[80];
    +    char *next = get_line(input);
    +    char *data;
    +    int result = 0;
    +
    +    *flags = 0;
    +    if (next != 0) {
    +	sprintf(prefix, "\t\t%s:", name);
    +	data = strtok(next, "\n ");
    +
    +	if (data != 0 && !strcmp(data, prefix)) {
    +	    result = 1;
    +
    +	    while ((data = strtok(NULL, "\n ")) != 0) {
    +
    +		LOAD(endO);
    +		LOAD(startO);
    +		LOAD(mafse);
    +		LOAD(strict);
    +		LOAD(nreie);
    +		LOAD(frecyc);
    +		LOAD(nolyspcl);
    +
    +		warning("Unexpected TagFlag '%s'", data);
    +		result = 0;
    +		break;
    +	    }
    +	} else if (data) {
    +	    warning("load_flat_TagFlags: '%s' vs '%s'", data, prefix);
    +	}
    +	free(next);
    +    }
    +    return result;
    +}
    +
    +#undef LOAD
    +
    +static int load_flat_AttrList(FILE *input, AttrList * attrs, int *length)
    +{
    +    attr *attributes;
    +    int j, jcmp, code;
    +    int result = 1;
    +    char name[MY_LIMIT];
    +    char *next;
    +    char trail[MY_LIMIT];
    +
    +#ifdef USE_PRETTYSRC
    +    int atype;
    +#endif
    +
    +    next = get_line(input);
    +    if (sscanf(next, FMT_NUM_ATTRS FMT_WS, length, trail) == 2
    +	&& *length > 0
    +	&& (attributes = typecallocn(attr, *length + 1)) != 0) {
    +	*attrs = attributes;
    +	for (j = 0; j < *length; ++j) {
    +	    next = get_line(input);
    +	    code = sscanf(next, FMT_ONE_ATTR FMT_WS, &jcmp, &atype, name, trail);
    +	    if (code == NUM_ONE_ATTR) {
    +		if (j != jcmp)
    +		    warning("resequence %s from %d to %d", name, jcmp, j);
    +		attributes[j].name = strdup(name);
    +#ifdef USE_PRETTYSRC
    +		attributes[j].type = atype;
    +#endif
    +	    } else {
    +		warning("Did not find attributes");
    +		result = 0;
    +		break;
    +	    }
    +	}
    +	if (*length > 1 && result)
    +	    qsort(attributes, *length, sizeof(attributes[0]), compare_attr);
    +    } else {
    +	warning("expected attribute count:\n%s", next);
    +    }
    +    return result;
    +}
    +
    +/* find the given attribute in the list of attr_types */
    +static int find_attribute(const char *attribute, const AttrType * attr_types)
    +{
    +    int j, k;
    +    int found = -1;
    +
    +    for (j = 0; attr_types[j].name; ++j) {
    +	AttrList list = attr_types[j].list;
    +
    +	for (k = 0; list[k].name; ++k) {
    +	    if (!strcmp(attribute, list[k].name)) {
    +		if (found >= 0)
    +		    warning("attribute %s is in attr_types %s and %s",
    +			    attribute,
    +			    attr_types[found].name,
    +			    attr_types[j].name);
    +		found = j;
    +	    }
    +	}
    +    }
    +    return (found >= 0);
    +}
    +
    +static int load_flat_HTTag(FILE *input, HTTag * tag, AttrType * allTypes)
    +{
    +    int result = 0;
    +    unsigned ncmp = 0;
    +    char name[MY_LIMIT];
    +    char trail[MY_LIMIT];
    +    char *next;
    +    int code;
    +    int j;
    +
    +    next = get_line(input);
    +    code = sscanf(next, "%d:%s" FMT_WS, &ncmp, name, trail);
    +    if (code == 3) {
    +	result = 1;
    +	tag->alias = 0;
    +	tag->aliases = 0;
    +	for (j = 0; name[j] != '\0'; ++j) {
    +	    int ch = UCH(name[j]);
    +
    +	    if (!isalnum(ch)) {
    +		if (strchr(alias_codes, ch) != NULL) {
    +		    tag->alias = (ch - '!') + 1;
    +		    name[j] = '\0';
    +		} else {
    +		    result = 0;
    +		}
    +		break;
    +	    }
    +	}
    +	tag->name = strdup(name);
    +#ifdef USE_COLOR_STYLE
    +	tag->name_len = strlen(tag->name);
    +#endif
    +#ifdef USE_JUSTIFY_ELTS
    +	next = get_line(input);
    +	if (sscanf(next, "%s" FMT_WS, name, trail) == 2) {
    +	    tag->can_justify = !strcmp(name, "justify");
    +	} else {
    +	    warning("Did not find can_justify");
    +	    result = 0;
    +	}
    +#endif
    +	if (result) {
    +	    result = load_flat_AttrList(input, &(tag->attributes), &(tag->number_of_attributes));
    +	}
    +	if (result) {
    +	    AttrType *myTypes;
    +	    int k, count;
    +
    +	    if ((count = read_num_attr_types(input)) > 0
    +		&& (myTypes = typecallocn(AttrType, count + 1)) != 0) {
    +		tag->attr_types = myTypes;
    +		for (k = 0; k < count; ++k) {
    +		    next = get_line(input);
    +		    if (next != 0
    +			&& sscanf(next, "%s" FMT_WS, name, trail)) {
    +			int found = -1;
    +
    +			for (j = 0; allTypes[j].name != 0; ++j) {
    +			    if (!strcmp(allTypes[j].name, name)) {
    +				myTypes[k].name = strdup(name);
    +				myTypes[k].list = allTypes[j].list;
    +				found = k;
    +				break;
    +			    }
    +			}
    +			if (found < 0) {
    +			    warning("not found in attr_types: %s", name);
    +			    continue;
    +			}
    +		    } else {
    +			result = 0;
    +			break;
    +		    }
    +		}
    +		if (result) {
    +		    if (count > 1)
    +			qsort(myTypes,
    +			      count,
    +			      sizeof(myTypes[0]),
    +			      compare_attr_types);
    +		    for (k = 0; k < tag->number_of_attributes; ++k) {
    +			if (!find_attribute(tag->attributes[k].name, tag->attr_types)) {
    +			    warning("%s attribute %s not found in attr_types",
    +				    tag->name, tag->attributes[k].name);
    +			}
    +		    }
    +		}
    +	    }
    +	}
    +	if (result) {
    +
    +	    next = get_line(input);
    +	    if (next != 0
    +		&& sscanf(next, "\t\tcontents: %s" FMT_WS, name, trail)) {
    +		tag->contents = s2SGMLContent(name);
    +		free(next);
    +	    } else {
    +		warning("Did not find contents");
    +		result = 0;
    +	    }
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "tagclass", &(tag->tagclass));
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "contains", &(tag->contains));
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "icontains", &(tag->icontains));
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "contained", &(tag->contained));
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "icontained", &(tag->icontained));
    +	}
    +	if (result) {
    +	    result = load_flat_TagClass(input, "canclose", &(tag->canclose));
    +	}
    +	if (result) {
    +	    result = load_flat_TagFlags(input, "flags", &(tag->flags));
    +	}
    +    } else {
    +	warning("load_flat_HTTag error");
    +    }
    +    return result;
    +}
    +
    +static int load_flat_AttrType(FILE *input, AttrType * types, size_t ncmp)
    +{
    +    int result = 0;
    +    int ntst;
    +    char name[MY_LIMIT];
    +    char trail[MY_LIMIT];
    +    char *next;
    +
    +    next = get_line(input);
    +    if (sscanf(next, "%d:%s" FMT_WS, &ntst, name, trail) == 3
    +	&& (ntst == (int) ncmp)) {
    +	result = 1;
    +	types->name = strdup(name);
    +	if (!load_flat_AttrList(input, &(types->list), &ntst))
    +	    result = 0;
    +    } else {
    +	warning("expected a tag:\n%s", next);
    +    }
    +    return result;
    +}
    +
    +static int compare_tags(const void *a, const void *b)
    +{
    +    const HTTag *p = (const HTTag *) a;
    +    const HTTag *q = (const HTTag *) b;
    +    int result = 0;
    +
    +    if ((result = strcmp(p->name, q->name)) == 0) {
    +	result = p->alias - q->alias;
    +    }
    +    return result;
    +}
    +
    +static SGML_dtd *load_flatfile(FILE *input)
    +{
    +    AttrType *attr_types = 0;
    +    SGML_dtd *result = 0;
    +    char *next;
    +    size_t n;
    +    int number_of_attrs = 0;
    +    int number_of_tags = 0;
    +    HTTag *tag;
    +    int code;
    +    char trail[MY_LIMIT];
    +
    +    if ((number_of_attrs = read_num_attr_types(input)) <= 0) {
    +	;
    +    } else if ((attr_types = typecallocn(AttrType, number_of_attrs + 1)) == 0) {
    +	failed("calloc attr_types");
    +    }
    +
    +    for (n = 0; n < (size_t) number_of_attrs; ++n) {
    +	if (!load_flat_AttrType(input, attr_types + n, n)) {
    +	    break;
    +	}
    +    }
    +
    +    next = get_line(input);
    +    code = sscanf(next, "%d tags" FMT_WS, &number_of_tags, trail);
    +    if (code == 2) {
    +	if ((result = typecalloc(SGML_dtd)) != 0
    +	    && (result->tags = typecallocn(HTTag, number_of_tags + 2)) != 0) {
    +	    for (n = 0; n < (size_t) number_of_tags; ++n) {
    +		if (load_flat_HTTag(input, &(result->tags[n]), attr_types)) {
    +		    result->number_of_tags = (n + 1);
    +		} else {
    +		    break;
    +		}
    +	    }
    +	    tag = 0;
    +	    for (n = 0; n < (size_t) number_of_tags; ++n) {
    +		if (result->tags[n].name != 0
    +		    && !strcmp(result->tags[n].name, "OBJECT")) {
    +		    tag = result->tags + number_of_tags;
    +		    *tag = result->tags[n];
    +		    tag->contents = SGML_MIXED;
    +		    tag->flags = Tgf_strict;
    +		    break;
    +		}
    +	    }
    +	    if (tag == 0) {
    +		warning("Did not find OBJECT tag");
    +		result = 0;
    +	    } else {
    +		qsort(result->tags, number_of_tags, sizeof(HTTag), compare_tags);
    +	    }
    +	}
    +    } else {
    +	warning("expected tag count:\n%s", next);
    +    }
    +    return result;
    +}
    +
    +static void cross_check(FILE *output, const SGML_dtd * the_dtd, int level)
    +{
    +    int ft;
    +
    +    fprintf(output, "Cross-check HTML DTD:\n");
    +    fprintf(output, "\n");
    +    /* make a sorted list of tags */
    +    /* for each tag in the list, find the classes it might contain */
    +    for (ft = 0; ft < the_dtd->number_of_tags; ++ft) {
    +	int xc;
    +	HTTag *ftag = &(the_dtd->tags[ft]);
    +
    +	/* for each contained-class, check if it says it can be contained */
    +	fprintf(output, "tag %s\n", EXT_name(ftag));
    +	for (xc = 0; class_list[xc].name != 0; ++xc) {
    +	    int rt;
    +	    int direct;
    +	    int passes = 2;
    +
    +	    /* most of the tags are (should be) symmetric */
    +	    if (level <= 1) {
    +		BOOL same = TRUE;
    +
    +		if ((ftag->contains & class_list[xc].tagclass)
    +		    != (ftag->icontains & class_list[xc].tagclass)) {
    +		    same = FALSE;
    +		} else if ((ftag->contains & class_list[xc].tagclass) == 0) {
    +		    continue;
    +		} else if (0) {
    +		    HTTag *rtag;
    +
    +		    for (rt = 0; rt < the_dtd->number_of_tags; ++rt) {
    +			rtag = &(the_dtd->tags[rt]);
    +			if (ftag == rtag)
    +			    continue;
    +			if ((ftag->contains & rtag->tagclass) == 0)
    +			    continue;
    +			if ((rtag->contained & ftag->tagclass)
    +			    != (rtag->icontained & ftag->tagclass)) {
    +			    same = FALSE;
    +			    break;
    +			}
    +		    }
    +		}
    +		if (same)
    +		    passes = 1;
    +	    }
    +
    +	    for (direct = 0; direct < passes; ++direct) {
    +		TagClass check = (direct ? ftag->contains : ftag->icontains);
    +		BOOL first = TRUE;
    +
    +		if ((check &= class_list[xc].tagclass) == 0)
    +		    continue;
    +
    +		for (rt = 0; rt < the_dtd->number_of_tags; ++rt) {
    +		    HTTag *rtag = &(the_dtd->tags[rt]);
    +		    TagClass check2 = (direct ? rtag->contained : rtag->icontained);
    +
    +		    if (rt == ft)
    +			continue;
    +		    if ((check & rtag->tagclass) == 0)
    +			continue;
    +		    if ((check2 & ftag->tagclass) == 0)
    +			continue;
    +		    if (first) {
    +			if (passes == 2) {
    +			    fprintf(output, "\t%s (%s)\n",
    +				    class_list[xc].name,
    +				    direct ? "direct" : "indirect");
    +			} else {
    +			    fprintf(output, "\t%s\n",
    +				    class_list[xc].name);
    +			}
    +		    }
    +		    fprintf(output, "%s%s",
    +			    first ? "\t\t" : " ",
    +			    EXT_name(rtag));
    +		    first = FALSE;
    +		}
    +		if (first) {
    +		    if (level > 1) {
    +			if (passes == 2) {
    +			    fprintf(output, "\t%s (%s)\n",
    +				    class_list[xc].name,
    +				    direct ? "direct" : "indirect");
    +			} else {
    +			    fprintf(output, "\t%s\n",
    +				    class_list[xc].name);
    +			}
    +			fprintf(output, "\t\t(missing)\n");
    +		    }
    +		} else {
    +		    fprintf(output, "\n");
    +		}
    +	    }
    +	}
    +    }
    +}
    +
    +int main(int argc, char *argv[])
    +{
    +    const SGML_dtd *the_dtd = &HTML_dtd;
    +    int ch;
    +    int dtd_version = 0;
    +    int c_option = FALSE;
    +    int h_option = FALSE;
    +    int l_option = FALSE;
    +    int x_option = 0;
    +    FILE *input = stdin;
    +    FILE *output = stdout;
    +
    +    input_filename = "";
    +    input_lineno = 0;
    +
    +    while ((ch = getopt(argc, argv, GETOPT)) != -1) {
    +	switch (ch) {
    +	case 'c':
    +	    c_option = TRUE;
    +	    break;
    +	case 'h':
    +	    h_option = TRUE;
    +	    break;
    +	case 'l':
    +	    l_option = TRUE;
    +	    input = fopen((input_filename = optarg), "r");
    +	    if (input == 0)
    +		failed(optarg);
    +	    break;
    +	case 'o':
    +	    output = fopen(optarg, "w");
    +	    if (output == 0)
    +		failed(optarg);
    +	    break;
    +	case 't':
    +	    dtd_version = 1;
    +	    break;
    +	case 's':
    +	    dtd_version = 0;
    +	    break;
    +	case 'x':
    +	    ++x_option;
    +	    break;
    +	default:
    +	    usage();
    +	}
    +    }
    +
    +    HTSwitchDTD(dtd_version);
    +    if (l_option)
    +	the_dtd = load_flatfile(input);
    +
    +    if (the_dtd != 0) {
    +	if (c_option)
    +	    dump_source(output, the_dtd, dtd_version);
    +	if (h_option)
    +	    dump_header(output, the_dtd);
    +	if (x_option)
    +	    cross_check(output, the_dtd, x_option);
    +	if (!c_option && !h_option && !x_option)
    +	    dump_flatfile(output, the_dtd);
    +    }
    +
    +    return EXIT_SUCCESS;
    +}
    diff --git a/WWW/Library/Implementation/hdr_HTMLDTD.h b/WWW/Library/Implementation/hdr_HTMLDTD.h
    new file mode 100644
    index 0000000..21888d0
    --- /dev/null
    +++ b/WWW/Library/Implementation/hdr_HTMLDTD.h
    @@ -0,0 +1,1000 @@
    +/* $LynxId: hdr_HTMLDTD.h,v 1.25 2022/09/28 22:26:32 tom Exp $ */
    +#ifndef hdr_HTMLDTD_H
    +#define hdr_HTMLDTD_H 1
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +/*
    +
    +   Element Numbers
    +
    +   Must Match all tables by element!
    +   These include tables in HTMLDTD.c
    +   and code in HTML.c.
    +
    + */
    +    typedef enum {
    +	HTML_A,
    +	HTML_ABBR,
    +	HTML_ACRONYM,
    +	HTML_ADDRESS,
    +	HTML_APPLET,
    +	HTML_AREA,
    +	HTML_ARTICLE,
    +	HTML_ASIDE,
    +	HTML_AU,
    +	HTML_AUTHOR,
    +	HTML_B,
    +	HTML_BANNER,
    +	HTML_BASE,
    +	HTML_BASEFONT,
    +	HTML_BDO,
    +	HTML_BGSOUND,
    +	HTML_BIG,
    +	HTML_BLINK,
    +	HTML_BLOCKQUOTE,
    +	HTML_BODY,
    +	HTML_BODYTEXT,
    +	HTML_BQ,
    +	HTML_BR,
    +	HTML_BUTTON,
    +	HTML_CAPTION,
    +	HTML_CENTER,
    +	HTML_CITE,
    +	HTML_CODE,
    +	HTML_COL,
    +	HTML_COLGROUP,
    +	HTML_COMMENT,
    +	HTML_CREDIT,
    +	HTML_DD,
    +	HTML_DEL,
    +	HTML_DEL_2,
    +	HTML_DFN,
    +	HTML_DIR,
    +	HTML_DIV,
    +	HTML_DL,
    +	HTML_DLC,
    +	HTML_DT,
    +	HTML_EM,
    +	HTML_EMBED,
    +	HTML_FIELDSET,
    +	HTML_FIG,
    +	HTML_FIGURE,
    +	HTML_FN,
    +	HTML_FONT,
    +	HTML_FOOTER,
    +	HTML_FORM,
    +	HTML_FRAME,
    +	HTML_FRAMESET,
    +	HTML_H1,
    +	HTML_H2,
    +	HTML_H3,
    +	HTML_H4,
    +	HTML_H5,
    +	HTML_H6,
    +	HTML_HEAD,
    +	HTML_HEADER,
    +	HTML_HR,
    +	HTML_HTML,
    +	HTML_HY,
    +	HTML_I,
    +	HTML_IFRAME,
    +	HTML_IMG,
    +	HTML_INPUT,
    +	HTML_INS,
    +	HTML_INS_2,
    +	HTML_ISINDEX,
    +	HTML_KBD,
    +	HTML_KEYGEN,
    +	HTML_LABEL,
    +	HTML_LEGEND,
    +	HTML_LH,
    +	HTML_LI,
    +	HTML_LINK,
    +	HTML_LISTING,
    +	HTML_MAIN,
    +	HTML_MAP,
    +	HTML_MARQUEE,
    +	HTML_MATH,
    +	HTML_MENU,
    +	HTML_META,
    +	HTML_NAV,
    +	HTML_NEXTID,
    +	HTML_NOFRAMES,
    +	HTML_NOTE,
    +	HTML_OBJECT,
    +	HTML_OL,
    +	HTML_OPTION,
    +	HTML_OVERLAY,
    +	HTML_P,
    +	HTML_PARAM,
    +	HTML_PLAINTEXT,
    +	HTML_PRE,
    +	HTML_Q,
    +	HTML_S,
    +	HTML_SAMP,
    +	HTML_SCRIPT,
    +	HTML_SECTION,
    +	HTML_SELECT,
    +	HTML_SHY,
    +	HTML_SMALL,
    +	HTML_SPAN,
    +	HTML_SPOT,
    +	HTML_STRIKE,
    +	HTML_STRONG,
    +	HTML_STYLE,
    +	HTML_SUB,
    +	HTML_SUP,
    +	HTML_TAB,
    +	HTML_TABLE,
    +	HTML_TBODY,
    +	HTML_TD,
    +	HTML_TEXTAREA,
    +	HTML_TEXTFLOW,
    +	HTML_TFOOT,
    +	HTML_TH,
    +	HTML_THEAD,
    +	HTML_TITLE,
    +	HTML_TR,
    +	HTML_TT,
    +	HTML_U,
    +	HTML_UL,
    +	HTML_VAR,
    +	HTML_WBR,
    +	HTML_XMP,
    +	HTML_ALT_OBJECT
    +    } HTMLElement;
    +
    +/* Notes: HTML.c uses a different extension of the
    +          HTML_ELEMENTS space privately, see
    +          HTNestedList.h.
    +
    +   Do NOT replace HTML_ELEMENTS with
    +   TABLESIZE(mumble_dtd.tags).
    +
    +   Keep the following defines in synch with
    +   the above enum!
    + */
    +
    +/* # of elements generally visible to Lynx code */
    +#define HTML_ELEMENTS 128
    +
    +/* # of elements visible to SGML parser */
    +#define HTML_ALL_ELEMENTS 129
    +
    +/*
    +
    +   Attribute numbers
    +
    +   Identifier is HTML__.
    +   These must match the tables in HTML.c!
    +
    + */
    +#define HTML_A_ACCESSKEY          0
    +#define HTML_A_CHARSET            1
    +#define HTML_A_CLASS              2
    +#define HTML_A_CLEAR              3
    +#define HTML_A_COORDS             4
    +#define HTML_A_DIR                5
    +#define HTML_A_HREF               6
    +#define HTML_A_HREFLANG           7
    +#define HTML_A_ID                 8
    +#define HTML_A_ISMAP              9
    +#define HTML_A_LANG              10
    +#define HTML_A_MD                11
    +#define HTML_A_NAME              12
    +#define HTML_A_NOTAB             13
    +#define HTML_A_ONBLUR            14
    +#define HTML_A_ONFOCUS           15
    +#define HTML_A_REL               16
    +#define HTML_A_REV               17
    +#define HTML_A_SHAPE             18
    +#define HTML_A_STYLE             19
    +#define HTML_A_TABINDEX          20
    +#define HTML_A_TARGET            21
    +#define HTML_A_TITLE             22
    +#define HTML_A_TYPE              23
    +#define HTML_A_URN               24
    +#define HTML_A_ATTRIBUTES        25
    +
    +#define HTML_ADDRESS_CLASS        0
    +#define HTML_ADDRESS_CLEAR        1
    +#define HTML_ADDRESS_DIR          2
    +#define HTML_ADDRESS_ID           3
    +#define HTML_ADDRESS_LANG         4
    +#define HTML_ADDRESS_NOWRAP       5
    +#define HTML_ADDRESS_STYLE        6
    +#define HTML_ADDRESS_TITLE        7
    +#define HTML_ADDRESS_ATTRIBUTES   8
    +
    +#define HTML_APPLET_ALIGN         0
    +#define HTML_APPLET_ALT           1
    +#define HTML_APPLET_CLASS         2
    +#define HTML_APPLET_CLEAR         3
    +#define HTML_APPLET_CODE          4
    +#define HTML_APPLET_CODEBASE      5
    +#define HTML_APPLET_DIR           6
    +#define HTML_APPLET_DOWNLOAD      7
    +#define HTML_APPLET_HEIGHT        8
    +#define HTML_APPLET_HSPACE        9
    +#define HTML_APPLET_ID           10
    +#define HTML_APPLET_LANG         11
    +#define HTML_APPLET_NAME         12
    +#define HTML_APPLET_STYLE        13
    +#define HTML_APPLET_TITLE        14
    +#define HTML_APPLET_VSPACE       15
    +#define HTML_APPLET_WIDTH        16
    +#define HTML_APPLET_ATTRIBUTES   17
    +
    +#define HTML_AREA_ACCESSKEY       0
    +#define HTML_AREA_ALT             1
    +#define HTML_AREA_CLASS           2
    +#define HTML_AREA_CLEAR           3
    +#define HTML_AREA_COORDS          4
    +#define HTML_AREA_DIR             5
    +#define HTML_AREA_HREF            6
    +#define HTML_AREA_ID              7
    +#define HTML_AREA_LANG            8
    +#define HTML_AREA_NOHREF          9
    +#define HTML_AREA_NOTAB          10
    +#define HTML_AREA_ONBLUR         11
    +#define HTML_AREA_ONFOCUS        12
    +#define HTML_AREA_SHAPE          13
    +#define HTML_AREA_STYLE          14
    +#define HTML_AREA_TABINDEX       15
    +#define HTML_AREA_TARGET         16
    +#define HTML_AREA_TITLE          17
    +#define HTML_AREA_ATTRIBUTES     18
    +
    +#define HTML_BASE_CLASS           0
    +#define HTML_BASE_HREF            1
    +#define HTML_BASE_ID              2
    +#define HTML_BASE_STYLE           3
    +#define HTML_BASE_TARGET          4
    +#define HTML_BASE_TITLE           5
    +#define HTML_BASE_ATTRIBUTES      6
    +
    +#define HTML_BGSOUND_CLASS        0
    +#define HTML_BGSOUND_CLEAR        1
    +#define HTML_BGSOUND_DIR          2
    +#define HTML_BGSOUND_ID           3
    +#define HTML_BGSOUND_LANG         4
    +#define HTML_BGSOUND_LOOP         5
    +#define HTML_BGSOUND_SRC          6
    +#define HTML_BGSOUND_STYLE        7
    +#define HTML_BGSOUND_TITLE        8
    +#define HTML_BGSOUND_ATTRIBUTES   9
    +
    +#define HTML_BODY_ALINK           0
    +#define HTML_BODY_BACKGROUND      1
    +#define HTML_BODY_BGCOLOR         2
    +#define HTML_BODY_CLASS           3
    +#define HTML_BODY_CLEAR           4
    +#define HTML_BODY_DIR             5
    +#define HTML_BODY_ID              6
    +#define HTML_BODY_LANG            7
    +#define HTML_BODY_LINK            8
    +#define HTML_BODY_ONLOAD          9
    +#define HTML_BODY_ONUNLOAD       10
    +#define HTML_BODY_STYLE          11
    +#define HTML_BODY_TEXT           12
    +#define HTML_BODY_TITLE          13
    +#define HTML_BODY_VLINK          14
    +#define HTML_BODY_ATTRIBUTES     15
    +
    +#define HTML_BODYTEXT_CLASS       0
    +#define HTML_BODYTEXT_CLEAR       1
    +#define HTML_BODYTEXT_DATA        2
    +#define HTML_BODYTEXT_DIR         3
    +#define HTML_BODYTEXT_ID          4
    +#define HTML_BODYTEXT_LANG        5
    +#define HTML_BODYTEXT_NAME        6
    +#define HTML_BODYTEXT_OBJECT      7
    +#define HTML_BODYTEXT_REF         8
    +#define HTML_BODYTEXT_STYLE       9
    +#define HTML_BODYTEXT_TITLE      10
    +#define HTML_BODYTEXT_TYPE       11
    +#define HTML_BODYTEXT_VALUE      12
    +#define HTML_BODYTEXT_VALUETYPE  13
    +#define HTML_BODYTEXT_ATTRIBUTES 14
    +
    +#define HTML_BQ_CITE              0
    +#define HTML_BQ_CLASS             1
    +#define HTML_BQ_CLEAR             2
    +#define HTML_BQ_DIR               3
    +#define HTML_BQ_ID                4
    +#define HTML_BQ_LANG              5
    +#define HTML_BQ_NOWRAP            6
    +#define HTML_BQ_STYLE             7
    +#define HTML_BQ_TITLE             8
    +#define HTML_BQ_ATTRIBUTES        9
    +
    +#define HTML_BUTTON_ACCESSKEY     0
    +#define HTML_BUTTON_CLASS         1
    +#define HTML_BUTTON_CLEAR         2
    +#define HTML_BUTTON_DIR           3
    +#define HTML_BUTTON_DISABLED      4
    +#define HTML_BUTTON_FORMACTION    5
    +#define HTML_BUTTON_ID            6
    +#define HTML_BUTTON_LANG          7
    +#define HTML_BUTTON_NAME          8
    +#define HTML_BUTTON_ONBLUR        9
    +#define HTML_BUTTON_ONFOCUS      10
    +#define HTML_BUTTON_READONLY     11
    +#define HTML_BUTTON_STYLE        12
    +#define HTML_BUTTON_TABINDEX     13
    +#define HTML_BUTTON_TITLE        14
    +#define HTML_BUTTON_TYPE         15
    +#define HTML_BUTTON_VALUE        16
    +#define HTML_BUTTON_ATTRIBUTES   17
    +
    +#define HTML_CAPTION_ACCESSKEY    0
    +#define HTML_CAPTION_ALIGN        1
    +#define HTML_CAPTION_CLASS        2
    +#define HTML_CAPTION_CLEAR        3
    +#define HTML_CAPTION_DIR          4
    +#define HTML_CAPTION_ID           5
    +#define HTML_CAPTION_LANG         6
    +#define HTML_CAPTION_STYLE        7
    +#define HTML_CAPTION_TITLE        8
    +#define HTML_CAPTION_ATTRIBUTES   9
    +
    +#define HTML_COL_ALIGN            0
    +#define HTML_COL_CHAR             1
    +#define HTML_COL_CHAROFF          2
    +#define HTML_COL_CLASS            3
    +#define HTML_COL_CLEAR            4
    +#define HTML_COL_DIR              5
    +#define HTML_COL_ID               6
    +#define HTML_COL_LANG             7
    +#define HTML_COL_SPAN             8
    +#define HTML_COL_STYLE            9
    +#define HTML_COL_TITLE           10
    +#define HTML_COL_VALIGN          11
    +#define HTML_COL_WIDTH           12
    +#define HTML_COL_ATTRIBUTES      13
    +
    +#define HTML_DEL_CITE             0
    +#define HTML_DEL_CLASS            1
    +#define HTML_DEL_DATETIME         2
    +#define HTML_DEL_DIR              3
    +#define HTML_DEL_ID               4
    +#define HTML_DEL_LANG             5
    +#define HTML_DEL_STYLE            6
    +#define HTML_DEL_TITLE            7
    +#define HTML_DEL_ATTRIBUTES       8
    +
    +#define HTML_DIV_ALIGN            0
    +#define HTML_DIV_CLASS            1
    +#define HTML_DIV_CLEAR            2
    +#define HTML_DIV_DIR              3
    +#define HTML_DIV_ID               4
    +#define HTML_DIV_LANG             5
    +#define HTML_DIV_STYLE            6
    +#define HTML_DIV_TITLE            7
    +#define HTML_DIV_ATTRIBUTES       8
    +
    +#define HTML_DL_CLASS             0
    +#define HTML_DL_CLEAR             1
    +#define HTML_DL_COMPACT           2
    +#define HTML_DL_DIR               3
    +#define HTML_DL_ID                4
    +#define HTML_DL_LANG              5
    +#define HTML_DL_STYLE             6
    +#define HTML_DL_TITLE             7
    +#define HTML_DL_ATTRIBUTES        8
    +
    +#define HTML_EMBED_ALIGN          0
    +#define HTML_EMBED_ALT            1
    +#define HTML_EMBED_BORDER         2
    +#define HTML_EMBED_CLASS          3
    +#define HTML_EMBED_CLEAR          4
    +#define HTML_EMBED_DIR            5
    +#define HTML_EMBED_HEIGHT         6
    +#define HTML_EMBED_ID             7
    +#define HTML_EMBED_IMAGEMAP       8
    +#define HTML_EMBED_ISMAP          9
    +#define HTML_EMBED_LANG          10
    +#define HTML_EMBED_MD            11
    +#define HTML_EMBED_NAME          12
    +#define HTML_EMBED_NOFLOW        13
    +#define HTML_EMBED_PARAMS        14
    +#define HTML_EMBED_SRC           15
    +#define HTML_EMBED_STYLE         16
    +#define HTML_EMBED_TITLE         17
    +#define HTML_EMBED_UNITS         18
    +#define HTML_EMBED_USEMAP        19
    +#define HTML_EMBED_WIDTH         20
    +#define HTML_EMBED_ATTRIBUTES    21
    +
    +#define HTML_FIG_ALIGN            0
    +#define HTML_FIG_BORDER           1
    +#define HTML_FIG_CLASS            2
    +#define HTML_FIG_CLEAR            3
    +#define HTML_FIG_DIR              4
    +#define HTML_FIG_HEIGHT           5
    +#define HTML_FIG_ID               6
    +#define HTML_FIG_IMAGEMAP         7
    +#define HTML_FIG_ISOBJECT         8
    +#define HTML_FIG_LANG             9
    +#define HTML_FIG_MD              10
    +#define HTML_FIG_NOFLOW          11
    +#define HTML_FIG_SRC             12
    +#define HTML_FIG_STYLE           13
    +#define HTML_FIG_TITLE           14
    +#define HTML_FIG_UNITS           15
    +#define HTML_FIG_WIDTH           16
    +#define HTML_FIG_ATTRIBUTES      17
    +
    +#define HTML_FONT_CLASS           0
    +#define HTML_FONT_CLEAR           1
    +#define HTML_FONT_COLOR           2
    +#define HTML_FONT_DIR             3
    +#define HTML_FONT_END             4
    +#define HTML_FONT_FACE            5
    +#define HTML_FONT_ID              6
    +#define HTML_FONT_LANG            7
    +#define HTML_FONT_SIZE            8
    +#define HTML_FONT_STYLE           9
    +#define HTML_FONT_TITLE          10
    +#define HTML_FONT_ATTRIBUTES     11
    +
    +#define HTML_FORM_ACCEPT          0
    +#define HTML_FORM_ACCEPT_CHARSET  1
    +#define HTML_FORM_ACTION          2
    +#define HTML_FORM_CLASS           3
    +#define HTML_FORM_CLEAR           4
    +#define HTML_FORM_DIR             5
    +#define HTML_FORM_ENCTYPE         6
    +#define HTML_FORM_ID              7
    +#define HTML_FORM_LANG            8
    +#define HTML_FORM_METHOD          9
    +#define HTML_FORM_ONRESET        10
    +#define HTML_FORM_ONSUBMIT       11
    +#define HTML_FORM_SCRIPT         12
    +#define HTML_FORM_STYLE          13
    +#define HTML_FORM_SUBJECT        14
    +#define HTML_FORM_TARGET         15
    +#define HTML_FORM_TITLE          16
    +#define HTML_FORM_ATTRIBUTES     17
    +
    +#define HTML_FRAME_CLASS          0
    +#define HTML_FRAME_FRAMEBORDER    1
    +#define HTML_FRAME_ID             2
    +#define HTML_FRAME_LONGDESC       3
    +#define HTML_FRAME_MARGINHEIGHT   4
    +#define HTML_FRAME_MARGINWIDTH    5
    +#define HTML_FRAME_NAME           6
    +#define HTML_FRAME_NORESIZE       7
    +#define HTML_FRAME_SCROLLING      8
    +#define HTML_FRAME_SRC            9
    +#define HTML_FRAME_STYLE         10
    +#define HTML_FRAME_TITLE         11
    +#define HTML_FRAME_ATTRIBUTES    12
    +
    +#define HTML_FRAMESET_COLS        0
    +#define HTML_FRAMESET_ONLOAD      1
    +#define HTML_FRAMESET_ONUNLOAD    2
    +#define HTML_FRAMESET_ROWS        3
    +#define HTML_FRAMESET_ATTRIBUTES  4
    +
    +#define HTML_GEN_CLASS            0
    +#define HTML_GEN_CLEAR            1
    +#define HTML_GEN_DIR              2
    +#define HTML_GEN_ID               3
    +#define HTML_GEN_LANG             4
    +#define HTML_GEN_STYLE            5
    +#define HTML_GEN_TITLE            6
    +#define HTML_GEN_ATTRIBUTES       7
    +
    +#define HTML_GEN5_CLASS           0
    +#define HTML_GEN5_DIR             1
    +#define HTML_GEN5_ID              2
    +#define HTML_GEN5_LANG            3
    +#define HTML_GEN5_ROLE            4
    +#define HTML_GEN5_STYLE           5
    +#define HTML_GEN5_TITLE           6
    +#define HTML_GEN5_ATTRIBUTES      7
    +
    +#define HTML_H_ALIGN              0
    +#define HTML_H_CLASS              1
    +#define HTML_H_CLEAR              2
    +#define HTML_H_DINGBAT            3
    +#define HTML_H_DIR                4
    +#define HTML_H_ID                 5
    +#define HTML_H_LANG               6
    +#define HTML_H_MD                 7
    +#define HTML_H_NOWRAP             8
    +#define HTML_H_SEQNUM             9
    +#define HTML_H_SKIP              10
    +#define HTML_H_SRC               11
    +#define HTML_H_STYLE             12
    +#define HTML_H_TITLE             13
    +#define HTML_H_ATTRIBUTES        14
    +
    +#define HTML_HR_ALIGN             0
    +#define HTML_HR_CLASS             1
    +#define HTML_HR_CLEAR             2
    +#define HTML_HR_DIR               3
    +#define HTML_HR_ID                4
    +#define HTML_HR_LANG              5
    +#define HTML_HR_MD                6
    +#define HTML_HR_NOSHADE           7
    +#define HTML_HR_SIZE              8
    +#define HTML_HR_SRC               9
    +#define HTML_HR_STYLE            10
    +#define HTML_HR_TITLE            11
    +#define HTML_HR_WIDTH            12
    +#define HTML_HR_ATTRIBUTES       13
    +
    +#define HTML_IFRAME_ALIGN         0
    +#define HTML_IFRAME_CLASS         1
    +#define HTML_IFRAME_FRAMEBORDER   2
    +#define HTML_IFRAME_HEIGHT        3
    +#define HTML_IFRAME_ID            4
    +#define HTML_IFRAME_LONGDESC      5
    +#define HTML_IFRAME_MARGINHEIGHT  6
    +#define HTML_IFRAME_MARGINWIDTH   7
    +#define HTML_IFRAME_NAME          8
    +#define HTML_IFRAME_SCROLLING     9
    +#define HTML_IFRAME_SRC          10
    +#define HTML_IFRAME_STYLE        11
    +#define HTML_IFRAME_TITLE        12
    +#define HTML_IFRAME_WIDTH        13
    +#define HTML_IFRAME_ATTRIBUTES   14
    +
    +#define HTML_IMG_ALIGN            0
    +#define HTML_IMG_ALT              1
    +#define HTML_IMG_BORDER           2
    +#define HTML_IMG_CLASS            3
    +#define HTML_IMG_CLEAR            4
    +#define HTML_IMG_DIR              5
    +#define HTML_IMG_HEIGHT           6
    +#define HTML_IMG_HSPACE           7
    +#define HTML_IMG_ID               8
    +#define HTML_IMG_ISMAP            9
    +#define HTML_IMG_ISOBJECT        10
    +#define HTML_IMG_LANG            11
    +#define HTML_IMG_LONGDESC        12
    +#define HTML_IMG_MD              13
    +#define HTML_IMG_NAME            14
    +#define HTML_IMG_SRC             15
    +#define HTML_IMG_STYLE           16
    +#define HTML_IMG_TITLE           17
    +#define HTML_IMG_UNITS           18
    +#define HTML_IMG_USEMAP          19
    +#define HTML_IMG_VSPACE          20
    +#define HTML_IMG_WIDTH           21
    +#define HTML_IMG_ATTRIBUTES      22
    +
    +#define HTML_INPUT_ACCEPT         0
    +#define HTML_INPUT_ACCEPT_CHARSET 1
    +#define HTML_INPUT_ACCESSKEY      2
    +#define HTML_INPUT_ALIGN          3
    +#define HTML_INPUT_ALT            4
    +#define HTML_INPUT_CHECKED        5
    +#define HTML_INPUT_CLASS          6
    +#define HTML_INPUT_CLEAR          7
    +#define HTML_INPUT_DIR            8
    +#define HTML_INPUT_DISABLED       9
    +#define HTML_INPUT_ERROR         10
    +#define HTML_INPUT_HEIGHT        11
    +#define HTML_INPUT_ID            12
    +#define HTML_INPUT_ISMAP         13
    +#define HTML_INPUT_LANG          14
    +#define HTML_INPUT_MAX           15
    +#define HTML_INPUT_MAXLENGTH     16
    +#define HTML_INPUT_MD            17
    +#define HTML_INPUT_MIN           18
    +#define HTML_INPUT_NAME          19
    +#define HTML_INPUT_NOTAB         20
    +#define HTML_INPUT_ONBLUR        21
    +#define HTML_INPUT_ONCHANGE      22
    +#define HTML_INPUT_ONFOCUS       23
    +#define HTML_INPUT_ONSELECT      24
    +#define HTML_INPUT_READONLY      25
    +#define HTML_INPUT_SIZE          26
    +#define HTML_INPUT_SRC           27
    +#define HTML_INPUT_STYLE         28
    +#define HTML_INPUT_TABINDEX      29
    +#define HTML_INPUT_TITLE         30
    +#define HTML_INPUT_TYPE          31
    +#define HTML_INPUT_USEMAP        32
    +#define HTML_INPUT_VALUE         33
    +#define HTML_INPUT_WIDTH         34
    +#define HTML_INPUT_ATTRIBUTES    35
    +
    +#define HTML_ISINDEX_ACTION       0
    +#define HTML_ISINDEX_CLASS        1
    +#define HTML_ISINDEX_DIR          2
    +#define HTML_ISINDEX_HREF         3
    +#define HTML_ISINDEX_ID           4
    +#define HTML_ISINDEX_LANG         5
    +#define HTML_ISINDEX_PROMPT       6
    +#define HTML_ISINDEX_STYLE        7
    +#define HTML_ISINDEX_TITLE        8
    +#define HTML_ISINDEX_ATTRIBUTES   9
    +
    +#define HTML_KEYGEN_CHALLENGE     0
    +#define HTML_KEYGEN_CLASS         1
    +#define HTML_KEYGEN_DIR           2
    +#define HTML_KEYGEN_ID            3
    +#define HTML_KEYGEN_LANG          4
    +#define HTML_KEYGEN_NAME          5
    +#define HTML_KEYGEN_STYLE         6
    +#define HTML_KEYGEN_TITLE         7
    +#define HTML_KEYGEN_ATTRIBUTES    8
    +
    +#define HTML_LABEL_ACCESSKEY      0
    +#define HTML_LABEL_CLASS          1
    +#define HTML_LABEL_CLEAR          2
    +#define HTML_LABEL_DIR            3
    +#define HTML_LABEL_FOR            4
    +#define HTML_LABEL_ID             5
    +#define HTML_LABEL_LANG           6
    +#define HTML_LABEL_ONBLUR         7
    +#define HTML_LABEL_ONFOCUS        8
    +#define HTML_LABEL_STYLE          9
    +#define HTML_LABEL_TITLE         10
    +#define HTML_LABEL_ATTRIBUTES    11
    +
    +#define HTML_LI_CLASS             0
    +#define HTML_LI_CLEAR             1
    +#define HTML_LI_DINGBAT           2
    +#define HTML_LI_DIR               3
    +#define HTML_LI_ID                4
    +#define HTML_LI_LANG              5
    +#define HTML_LI_MD                6
    +#define HTML_LI_SKIP              7
    +#define HTML_LI_SRC               8
    +#define HTML_LI_STYLE             9
    +#define HTML_LI_TITLE            10
    +#define HTML_LI_TYPE             11
    +#define HTML_LI_VALUE            12
    +#define HTML_LI_ATTRIBUTES       13
    +
    +#define HTML_LINK_CHARSET         0
    +#define HTML_LINK_CLASS           1
    +#define HTML_LINK_DIR             2
    +#define HTML_LINK_HREF            3
    +#define HTML_LINK_HREFLANG        4
    +#define HTML_LINK_ID              5
    +#define HTML_LINK_LANG            6
    +#define HTML_LINK_MEDIA           7
    +#define HTML_LINK_REL             8
    +#define HTML_LINK_REV             9
    +#define HTML_LINK_STYLE          10
    +#define HTML_LINK_TARGET         11
    +#define HTML_LINK_TITLE          12
    +#define HTML_LINK_TYPE           13
    +#define HTML_LINK_ATTRIBUTES     14
    +
    +#define HTML_MAP_CLASS            0
    +#define HTML_MAP_CLEAR            1
    +#define HTML_MAP_DIR              2
    +#define HTML_MAP_ID               3
    +#define HTML_MAP_LANG             4
    +#define HTML_MAP_NAME             5
    +#define HTML_MAP_STYLE            6
    +#define HTML_MAP_TITLE            7
    +#define HTML_MAP_ATTRIBUTES       8
    +
    +#define HTML_MATH_BOX             0
    +#define HTML_MATH_CLASS           1
    +#define HTML_MATH_CLEAR           2
    +#define HTML_MATH_DIR             3
    +#define HTML_MATH_ID              4
    +#define HTML_MATH_LANG            5
    +#define HTML_MATH_STYLE           6
    +#define HTML_MATH_TITLE           7
    +#define HTML_MATH_ATTRIBUTES      8
    +
    +#define HTML_META_CHARSET         0
    +#define HTML_META_CONTENT         1
    +#define HTML_META_HTTP_EQUIV      2
    +#define HTML_META_NAME            3
    +#define HTML_META_SCHEME          4
    +#define HTML_META_ATTRIBUTES      5
    +
    +#define HTML_NEXTID_N             0
    +#define HTML_NEXTID_ATTRIBUTES    1
    +
    +#define HTML_NOTE_CLASS           0
    +#define HTML_NOTE_CLEAR           1
    +#define HTML_NOTE_DIR             2
    +#define HTML_NOTE_ID              3
    +#define HTML_NOTE_LANG            4
    +#define HTML_NOTE_MD              5
    +#define HTML_NOTE_ROLE            6
    +#define HTML_NOTE_SRC             7
    +#define HTML_NOTE_STYLE           8
    +#define HTML_NOTE_TITLE           9
    +#define HTML_NOTE_ATTRIBUTES     10
    +
    +#define HTML_OBJECT_ALIGN         0
    +#define HTML_OBJECT_ARCHIVE       1
    +#define HTML_OBJECT_BORDER        2
    +#define HTML_OBJECT_CLASS         3
    +#define HTML_OBJECT_CLASSID       4
    +#define HTML_OBJECT_CODEBASE      5
    +#define HTML_OBJECT_CODETYPE      6
    +#define HTML_OBJECT_DATA          7
    +#define HTML_OBJECT_DECLARE       8
    +#define HTML_OBJECT_DIR           9
    +#define HTML_OBJECT_HEIGHT       10
    +#define HTML_OBJECT_HSPACE       11
    +#define HTML_OBJECT_ID           12
    +#define HTML_OBJECT_ISMAP        13
    +#define HTML_OBJECT_LANG         14
    +#define HTML_OBJECT_NAME         15
    +#define HTML_OBJECT_NOTAB        16
    +#define HTML_OBJECT_SHAPES       17
    +#define HTML_OBJECT_STANDBY      18
    +#define HTML_OBJECT_STYLE        19
    +#define HTML_OBJECT_TABINDEX     20
    +#define HTML_OBJECT_TITLE        21
    +#define HTML_OBJECT_TYPE         22
    +#define HTML_OBJECT_USEMAP       23
    +#define HTML_OBJECT_VSPACE       24
    +#define HTML_OBJECT_WIDTH        25
    +#define HTML_OBJECT_ATTRIBUTES   26
    +
    +#define HTML_OL_CLASS             0
    +#define HTML_OL_CLEAR             1
    +#define HTML_OL_COMPACT           2
    +#define HTML_OL_CONTINUE          3
    +#define HTML_OL_DIR               4
    +#define HTML_OL_ID                5
    +#define HTML_OL_LANG              6
    +#define HTML_OL_SEQNUM            7
    +#define HTML_OL_START             8
    +#define HTML_OL_STYLE             9
    +#define HTML_OL_TITLE            10
    +#define HTML_OL_TYPE             11
    +#define HTML_OL_ATTRIBUTES       12
    +
    +#define HTML_OPTION_CLASS         0
    +#define HTML_OPTION_CLEAR         1
    +#define HTML_OPTION_DIR           2
    +#define HTML_OPTION_DISABLED      3
    +#define HTML_OPTION_ERROR         4
    +#define HTML_OPTION_ID            5
    +#define HTML_OPTION_LABEL         6
    +#define HTML_OPTION_LANG          7
    +#define HTML_OPTION_SELECTED      8
    +#define HTML_OPTION_SHAPE         9
    +#define HTML_OPTION_STYLE        10
    +#define HTML_OPTION_TITLE        11
    +#define HTML_OPTION_VALUE        12
    +#define HTML_OPTION_ATTRIBUTES   13
    +
    +#define HTML_OVERLAY_CLASS        0
    +#define HTML_OVERLAY_HEIGHT       1
    +#define HTML_OVERLAY_ID           2
    +#define HTML_OVERLAY_IMAGEMAP     3
    +#define HTML_OVERLAY_MD           4
    +#define HTML_OVERLAY_SRC          5
    +#define HTML_OVERLAY_STYLE        6
    +#define HTML_OVERLAY_TITLE        7
    +#define HTML_OVERLAY_UNITS        8
    +#define HTML_OVERLAY_WIDTH        9
    +#define HTML_OVERLAY_X           10
    +#define HTML_OVERLAY_Y           11
    +#define HTML_OVERLAY_ATTRIBUTES  12
    +
    +#define HTML_P_ALIGN              0
    +#define HTML_P_CLASS              1
    +#define HTML_P_CLEAR              2
    +#define HTML_P_DIR                3
    +#define HTML_P_ID                 4
    +#define HTML_P_LANG               5
    +#define HTML_P_NOWRAP             6
    +#define HTML_P_STYLE              7
    +#define HTML_P_TITLE              8
    +#define HTML_P_ATTRIBUTES         9
    +
    +#define HTML_PARAM_ACCEPT         0
    +#define HTML_PARAM_ACCEPT_CHARSET 1
    +#define HTML_PARAM_ACCEPT_ENCODING 2
    +#define HTML_PARAM_CLASS          3
    +#define HTML_PARAM_CLEAR          4
    +#define HTML_PARAM_DATA           5
    +#define HTML_PARAM_DIR            6
    +#define HTML_PARAM_ID             7
    +#define HTML_PARAM_LANG           8
    +#define HTML_PARAM_NAME           9
    +#define HTML_PARAM_OBJECT        10
    +#define HTML_PARAM_REF           11
    +#define HTML_PARAM_STYLE         12
    +#define HTML_PARAM_TITLE         13
    +#define HTML_PARAM_TYPE          14
    +#define HTML_PARAM_VALUE         15
    +#define HTML_PARAM_VALUEREF      16
    +#define HTML_PARAM_VALUETYPE     17
    +#define HTML_PARAM_ATTRIBUTES    18
    +
    +#define HTML_Q_CITE               0
    +#define HTML_Q_CLASS              1
    +#define HTML_Q_CLEAR              2
    +#define HTML_Q_DIR                3
    +#define HTML_Q_ID                 4
    +#define HTML_Q_LANG               5
    +#define HTML_Q_STYLE              6
    +#define HTML_Q_TITLE              7
    +#define HTML_Q_ATTRIBUTES         8
    +
    +#define HTML_SCRIPT_CHARSET       0
    +#define HTML_SCRIPT_CLASS         1
    +#define HTML_SCRIPT_CLEAR         2
    +#define HTML_SCRIPT_DEFER         3
    +#define HTML_SCRIPT_DIR           4
    +#define HTML_SCRIPT_EVENT         5
    +#define HTML_SCRIPT_FOR           6
    +#define HTML_SCRIPT_ID            7
    +#define HTML_SCRIPT_LANG          8
    +#define HTML_SCRIPT_LANGUAGE      9
    +#define HTML_SCRIPT_NAME         10
    +#define HTML_SCRIPT_SCRIPTENGINE 11
    +#define HTML_SCRIPT_SRC          12
    +#define HTML_SCRIPT_STYLE        13
    +#define HTML_SCRIPT_TITLE        14
    +#define HTML_SCRIPT_TYPE         15
    +#define HTML_SCRIPT_ATTRIBUTES   16
    +
    +#define HTML_SELECT_ALIGN         0
    +#define HTML_SELECT_CLASS         1
    +#define HTML_SELECT_CLEAR         2
    +#define HTML_SELECT_DIR           3
    +#define HTML_SELECT_DISABLED      4
    +#define HTML_SELECT_ERROR         5
    +#define HTML_SELECT_HEIGHT        6
    +#define HTML_SELECT_ID            7
    +#define HTML_SELECT_LANG          8
    +#define HTML_SELECT_MD            9
    +#define HTML_SELECT_MULTIPLE     10
    +#define HTML_SELECT_NAME         11
    +#define HTML_SELECT_NOTAB        12
    +#define HTML_SELECT_ONBLUR       13
    +#define HTML_SELECT_ONCHANGE     14
    +#define HTML_SELECT_ONFOCUS      15
    +#define HTML_SELECT_SIZE         16
    +#define HTML_SELECT_STYLE        17
    +#define HTML_SELECT_TABINDEX     18
    +#define HTML_SELECT_TITLE        19
    +#define HTML_SELECT_UNITS        20
    +#define HTML_SELECT_WIDTH        21
    +#define HTML_SELECT_ATTRIBUTES   22
    +
    +#define HTML_STYLE_CLASS          0
    +#define HTML_STYLE_DIR            1
    +#define HTML_STYLE_ID             2
    +#define HTML_STYLE_LANG           3
    +#define HTML_STYLE_MEDIA          4
    +#define HTML_STYLE_NOTATION       5
    +#define HTML_STYLE_STYLE          6
    +#define HTML_STYLE_TITLE          7
    +#define HTML_STYLE_TYPE           8
    +#define HTML_STYLE_ATTRIBUTES     9
    +
    +#define HTML_TAB_ALIGN            0
    +#define HTML_TAB_CLASS            1
    +#define HTML_TAB_CLEAR            2
    +#define HTML_TAB_DIR              3
    +#define HTML_TAB_DP               4
    +#define HTML_TAB_ID               5
    +#define HTML_TAB_INDENT           6
    +#define HTML_TAB_LANG             7
    +#define HTML_TAB_STYLE            8
    +#define HTML_TAB_TITLE            9
    +#define HTML_TAB_TO              10
    +#define HTML_TAB_ATTRIBUTES      11
    +
    +#define HTML_TABLE_ALIGN          0
    +#define HTML_TABLE_BACKGROUND     1
    +#define HTML_TABLE_BORDER         2
    +#define HTML_TABLE_CELLPADDING    3
    +#define HTML_TABLE_CELLSPACING    4
    +#define HTML_TABLE_CLASS          5
    +#define HTML_TABLE_CLEAR          6
    +#define HTML_TABLE_COLS           7
    +#define HTML_TABLE_COLSPEC        8
    +#define HTML_TABLE_DIR            9
    +#define HTML_TABLE_DP            10
    +#define HTML_TABLE_FRAME         11
    +#define HTML_TABLE_ID            12
    +#define HTML_TABLE_LANG          13
    +#define HTML_TABLE_NOFLOW        14
    +#define HTML_TABLE_NOWRAP        15
    +#define HTML_TABLE_RULES         16
    +#define HTML_TABLE_STYLE         17
    +#define HTML_TABLE_SUMMARY       18
    +#define HTML_TABLE_TITLE         19
    +#define HTML_TABLE_UNITS         20
    +#define HTML_TABLE_WIDTH         21
    +#define HTML_TABLE_ATTRIBUTES    22
    +
    +#define HTML_TD_ABBR              0
    +#define HTML_TD_ALIGN             1
    +#define HTML_TD_AXES              2
    +#define HTML_TD_AXIS              3
    +#define HTML_TD_BACKGROUND        4
    +#define HTML_TD_CHAR              5
    +#define HTML_TD_CHAROFF           6
    +#define HTML_TD_CLASS             7
    +#define HTML_TD_CLEAR             8
    +#define HTML_TD_COLSPAN           9
    +#define HTML_TD_DIR              10
    +#define HTML_TD_DP               11
    +#define HTML_TD_HEADERS          12
    +#define HTML_TD_HEIGHT           13
    +#define HTML_TD_ID               14
    +#define HTML_TD_LANG             15
    +#define HTML_TD_NOWRAP           16
    +#define HTML_TD_ROWSPAN          17
    +#define HTML_TD_SCOPE            18
    +#define HTML_TD_STYLE            19
    +#define HTML_TD_TITLE            20
    +#define HTML_TD_VALIGN           21
    +#define HTML_TD_WIDTH            22
    +#define HTML_TD_ATTRIBUTES       23
    +
    +#define HTML_TEXTAREA_ACCEPT_CHARSET 0
    +#define HTML_TEXTAREA_ACCESSKEY   1
    +#define HTML_TEXTAREA_ALIGN       2
    +#define HTML_TEXTAREA_CLASS       3
    +#define HTML_TEXTAREA_CLEAR       4
    +#define HTML_TEXTAREA_COLS        5
    +#define HTML_TEXTAREA_DIR         6
    +#define HTML_TEXTAREA_DISABLED    7
    +#define HTML_TEXTAREA_ERROR       8
    +#define HTML_TEXTAREA_ID          9
    +#define HTML_TEXTAREA_LANG       10
    +#define HTML_TEXTAREA_NAME       11
    +#define HTML_TEXTAREA_NOTAB      12
    +#define HTML_TEXTAREA_ONBLUR     13
    +#define HTML_TEXTAREA_ONCHANGE   14
    +#define HTML_TEXTAREA_ONFOCUS    15
    +#define HTML_TEXTAREA_ONSELECT   16
    +#define HTML_TEXTAREA_READONLY   17
    +#define HTML_TEXTAREA_ROWS       18
    +#define HTML_TEXTAREA_STYLE      19
    +#define HTML_TEXTAREA_TABINDEX   20
    +#define HTML_TEXTAREA_TITLE      21
    +#define HTML_TEXTAREA_ATTRIBUTES 22
    +
    +#define HTML_TR_ALIGN             0
    +#define HTML_TR_CHAR              1
    +#define HTML_TR_CHAROFF           2
    +#define HTML_TR_CLASS             3
    +#define HTML_TR_CLEAR             4
    +#define HTML_TR_DIR               5
    +#define HTML_TR_DP                6
    +#define HTML_TR_ID                7
    +#define HTML_TR_LANG              8
    +#define HTML_TR_NOWRAP            9
    +#define HTML_TR_STYLE            10
    +#define HTML_TR_TITLE            11
    +#define HTML_TR_VALIGN           12
    +#define HTML_TR_ATTRIBUTES       13
    +
    +#define HTML_UL_CLASS             0
    +#define HTML_UL_CLEAR             1
    +#define HTML_UL_COMPACT           2
    +#define HTML_UL_DINGBAT           3
    +#define HTML_UL_DIR               4
    +#define HTML_UL_ID                5
    +#define HTML_UL_LANG              6
    +#define HTML_UL_MD                7
    +#define HTML_UL_PLAIN             8
    +#define HTML_UL_SRC               9
    +#define HTML_UL_STYLE            10
    +#define HTML_UL_TITLE            11
    +#define HTML_UL_TYPE             12
    +#define HTML_UL_WRAP             13
    +#define HTML_UL_ATTRIBUTES       14
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +#endif				/* hdr_HTMLDTD_H */
    diff --git a/WWW/Library/Implementation/makefile.in b/WWW/Library/Implementation/makefile.in
    new file mode 100644
    index 0000000..0b467d7
    --- /dev/null
    +++ b/WWW/Library/Implementation/makefile.in
    @@ -0,0 +1,384 @@
    +# $LynxId: makefile.in,v 1.35 2021/07/16 20:01:00 tom Exp $
    +# Make WWW under unix for a.n.other unix system (bsd)
    +# Use this as a template
    +
    +# For W3 distribution, machine type for subdirectories
    +WWW_MACH	= Implementation
    +WWWINC		= $(top_srcdir)/WWW/Library/Implementation
    +
    +ECHO =		@DONT_ECHO_CC@
    +LFLAGS =
    +
    +prefix		= @prefix@
    +exec_prefix	= @exec_prefix@
    +datarootdir	= @datarootdir@
    +top_srcdir	= @top_srcdir@
    +srcdir		= @srcdir@
    +VPATH		= $(srcdir)
    +
    +LYFLAGS		= # FIXME: set in parent makefile
    +
    +CC		= @CC@
    +DEFS		= @DEFS@
    +EXTRA_CPPFLAGS	= @EXTRA_CPPFLAGS@
    +CPPFLAGS	= @CPPFLAGS@
    +
    +AR		= @AR@
    +ARFLAGS		= @ARFLAGS@
    +
    +RANLIB		= @RANLIB@
    +
    +o		= .@OBJEXT@
    +x		= @EXEEXT@
    +
    +INTLDIR_CPPFLAGS= @INTLDIR_CPPFLAGS@ -I$(top_srcdir)/intl
    +
    +CPP		= @CPP@
    +CPPOPTS		= $(DEFS) $(LYFLAGS) \
    +		-I../../.. \
    +		-I../../../src \
    +		-I$(top_srcdir) \
    +		-I$(top_srcdir)/src \
    +		$(INTLDIR_CPPFLAGS) -I$(WWWINC) $(EXTRA_CPPFLAGS) $(CPPFLAGS)
    +
    +LY_CFLAGS	= @CFLAGS@ @EXTRA_CFLAGS@
    +CFLAGS		= $(CPPOPTS) $(LY_CFLAGS)
    +
    +LINT		= @LINT@
    +LINTOPTS	=
    +
    +CTAGS		= @CTAGS@
    +
    +# Directory for installed binary:
    +BINDIR		= @bindir@
    +
    +# Where is the W3 object library to be installed (not normally done)?
    +#_________________ OK if normal W3 distribution
    +# Where is the WWW source root?
    +WWW = $(top_srcdir)/WWW
    +
    +#  Where should temporary (object) files go?
    +WTMP = ../..
    +
    +# (Version.make)
    +VC = 2.14
    +#______________________________________________________________________
    +# (originally CommonMakefile)
    +
    +#  If this env var is set to something else Some makes will use that instead
    +SHELL = @CONFIG_SHELL@
    +
    +#	.h files are distributed but originally are made from the
    +#	self-documenting hypertext files.
    +
    +.SUFFIXES: .h .html
    +.html.h:
    +#	- chmod +w $*.h
    +	www -w90 -na -to text/x-c $*.html > $*.h
    +#	chmod -w $*.h
    +
    +# If this is actually run in a subdirectory,
    +#
    +# WWW = ../../..
    +# WWW = ../..	For [cernlib] build in this directory
    +
    +CMN = $(WWW)/Library/Implementation/
    +
    +# Where shall we put the objects and built library?
    +
    +LOB = .
    +
    +# Only needed if HTWAIS.c is to be compiled. Put into your Makefile.include
    +#  uncomment these and fill in WAISINC for adding direct wais access
    +#  to Lynx.
    +@MAKE_WAIS@HTWAIS_c = $(CMN)/HTWAIS.c
    +@MAKE_WAIS@HTWAIS_o = $(LOB)/HTWAIS$o
    +@MAKE_WAIS@WAIS = YES
    +#WAISINC = -I../../../../freeWAIS-0.202/ir
    +@MAKE_WAIS@WAISCFLAGS = -DDIRECT_WAIS
    +#
    +
    +# add -DNEW_GATEWAY here for the new gateway config stuff
    +CFLAGS2 = $(CFLAGS) $(LYFLAGS) $(WAISCFLAGS) -I$(CMN) -DACCESS_AUTH
    +
    +COMPILE	= $(ECHO) $(CC) $(CFLAGS2) -c
    +
    +COMMON = $(LOB)/HTParse$o $(LOB)/HTAccess$o $(LOB)/HTTP$o \
    +	$(LOB)/HTFile$o	$(LOB)/HTBTree$o $(LOB)/HTFTP$o $(LOB)/HTTCP$o \
    +	$(LOB)/SGML$o $(LOB)/HTMLDTD$o $(LOB)/HTChunk$o \
    +	$(LOB)/HTPlain$o \
    +	$(LOB)/HTMLGen$o \
    +	$(LOB)/HTAtom$o $(LOB)/HTAnchor$o $(LOB)/HTStyle$o \
    +	$(LOB)/HTList$o $(LOB)/HTString$o \
    +	$(LOB)/HTRules$o $(LOB)/HTFormat$o  $(LOB)/HTMIME$o \
    +	$(LOB)/HTNews$o  $(LOB)/HTGopher$o \
    +	$(LOB)/HTTelnet$o $(LOB)/HTFinger$o $(LOB)/HTWSRC$o $(HTWAIS_o) \
    +	$(LOB)/HTAAUtil$o $(LOB)/HTAABrow$o \
    +	$(LOB)/HTGroup$o \
    +	$(LOB)/HTAAProt$o \
    +	$(LOB)/HTAssoc$o  $(LOB)/HTLex$o    $(LOB)/HTUU$o \
    +	$(LOB)/HTDOS$o
    +
    +CFILES = $(CMN)HTParse.c $(CMN)HTAccess.c $(CMN)HTTP.c $(CMN)HTFile.c \
    +	$(CMN)HTBTree.c \
    +	$(CMN)HTFTP.c	$(CMN)HTTCP.c	  $(CMN)SGML.c \
    +	$(CMN)HTMLDTD.c \
    +	$(CMN)HTPlain.c	\
    +	$(CMN)HTMLGen.c \
    +	$(CMN)HTChunk.c $(CMN)HTAtom.c    $(CMN)HTAnchor.c $(CMN)HTStyle.c \
    +	$(CMN)HTList.c  $(CMN)HTString.c  $(CMN)HTRules.c \
    +	$(CMN)HTFormat.c $(CMN)HTMIME.c \
    +	$(CMN)HTNews.c  $(CMN)HTGopher.c  $(CMN)HTTelnet.c \
    +	$(CMN)HTFinger.c $(HTWAIS_c)	  $(CMN)HTWSRC.c \
    +	$(CMN)HTAABrow.c \
    +	$(CMN)HTGroup.c  \
    +	$(CMN)HTAAProt.c \
    +	$(CMN)HTAssoc.c  $(CMN)HTLex.c    $(CMN)HTUU.c
    +
    +HFILES = $(CMN)HTParse.h $(CMN)HTAccess.h $(CMN)HTTP.h $(CMN)HTFile.h \
    +	$(CMN)HTBTree.h $(CMN)HTFTP.h $(CMN)HTTCP.h \
    +	$(CMN)SGML.h $(CMN)HTML.h $(CMN)HTMLDTD.h $(CMN)HTChunk.h \
    +	$(CMN)HTPlain.h	\
    +	$(CMN)HTFWriter.h	$(CMN)HTMLGen.h \
    +	$(CMN)HTStream.h \
    +	$(CMN)HTAtom.h $(CMN)HTAnchor.h $(CMN)HTStyle.h \
    +	$(CMN)HTList.h \
    +	$(CMN)HTString.h $(CMN)HTRules.h \
    +	$(CMN)HTFormat.h $(CMN)HTInit.h \
    +	$(CMN)HTMIME.h $(CMN)HTNews.h \
    +	$(CMN)HTGopher.h \
    +	$(CMN)HTUtils.h $(CMN)www_tcp.h $(CMN)HText.h \
    +	$(CMN)HTTelnet.h $(CMN)HTFinger.h \
    +	$(CMN)HTWAIS.h  $(CMN)HTWSRC.h \
    +	$(CMN)HTAABrow.h \
    +	$(CMN)HTGroup.h  \
    +	$(CMN)HTAAProt.h \
    +	$(CMN)HTAssoc.h  $(CMN)HTLex.h    $(CMN)HTUU.h
    +
    +C_SRC		= $(COMMON:$o=.c)
    +
    +all : $(LOB)/libwww.a
    +
    +lint:
    +	$(LINT) $(LINTOPTS) $(CPPOPTS) $(C_SRC) 2>&1 |tee ../../../lint.libwww
    +
    +.SUFFIXES: $o .i .h .html
    +
    +.c$o:
    +	@RULE_CC@
    +	@ECHO_CC@$(CC) $(CPPOPTS) $(CFLAGS) -c $(srcdir)/$*.c
    +
    +.c.i:
    +	@RULE_CC@
    +	@ECHO_CC@$(CPP) $(CPPOPTS) $(srcdir)/$*.c >$@
    +
    +depend :
    +	makedepend -fmakefile -- $(CFLAGS) -- $(CFILES)
    +
    +#	Library
    +#
    +#  On SGI, ranlib is unnecessary and does not exist so we ignore errors
    +# for that step
    +$(LOB)/libwww.a : $(COMMON)
    +	$(AR) $(ARFLAGS) $(LOB)/libwww.a $(COMMON)
    +	-$(RANLIB) $(LOB)/libwww.a
    +
    +#	Clean up everything generatable except final products
    +clean :
    +	rm -f core *.core *.leaks *.[oi] *.bak tags TAGS
    +	rm -f dtd_util$x
    +	rm -f $(LOB)/*$o
    +
    +distclean : clean
    +	@echo made $@
    +
    +tags:
    +	$(CTAGS) *.[ch]
    +
    +#			Common code
    +#			-----------
    +
    +$(LOB)/HTList$o : $(CMN)HTList.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTList.c
    +
    +$(LOB)/HTAnchor$o : $(CMN)HTAnchor.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTAnchor.c
    +
    +$(LOB)/HTFormat$o : $(CMN)HTFormat.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTFormat.c
    +
    +$(LOB)/HTMIME$o : $(CMN)HTMIME.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTMIME.c
    +
    +$(LOB)/HTNews$o : $(CMN)HTNews.c $(CMN)HTUtils.h $(CMN)HTList.h\
    +	 $(CMN)HTMLDTD.h
    +	$(COMPILE) $(CMN)HTNews.c
    +
    +$(LOB)/HTGopher$o : $(CMN)HTGopher.c $(CMN)HTUtils.h $(CMN)HTList.h \
    +	 $(CMN)HTMLDTD.h
    +	$(COMPILE) $(CMN)HTGopher.c
    +
    +$(LOB)/HTTelnet$o : $(CMN)HTTelnet.c $(CMN)HTUtils.h $(CMN)HTTelnet.h $(CMN)../../../userdefs.h
    +	$(COMPILE) $(CMN)HTTelnet.c
    +
    +$(LOB)/HTFinger$o : $(CMN)HTFinger.c $(CMN)HTUtils.h $(CMN)HTList.h \
    +	$(CMN)HTMLDTD.h
    +	$(COMPILE) $(CMN)HTFinger.c
    +
    +$(LOB)/HTStyle$o : $(CMN)HTStyle.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTStyle.c
    +
    +$(LOB)/HTAtom$o : $(CMN)HTAtom.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTAtom.c
    +
    +$(LOB)/HTChunk$o : $(CMN)HTChunk.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTChunk.c
    +
    +$(LOB)/HTString$o : $(CMN)HTString.c $(CMN)HTUtils.h $(CMN)Version.make
    +	$(COMPILE) -DVC=\"$(VC)\" $(CMN)HTString.c
    +
    +$(LOB)/HTRules$o : $(CMN)HTRules.c $(CMN)HTUtils.h $(CMN)Version.make \
    +	 $(CMN)HTAAProt.h
    +	$(COMPILE) -DVC=\"$(VC)\" $(CMN)HTRules.c
    +
    +$(LOB)/SGML$o : $(CMN)SGML.c $(CMN)HTUtils.h $(CMN)UCAux.h
    +	$(COMPILE) $(CMN)SGML.c
    +
    +$(LOB)/HTMLGen$o : $(CMN)HTMLGen.c $(CMN)HTUtils.h $(CMN)HTMLDTD.h
    +	$(COMPILE) $(CMN)HTMLGen.c
    +
    +$(LOB)/HTMLDTD$o : $(CMN)HTMLDTD.c $(CMN)SGML.h
    +	$(COMPILE) $(CMN)HTMLDTD.c
    +
    +$(LOB)/HTPlain$o : $(CMN)HTPlain.c $(CMN)HTPlain.h $(CMN)HTStream.h \
    +	 $(CMN)UCAux.h
    +	$(COMPILE) $(CMN)HTPlain.c
    +
    +$(LOB)/HTWAIS$o : $(CMN)HTWAIS.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(WAISINC) $(CMN)HTWAIS.c
    +
    +$(LOB)/HTWSRC$o : $(CMN)HTWSRC.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTWSRC.c
    +
    +#	Access Authorization
    +
    +$(LOB)/HTAAUtil$o : $(CMN)HTAAUtil.c $(CMN)HTAAUtil.h \
    +	 $(CMN)HTUtils.h $(CMN)HTString.h
    +	$(COMPILE) $(CMN)HTAAUtil.c
    +
    +$(LOB)/HTGroup$o : $(CMN)HTGroup.c $(CMN)HTGroup.h \
    +	 $(CMN)HTAAUtil.h \
    +	 $(CMN)HTAssoc.h $(CMN)HTLex.h
    +	$(COMPILE) $(CMN)HTGroup.c
    +
    +$(LOB)/HTAABrow$o : $(CMN)HTAABrow.c $(CMN)HTAABrow.h \
    +	 $(CMN)HTAAUtil.h $(CMN)HTUU.h \
    +	 $(CMN)HTUtils.h $(CMN)HTString.h \
    +	 $(CMN)HTParse.h $(CMN)HTList.h \
    +	 $(CMN)HTAssoc.h
    +	$(COMPILE) $(CMN)HTAABrow.c
    +
    +$(LOB)/HTAAProt$o : $(CMN)HTAAProt.c $(CMN)HTAAProt.h \
    +	 $(CMN)HTUtils.h $(CMN)HTAAUtil.h \
    +	 $(CMN)HTAssoc.h $(CMN)HTLex.h
    +	$(COMPILE) $(CMN)HTAAProt.c
    +
    +$(LOB)/HTAssoc$o : $(CMN)HTAssoc.c $(CMN)HTAssoc.h \
    +	$(CMN)HTUtils.h $(CMN)HTString.h $(CMN)HTList.h
    +	$(COMPILE) $(CMN)HTAssoc.c
    +
    +$(LOB)/HTLex$o : $(CMN)HTLex.c $(CMN)HTLex.h $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTLex.c
    +
    +$(LOB)/HTUU$o : $(CMN)HTUU.c $(CMN)HTUU.h $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTUU.c
    +
    +
    +#	Communications & Files
    +
    +$(LOB)/HTTP$o : $(CMN)HTTP.c $(CMN)HTUtils.h $(CMN)HTAABrow.h
    +	$(COMPILE) $(CMN)HTTP.c
    +
    +$(LOB)/HTTCP$o : $(CMN)HTTCP.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTTCP.c
    +
    +$(LOB)/HTFile$o : $(CMN)HTFile.c $(CMN)HTUtils.h \
    +	 $(CMN)HTMLDTD.h
    +	$(COMPILE) $(CMN)HTFile.c
    +
    +$(LOB)/HTBTree$o : $(CMN)HTBTree.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTBTree.c
    +
    +$(LOB)/HTFTP$o : $(CMN)HTFTP.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTFTP.c
    +
    +$(LOB)/HTAccess$o : $(CMN)HTAccess.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTAccess.c
    +
    +$(LOB)/HTParse$o : $(CMN)HTParse.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTParse.c
    +
    +$(LOB)/HTVMS_WaisUI$o : $(CMN)HTVMS_WaisUI.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTVMS_WaisUI.c
    +
    +$(LOB)/HTDOS$o : $(CMN)HTDOS.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)HTDOS.c
    +
    +# Utilities
    +$(LOB)/dtd_util$o : $(CMN)dtd_util.c $(CMN)HTUtils.h
    +	$(COMPILE) $(CMN)dtd_util.c
    +
    +DTD_UTIL = $(LOB)/dtd_util$o $(LOB)/HTMLDTD$o
    +
    +sources: dtd_util$x src0_HTMLDTD.txt src1_HTMLDTD.txt
    +	-rm -f *_HTMLDTD.h
    +	./dtd_util$x -l src0_HTMLDTD.txt -s -c -o src0_HTMLDTD.h
    +	./dtd_util$x -l src1_HTMLDTD.txt -t -c -o src1_HTMLDTD.h
    +	./dtd_util$x -l src0_HTMLDTD.txt -s -h -o hdr_HTMLDTD.h
    +
    +dtd_util$x: $(DTD_UTIL)
    +	$(CC) $(CC_OPTS) $(LDFLAGS) -o $@  $(DTD_UTIL) $(LIBS)
    +
    +DIFFC	= diff -u -I LynxId
    +check: dtd_util$x
    +	@echo "** comparing builtin src0_HTMLDTD.txt"
    +	./dtd_util$x >HTMLDTD.log
    +	-$(DIFFC) src0_HTMLDTD.txt HTMLDTD.log
    +
    +	@echo "** comparing reloaded src0_HTMLDTD.txt"
    +	./dtd_util$x -l src0_HTMLDTD.txt >HTMLDTD.log
    +	-$(DIFFC) src0_HTMLDTD.txt HTMLDTD.log
    +
    +	@echo "** comparing reloaded src1_HTMLDTD.txt"
    +	./dtd_util$x -t -l src1_HTMLDTD.txt >HTMLDTD.log
    +	-$(DIFFC) src1_HTMLDTD.txt HTMLDTD.log
    +
    +	@echo "** comparing header generated from builtin"
    +	./dtd_util$x -s -h -o HTMLDTD.log
    +	-$(DIFFC) hdr_HTMLDTD.h HTMLDTD.log
    +	./dtd_util$x -t -h -o HTMLDTD.log
    +	-$(DIFFC) hdr_HTMLDTD.h HTMLDTD.log
    +
    +	@echo "** comparing header generated by load"
    +	./dtd_util$x -s -h -o HTMLDTD.log -l src0_HTMLDTD.txt
    +	-$(DIFFC) hdr_HTMLDTD.h HTMLDTD.log
    +	./dtd_util$x -t -h -o HTMLDTD.log -l src1_HTMLDTD.txt
    +	-$(DIFFC) hdr_HTMLDTD.h HTMLDTD.log
    +
    +	@echo "** comparing strict source generated from builtin"
    +	./dtd_util$x -s -c -o HTMLDTD.log
    +	-$(DIFFC) src0_HTMLDTD.h HTMLDTD.log
    +
    +	@echo "** comparing strict source generated by load"
    +	./dtd_util$x -s -c -o HTMLDTD.log -l src0_HTMLDTD.txt
    +	-$(DIFFC) src0_HTMLDTD.h HTMLDTD.log
    +
    +	@echo "** comparing tagsoup source generated from builtin"
    +	./dtd_util$x -t -c -o HTMLDTD.log
    +	-$(DIFFC) src1_HTMLDTD.h HTMLDTD.log
    +
    +	@echo "** comparing tagsoup source generated by load"
    +	./dtd_util$x -t -c -o HTMLDTD.log -l src1_HTMLDTD.txt
    +	-$(DIFFC) src1_HTMLDTD.h HTMLDTD.log
    +
    +# DO NOT DELETE THIS LINE -- make depend depends on it.
    diff --git a/WWW/Library/Implementation/src0_HTMLDTD.h b/WWW/Library/Implementation/src0_HTMLDTD.h
    new file mode 100644
    index 0000000..a2daa4f
    --- /dev/null
    +++ b/WWW/Library/Implementation/src0_HTMLDTD.h
    @@ -0,0 +1,2478 @@
    +/* $LynxId: src0_HTMLDTD.h,v 1.49 2022/09/30 00:04:06 tom Exp $ */
    +#ifndef src_HTMLDTD_H0
    +#define src_HTMLDTD_H0 1
    +
    +#ifndef once_HTMLDTD
    +#define once_HTMLDTD 1
    +
    +#define T_A             0x00008,0x0B007,0x0FF17,0x37787,0x77BA7,0x8604F,0x00014
    +#define T_ABBR          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_ACRONYM       0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_ADDRESS       0x00200,0x0F14F,0x8FFFF,0x136680,0x1B6FAF,0x80317,0x00000
    +#define T_APPLET        0x02000,0x0B0CF,0x8FFFF,0x37F9F,0xB7FBF,0x8300F,0x00000
    +#define T_AREA          0x08000,0x00000,0x00000,0x08000,0x3FFFF,0x00F1F,0x00001
    +#define T_ARTICLE       0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_ASIDE         0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_AU            0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_AUTHOR        0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_B             0x00001,0x8B04F,0xAFFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_BANNER        0x00200,0x0FB8F,0x0FFFF,0x130000,0x130000,0x8031F,0x00000
    +#define T_BASE          0x40000,0x00000,0x00000,0x50000,0x50000,0x8000F,0x00001
    +#define T_BASEFONT      0x01000,0x00000,0x00000,0x377AF,0x37FAF,0x8F000,0x00001
    +#define T_BDO           0x00100,0x0B04F,0x8FFFF,0x136680,0x1B6FAF,0x0033F,0x00000
    +#define T_BGSOUND       0x01000,0x00000,0x00000,0x777AF,0x77FAF,0x8730F,0x00001
    +#define T_BIG           0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_BLINK         0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00001,0x00014
    +#define T_BLOCKQUOTE    0x00200,0xAFBCF,0xAFFFF,0x1B6680,0x1B6FAF,0x8031F,0x00000
    +#define T_BODY          0x20000,0x12FB8F,0x12FFFF,0x30000,0x30000,0xDFF7F,0x00003
    +#define T_BODYTEXT      0x20000,0x10FB8F,0x1AFFFF,0x30200,0xB7FAF,0x8F17F,0x00003
    +#define T_BQ            0x00200,0xAFBCF,0xAFFFF,0x1B6680,0x1B6FAF,0x8031F,0x00000
    +#define T_BR            0x01000,0x00000,0x00000,0x377BF,0x77FBF,0x8101F,0x00001
    +#define T_BUTTON        0x02000,0x0BB07,0x0FF37,0x0378F,0x37FBF,0x8115F,0x00000
    +#define T_CAPTION       0x00100,0x0B04F,0x8FFFF,0x106A00,0x1B6FA7,0x8035F,0x00000
    +#define T_CENTER        0x00200,0x8FBCF,0x8FFFF,0x1B6680,0x1B6FA7,0x8071F,0x00000
    +#define T_CITE          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00010
    +#define T_CODE          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00000
    +#define T_COL           0x04000,0x00000,0x00000,0x00820,0x36FA7,0x88F5F,0x00001
    +#define T_COLGROUP      0x00020,0x04000,0x04000,0x00800,0x36FA7,0x8875F,0x00001
    +#define T_COMMENT       0x00004,0x00000,0x00000,0xA77AF,0x7FFFF,0x00003,0x00000
    +#define T_CREDIT        0x00100,0x0B04F,0x8FFFF,0x106A00,0x1B7FBF,0x8030F,0x00000
    +#define T_DD            0x00400,0x0FBCF,0x8FFFF,0x00800,0xB6FFF,0x8071F,0x00001
    +#define T_DEL           0x00002,0x8B04F,0x8FEFF,0x87F8F,0xF7FBF,0x100003,0x00000
    +#define T_DEL_2         0x100000,0x8BBCF,0x8FFFF,0xA7F8F,0xF7FBF,0x100003,0x00000
    +#define T_DFN           0x00002,0x8B0CF,0x8FFFF,0x8778F,0xF7FBF,0x00003,0x00000
    +#define T_DIR           0x00800,0x0B400,0x0F75F,0x137680,0x136FB7,0x84F7F,0x00000
    +#define T_DIV           0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x80317,0x00004
    +#define T_DL            0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FB7,0x00757,0x00000
    +#define T_DLC           0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FB7,0x0075F,0x00000
    +#define T_DT            0x00400,0x0B04F,0x0B1FF,0x00800,0x17FFF,0x8071F,0x00001
    +#define T_EM            0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00010
    +#define T_EMBED         0x02000,0x8F107,0x8FFF7,0xB6FBF,0xB7FBF,0x1FF7F,0x00001
    +#define T_FIELDSET      0x00200,0x8FB4F,0x8FF7F,0x186787,0x1B7FF7,0x8805F,0x00000
    +#define T_FIG           0x00200,0x0FB00,0x8FFFF,0x136680,0x1B6FBF,0x8834F,0x00000
    +#define T_FIGURE        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_FN            0x00200,0x8FBCF,0x8FFFF,0x1B6680,0x1B7EBF,0x8114F,0x00000
    +#define T_FONT          0x00001,0x8B04F,0x8FFFF,0xB778F,0xF7FBF,0x00001,0x00014
    +#define T_FOOTER        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_FORM          0x00080,0x0FF6F,0x0FF7F,0x136E07,0x132F07,0x88DFF,0x00000
    +#define T_FRAME         0x10000,0x00000,0x00000,0x10000,0x10000,0x9FFFF,0x00001
    +#define T_FRAMESET      0x10000,0x90000,0x90000,0x90000,0x93000,0x9FFFF,0x00000
    +#define T_H1            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H2            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H3            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H4            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H5            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H6            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_HEAD          0x40000,0x4F000,0x47000,0x10000,0x10000,0x9FF7F,0x00007
    +#define T_HEADER        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_HR            0x04000,0x00000,0x00000,0x3FE80,0x3FFBF,0x87F37,0x00001
    +#define T_HTML          0x10000,0x7FB8F,0x7FFFF,0x00000,0x00000,0x1FFFF,0x00003
    +#define T_HY            0x01000,0x00000,0x00000,0x3779F,0x77FBF,0x8101F,0x00001
    +#define T_I             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_IFRAME        0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0xD315F,0x00000
    +#define T_IMG           0x01000,0x00000,0x00000,0x3779F,0x37FBF,0x80000,0x00001
    +#define T_INPUT         0x00040,0x00000,0x00000,0x03F87,0x37F87,0x8904F,0x00001
    +#define T_INS           0x00002,0x8B04F,0x8FEFF,0x87F8F,0xF7FBF,0x100003,0x00000
    +#define T_INS_2         0x100000,0x8BBCF,0x8FFFF,0xA7F8F,0xF7FBF,0x100003,0x00000
    +#define T_ISINDEX       0x08000,0x00000,0x00000,0x7778F,0x7FFAF,0x80007,0x00001
    +#define T_KBD           0x00002,0x00000,0x00000,0x2778F,0x77FBF,0x00003,0x00000
    +#define T_KEYGEN        0x00040,0x00000,0x00000,0x07FB7,0x37FB7,0x80070,0x00001
    +#define T_LABEL         0x00002,0x0304F,0x0FFFF,0x0679F,0x36FBF,0x00007,0x00000
    +#define T_LEGEND        0x00002,0x0B04F,0x8FF7F,0x00200,0xB7FA7,0x00003,0x00000
    +#define T_LH            0x00400,0x0BB7F,0x8FFFF,0x00800,0x97FFF,0x8071F,0x00001
    +#define T_LI            0x00400,0x0BBFF,0x8FFFF,0x00800,0x97FFF,0x8071F,0x00001
    +#define T_LINK          0x08000,0x00000,0x00000,0x50000,0x50000,0x0FF7F,0x00001
    +#define T_LISTING       0x00800,0x00000,0x00000,0x136600,0x136F00,0x80F1F,0x00000
    +#define T_MAIN          0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_MAP           0x08000,0x08000,0x08000,0x37FCF,0x37FBF,0x0051F,0x00000
    +#define T_MARQUEE       0x04000,0x0000F,0x8F01F,0x37787,0xB7FA7,0x8301C,0x00000
    +#define T_MATH          0x00004,0x0B05F,0x8FFFF,0x2778F,0xF7FBF,0x0001F,0x00000
    +#define T_MENU          0x00800,0x0B400,0x0F75F,0x117680,0x136FB7,0x88F7F,0x00000
    +#define T_META          0x08000,0x00000,0x00000,0x50000,0x50000,0x0FF7F,0x00001
    +#define T_NAV           0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_NEXTID        0x01000,0x00000,0x00000,0x50000,0x1FFF7,0x00001,0x00001
    +#define T_NOFRAMES      0x20000,0x12FB8F,0x10FFFF,0x17000,0x17000,0x0CF5F,0x00000
    +#define T_NOTE          0x00200,0x0BBAF,0x8FFFF,0x1376B0,0x1B7FFF,0x8031F,0x00000
    +#define T_OBJECT        0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0x83D5F,0x00020
    +#define T_OL            0x00800,0x0C400,0x8FFFF,0x137680,0x1B7FB7,0x88F7F,0x00000
    +#define T_OPTION        0x08000,0x00000,0x00000,0x00040,0x37FFF,0x8031F,0x00001
    +#define T_OVERLAY       0x04000,0x00000,0x00000,0x00200,0x37FBF,0x83F7F,0x00001
    +#define T_P             0x00100,0x0B04F,0x8FFFF,0x136680,0x1B6FA7,0x80117,0x00001
    +#define T_PARAM         0x01000,0x00000,0x00000,0x33500,0x37FFF,0x81560,0x00001
    +#define T_PLAINTEXT     0x10000,0xFFFFF,0xFFFFF,0x90000,0x90000,0x3FFFF,0x00001
    +#define T_PRE           0x00200,0x0F04F,0x0F05E,0x136680,0x136FF0,0x8071E,0x00000
    +#define T_Q             0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00000
    +#define T_S             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_SAMP          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00010
    +#define T_SCRIPT        0x02000,0x00000,0x00000,0x77F9F,0x77FFF,0x87D5F,0x00000
    +#define T_SECTION       0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_SELECT        0x00040,0x08000,0x08000,0x03FAF,0x33FBF,0x80D5F,0x00008
    +#define T_SHY           0x01000,0x00000,0x00000,0x3779F,0x77FBF,0x8101F,0x00001
    +#define T_SMALL         0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_SPAN          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x80003,0x00000
    +#define T_SPOT          0x00008,0x00000,0x00000,0x3FFF7,0x3FFF7,0x00008,0x00001
    +#define T_STRIKE        0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_STRONG        0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00010
    +#define T_STYLE         0x40000,0x00000,0x00000,0x7638F,0x76FAF,0x8001F,0x00000
    +#define T_SUB           0x00004,0x8B05F,0x8FFFF,0x8779F,0xF7FBF,0x00007,0x00000
    +#define T_SUP           0x00004,0x8B05F,0x8FFFF,0x8779F,0xF7FBF,0x00007,0x00000
    +#define T_TAB           0x01000,0x00000,0x00000,0x3778F,0x57FAF,0x00001,0x00001
    +#define T_TABLE         0x00800,0x0F1E0,0x8FFFF,0x136680,0x1B6FA7,0x8C57F,0x00000
    +#define T_TBODY         0x00020,0x00020,0x8FFFF,0x00880,0xB7FB7,0x8C75F,0x00003
    +#define T_TD            0x00400,0x0FBCF,0x8FFFF,0x00020,0xB7FB7,0x8C75F,0x00001
    +#define T_TEXTAREA      0x00040,0x00000,0x00000,0x07F8F,0x33FBF,0x80D5F,0x00040
    +#define T_TEXTFLOW      0x20000,0x18FBFF,0x19FFFF,0x977B0,0xB7FB7,0x9B00F,0x00003
    +#define T_TFOOT         0x00020,0x00020,0x8FFFF,0x00800,0xB7FB7,0x8CF5F,0x00001
    +#define T_TH            0x00400,0x0FBCF,0x0FFFF,0x00020,0xB7FB7,0x8CF5F,0x00001
    +#define T_THEAD         0x00020,0x00020,0x8FFFF,0x00800,0xB7FB7,0x8CF5F,0x00001
    +#define T_TITLE         0x40000,0x00000,0x00000,0x50000,0x50000,0x0031F,0x0000C
    +#define T_TR            0x00020,0x00400,0x8FFFF,0x00820,0xB7FB7,0x8C75F,0x00001
    +#define T_TT            0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00010
    +#define T_U             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_UL            0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FFF,0x8075F,0x00000
    +#define T_VAR           0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_WBR           0x00001,0x00000,0x00000,0x3778F,0x77FBF,0x8101F,0x00001
    +#define T_XMP           0x00800,0x00000,0x00000,0x1367E0,0x136FFF,0x0875F,0x00001
    +#define T_OBJECT_PCDATA 0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0x83D5F,0x00008
    +#define T__UNREC_	0x00000,0x00000,0x00000,0x00000,0x00000,0x00000,0x00000
    +#ifdef USE_PRETTYSRC
    +# define N HTMLA_NORMAL
    +# define i HTMLA_ANAME
    +# define h HTMLA_HREF
    +# define c HTMLA_CLASS
    +# define x HTMLA_AUXCLASS
    +# define T(t) , t
    +#else
    +# define T(t)			/*nothing */
    +#endif
    +/* *INDENT-OFF* */
    +
    +#define ATTR_TYPE(name) #name, name##_attr_list
    +
    +/* generic attributes, used in different tags */
    +static const attr core_attr_list[] = {
    +	{ "CLASS"         T(c) },
    +	{ "ID"            T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType core_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ 0, 0 },
    +};
    +
    +static const attr i18n_attr_list[] = {
    +	{ "DIR"           T(N) },
    +	{ "LANG"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType i18n_attr_type[] = {
    +	{ ATTR_TYPE(i18n) },
    +	{ 0, 0 },
    +};
    +
    +static const attr events_attr_list[] = {
    +	{ "ONCLICK"       T(N) },
    +	{ "ONDBLCLICK"    T(N) },
    +	{ "ONKEYDOWN"     T(N) },
    +	{ "ONKEYPRESS"    T(N) },
    +	{ "ONKEYUP"       T(N) },
    +	{ "ONMOUSEDOWN"   T(N) },
    +	{ "ONMOUSEMOVE"   T(N) },
    +	{ "ONMOUSEOUT"    T(N) },
    +	{ "ONMOUSEOVER"   T(N) },
    +	{ "ONMOUSEUP"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType events_attr_type[] = {
    +	{ ATTR_TYPE(events) },
    +	{ 0, 0 },
    +};
    +
    +static const attr align_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType align_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ 0, 0 },
    +};
    +
    +static const attr cellalign_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType cellalign_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ 0, 0 },
    +};
    +
    +static const attr bgcolor_attr_list[] = {
    +	{ "BGCOLOR"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType bgcolor_attr_type[] = {
    +	{ ATTR_TYPE(bgcolor) },
    +	{ 0, 0 },
    +};
    +
    +
    +/* tables defining attributes per-tag in terms of generic attributes (editable) */
    +static const attr A_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CHARSET"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "URN"           T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType A_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(A) },
    +	{ 0, 0 },
    +};
    +
    +static const attr ADDRESS_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType ADDRESS_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(ADDRESS) },
    +	{ 0, 0 },
    +};
    +
    +static const attr APPLET_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "CODE"          T(N) },
    +	{ "CODEBASE"      T(h) },
    +	{ "DOWNLOAD"      T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "NAME"          T(i) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType APPLET_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(APPLET) },
    +	{ 0, 0 },
    +};
    +
    +static const attr AREA_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "HREF"          T(h) },
    +	{ "NOHREF"        T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType AREA_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(AREA) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BASE_attr_list[] = {
    +	{ "HREF"          T(h) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BASE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(BASE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BGSOUND_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "LOOP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BGSOUND_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BGSOUND) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BODY_attr_list[] = {
    +	{ "ALINK"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "LINK"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "TEXT"          T(N) },
    +	{ "VLINK"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BODY_attr_type[] = {
    +	{ ATTR_TYPE(bgcolor) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BODY) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BODYTEXT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BODYTEXT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BODYTEXT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BQ_attr_list[] = {
    +	{ "CITE"          T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BQ_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BQ) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BUTTON_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "FORMACTION"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BUTTON_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BUTTON) },
    +	{ 0, 0 },
    +};
    +
    +static const attr CAPTION_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType CAPTION_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(CAPTION) },
    +	{ 0, 0 },
    +};
    +
    +static const attr COL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "SPAN"          T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType COL_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(COL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DEL_attr_list[] = {
    +	{ "CITE"          T(N) },
    +	{ "DATETIME"      T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DEL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DEL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DIV_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DIV_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DIV) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr EMBED_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOFLOW"        T(N) },
    +	{ "PARAMS"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType EMBED_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(EMBED) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FIG_attr_list[] = {
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FIG_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FIG) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FONT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COLOR"         T(N) },
    +	{ "END"           T(N) },
    +	{ "FACE"          T(N) },
    +	{ "SIZE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FONT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FONT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FORM_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACTION"        T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "ENCTYPE"       T(N) },
    +	{ "METHOD"        T(N) },
    +	{ "ONRESET"       T(N) },
    +	{ "ONSUBMIT"      T(N) },
    +	{ "SCRIPT"        T(N) },
    +	{ "SUBJECT"       T(N) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FORM_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FORM) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FRAME_attr_list[] = {
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NORESIZE"      T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FRAME_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(FRAME) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FRAMESET_attr_list[] = {
    +	{ "COLS"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FRAMESET_attr_type[] = {
    +	{ ATTR_TYPE(FRAMESET) },
    +	{ 0, 0 },
    +};
    +
    +static const attr GEN_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType GEN_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(GEN) },
    +	{ 0, 0 },
    +};
    +
    +static const attr GEN5_attr_list[] = {
    +	{ "ROLE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType GEN5_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(GEN5) },
    +	{ 0, 0 },
    +};
    +
    +static const attr H_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType H_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(H) },
    +	{ 0, 0 },
    +};
    +
    +static const attr HR_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOSHADE"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType HR_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(HR) },
    +	{ 0, 0 },
    +};
    +
    +static const attr IFRAME_attr_list[] = {
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType IFRAME_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(IFRAME) },
    +	{ 0, 0 },
    +};
    +
    +static const attr IMG_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType IMG_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(IMG) },
    +	{ 0, 0 },
    +};
    +
    +static const attr INPUT_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CHECKED"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MAX"           T(N) },
    +	{ "MAXLENGTH"     T(N) },
    +	{ "MD"            T(N) },
    +	{ "MIN"           T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType INPUT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(INPUT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr ISINDEX_attr_list[] = {
    +	{ "ACTION"        T(h) },
    +	{ "HREF"          T(h) },
    +	{ "PROMPT"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType ISINDEX_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(ISINDEX) },
    +	{ 0, 0 },
    +};
    +
    +static const attr KEYGEN_attr_list[] = {
    +	{ "CHALLENGE"     T(N) },
    +	{ "NAME"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType KEYGEN_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(KEYGEN) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LABEL_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LABEL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LABEL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LI_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LI_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LI) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LINK_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LINK_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LINK) },
    +	{ 0, 0 },
    +};
    +
    +static const attr MAP_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NAME"          T(i) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType MAP_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(MAP) },
    +	{ 0, 0 },
    +};
    +
    +static const attr MATH_attr_list[] = {
    +	{ "BOX"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType MATH_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(MATH) },
    +	{ 0, 0 },
    +};
    +
    +static const attr META_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "CONTENT"       T(N) },
    +	{ "HTTP-EQUIV"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCHEME"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType META_attr_type[] = {
    +	{ ATTR_TYPE(META) },
    +	{ 0, 0 },
    +};
    +
    +static const attr NEXTID_attr_list[] = {
    +	{ "N"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType NEXTID_attr_type[] = {
    +	{ ATTR_TYPE(NEXTID) },
    +	{ 0, 0 },
    +};
    +
    +static const attr NOTE_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "ROLE"          T(x) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType NOTE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(NOTE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OBJECT_attr_list[] = {
    +	{ "ARCHIVE"       T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASSID"       T(h) },
    +	{ "CODEBASE"      T(h) },
    +	{ "CODETYPE"      T(N) },
    +	{ "DATA"          T(h) },
    +	{ "DECLARE"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "SHAPES"        T(N) },
    +	{ "STANDBY"       T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OBJECT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OBJECT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "CONTINUE"      T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "START"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OPTION_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "LABEL"         T(N) },
    +	{ "SELECTED"      T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OPTION_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OPTION) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OVERLAY_attr_list[] = {
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ "X"             T(N) },
    +	{ "Y"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OVERLAY_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(OVERLAY) },
    +	{ 0, 0 },
    +};
    +
    +static const attr P_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType P_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(P) },
    +	{ 0, 0 },
    +};
    +
    +static const attr PARAM_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCEPT-ENCODING" T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUEREF"      T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType PARAM_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(PARAM) },
    +	{ 0, 0 },
    +};
    +
    +static const attr Q_attr_list[] = {
    +	{ "CITE"          T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType Q_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(Q) },
    +	{ 0, 0 },
    +};
    +
    +static const attr SCRIPT_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DEFER"         T(N) },
    +	{ "EVENT"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "LANGUAGE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCRIPTENGINE"  T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType SCRIPT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(SCRIPT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr SELECT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "MD"            T(N) },
    +	{ "MULTIPLE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType SELECT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(SELECT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr STYLE_attr_list[] = {
    +	{ "MEDIA"         T(N) },
    +	{ "NOTATION"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType STYLE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(STYLE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TAB_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DP"            T(N) },
    +	{ "INDENT"        T(N) },
    +	{ "TO"            T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TAB_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TAB) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TABLE_attr_list[] = {
    +	{ "BACKGROUND"    T(h) },
    +	{ "BORDER"        T(N) },
    +	{ "CELLPADDING"   T(N) },
    +	{ "CELLSPACING"   T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "COLSPEC"       T(N) },
    +	{ "DP"            T(N) },
    +	{ "FRAME"         T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "RULES"         T(N) },
    +	{ "SUMMARY"       T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TABLE_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TABLE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TD_attr_list[] = {
    +	{ "ABBR"          T(N) },
    +	{ "AXES"          T(N) },
    +	{ "AXIS"          T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLSPAN"       T(N) },
    +	{ "DP"            T(N) },
    +	{ "HEADERS"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "ROWSPAN"       T(N) },
    +	{ "SCOPE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TD_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TD) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TEXTAREA_attr_list[] = {
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TEXTAREA_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TEXTAREA) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TR_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DP"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TR_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TR) },
    +	{ 0, 0 },
    +};
    +
    +static const attr UL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "PLAIN"         T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ "WRAP"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType UL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(UL) },
    +	{ 0, 0 },
    +};
    +
    +
    +/* attribute lists for the runtime (generated by dtd_util) */
    +static const attr A_attr[] = {          /* A attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "URN"           T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr ADDRESS_attr[] = {    /* ADDRESS attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr APPLET_attr[] = {     /* APPLET attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "CODE"          T(N) },
    +	{ "CODEBASE"      T(h) },
    +	{ "DIR"           T(N) },
    +	{ "DOWNLOAD"      T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr AREA_attr[] = {       /* AREA attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOHREF"        T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BASE_attr[] = {       /* BASE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BGSOUND_attr[] = {    /* BGSOUND attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LOOP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BODY_attr[] = {       /* BODY attributes */
    +	{ "ALINK"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "BGCOLOR"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LINK"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TEXT"          T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VLINK"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BODYTEXT_attr[] = {   /* BODYTEXT attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BQ_attr[] = {         /* BLOCKQUOTE attributes */
    +	{ "CITE"          T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BUTTON_attr[] = {     /* BUTTON attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "FORMACTION"    T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr CAPTION_attr[] = {    /* CAPTION attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr COL_attr[] = {        /* COL attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SPAN"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DEL_attr[] = {        /* DEL attributes */
    +	{ "CITE"          T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DATETIME"      T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DIV_attr[] = {        /* CENTER attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DL_attr[] = {         /* DL attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr EMBED_attr[] = {      /* EMBED attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOFLOW"        T(N) },
    +	{ "PARAMS"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FIG_attr[] = {        /* FIG attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FONT_attr[] = {       /* BASEFONT attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLOR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "END"           T(N) },
    +	{ "FACE"          T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FORM_attr[] = {       /* FORM attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACTION"        T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ENCTYPE"       T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "METHOD"        T(N) },
    +	{ "ONRESET"       T(N) },
    +	{ "ONSUBMIT"      T(N) },
    +	{ "SCRIPT"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "SUBJECT"       T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FRAME_attr[] = {      /* FRAME attributes */
    +	{ "CLASS"         T(c) },
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "ID"            T(i) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NORESIZE"      T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FRAMESET_attr[] = {   /* FRAMESET attributes */
    +	{ "COLS"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr GEN_attr[] = {        /* ABBR attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr GEN5_attr[] = {       /* ARTICLE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "ROLE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr H_attr[] = {          /* H1 attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr HR_attr[] = {         /* HR attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOSHADE"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr IFRAME_attr[] = {     /* IFRAME attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr IMG_attr[] = {        /* IMG attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LANG"          T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr INPUT_attr[] = {      /* INPUT attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CHECKED"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MAX"           T(N) },
    +	{ "MAXLENGTH"     T(N) },
    +	{ "MD"            T(N) },
    +	{ "MIN"           T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr ISINDEX_attr[] = {    /* ISINDEX attributes */
    +	{ "ACTION"        T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "PROMPT"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr KEYGEN_attr[] = {     /* KEYGEN attributes */
    +	{ "CHALLENGE"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LABEL_attr[] = {      /* LABEL attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LI_attr[] = {         /* LI attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LINK_attr[] = {       /* LINK attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr MAP_attr[] = {        /* MAP attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr MATH_attr[] = {       /* MATH attributes */
    +	{ "BOX"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr META_attr[] = {       /* META attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CONTENT"       T(N) },
    +	{ "HTTP-EQUIV"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCHEME"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr NEXTID_attr[] = {     /* NEXTID attributes */
    +	{ "N"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr NOTE_attr[] = {       /* NOTE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "ROLE"          T(x) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OBJECT_attr[] = {     /* OBJECT attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ARCHIVE"       T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLASSID"       T(h) },
    +	{ "CODEBASE"      T(h) },
    +	{ "CODETYPE"      T(N) },
    +	{ "DATA"          T(h) },
    +	{ "DECLARE"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "SHAPES"        T(N) },
    +	{ "STANDBY"       T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OL_attr[] = {         /* OL attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "CONTINUE"      T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "START"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OPTION_attr[] = {     /* OPTION attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LABEL"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "SELECTED"      T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OVERLAY_attr[] = {    /* OVERLAY attributes */
    +	{ "CLASS"         T(c) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ "X"             T(N) },
    +	{ "Y"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr P_attr[] = {          /* P attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr PARAM_attr[] = {      /* PARAM attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCEPT-ENCODING" T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUEREF"      T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr Q_attr[] = {          /* Q attributes */
    +	{ "CITE"          T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr SCRIPT_attr[] = {     /* SCRIPT attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DEFER"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "EVENT"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LANGUAGE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCRIPTENGINE"  T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr SELECT_attr[] = {     /* SELECT attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "MULTIPLE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr STYLE_attr[] = {      /* STYLE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "NOTATION"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TAB_attr[] = {        /* TAB attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "ID"            T(i) },
    +	{ "INDENT"        T(N) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TO"            T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TABLE_attr[] = {      /* TABLE attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "BORDER"        T(N) },
    +	{ "CELLPADDING"   T(N) },
    +	{ "CELLSPACING"   T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "COLSPEC"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "FRAME"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "RULES"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "SUMMARY"       T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TD_attr[] = {         /* TD attributes */
    +	{ "ABBR"          T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "AXES"          T(N) },
    +	{ "AXIS"          T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLSPAN"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "HEADERS"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "ROWSPAN"       T(N) },
    +	{ "SCOPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TEXTAREA_attr[] = {   /* TEXTAREA attributes */
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TR_attr[] = {         /* TBODY attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr UL_attr[] = {         /* DIR attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "PLAIN"         T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "WRAP"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +/* *INDENT-ON* */
    +
    +/* justification-flags */
    +#undef N
    +#undef i
    +#undef h
    +#undef c
    +#undef x
    +
    +#undef T
    +
    +/* tag-names */
    +#undef A
    +#undef ABBR
    +#undef ACRONYM
    +#undef ADDRESS
    +#undef APPLET
    +#undef AREA
    +#undef ARTICLE
    +#undef ASIDE
    +#undef AU
    +#undef AUTHOR
    +#undef B
    +#undef BANNER
    +#undef BASE
    +#undef BASEFONT
    +#undef BDO
    +#undef BGSOUND
    +#undef BIG
    +#undef BLINK
    +#undef BLOCKQUOTE
    +#undef BODY
    +#undef BODYTEXT
    +#undef BQ
    +#undef BR
    +#undef BUTTON
    +#undef CAPTION
    +#undef CENTER
    +#undef CITE
    +#undef CODE
    +#undef COL
    +#undef COLGROUP
    +#undef COMMENT
    +#undef CREDIT
    +#undef DD
    +#undef DEL
    +#undef DEL
    +#undef DFN
    +#undef DIR
    +#undef DIV
    +#undef DL
    +#undef DLC
    +#undef DT
    +#undef EM
    +#undef EMBED
    +#undef FIELDSET
    +#undef FIG
    +#undef FIGURE
    +#undef FN
    +#undef FONT
    +#undef FOOTER
    +#undef FORM
    +#undef FRAME
    +#undef FRAMESET
    +#undef H1
    +#undef H2
    +#undef H3
    +#undef H4
    +#undef H5
    +#undef H6
    +#undef HEAD
    +#undef HEADER
    +#undef HR
    +#undef HTML
    +#undef HY
    +#undef I
    +#undef IFRAME
    +#undef IMG
    +#undef INPUT
    +#undef INS
    +#undef INS
    +#undef ISINDEX
    +#undef KBD
    +#undef KEYGEN
    +#undef LABEL
    +#undef LEGEND
    +#undef LH
    +#undef LI
    +#undef LINK
    +#undef LISTING
    +#undef MAIN
    +#undef MAP
    +#undef MARQUEE
    +#undef MATH
    +#undef MENU
    +#undef META
    +#undef NAV
    +#undef NEXTID
    +#undef NOFRAMES
    +#undef NOTE
    +#undef OBJECT
    +#undef OL
    +#undef OPTION
    +#undef OVERLAY
    +#undef P
    +#undef PARAM
    +#undef PLAINTEXT
    +#undef PRE
    +#undef Q
    +#undef S
    +#undef SAMP
    +#undef SCRIPT
    +#undef SECTION
    +#undef SELECT
    +#undef SHY
    +#undef SMALL
    +#undef SPAN
    +#undef SPOT
    +#undef STRIKE
    +#undef STRONG
    +#undef STYLE
    +#undef SUB
    +#undef SUP
    +#undef TAB
    +#undef TABLE
    +#undef TBODY
    +#undef TD
    +#undef TEXTAREA
    +#undef TEXTFLOW
    +#undef TFOOT
    +#undef TH
    +#undef THEAD
    +#undef TITLE
    +#undef TR
    +#undef TT
    +#undef U
    +#undef UL
    +#undef VAR
    +#undef WBR
    +#undef XMP
    +#undef OBJECT_PCDATA
    +
    +/* these definitions are used in the tags-tables */
    +#undef P
    +#undef P_
    +#ifdef USE_COLOR_STYLE
    +#define P_(x) #x, (sizeof #x) -1
    +#define NULL_HTTag_ NULL, 0
    +#else
    +#define P_(x) #x
    +#define NULL_HTTag_ NULL
    +#endif
    +
    +#ifdef USE_JUSTIFY_ELTS
    +#define P(x) P_(x), 1
    +#define P0(x) P_(x), 0
    +#define NULL_HTTag NULL_HTTag_,0
    +#else
    +#define P(x) P_(x)
    +#define P0(x) P_(x)
    +#define NULL_HTTag NULL_HTTag_
    +#endif
    +
    +#define ATTR_DATA(name) name##_attr, HTML_##name##_ATTRIBUTES, name##_attr_type
    +
    +#endif /* once_HTMLDTD */
    +/* *INDENT-OFF* */
    +static const HTTag tags_table0[HTML_ALL_ELEMENTS] = {
    + { P(A),           ATTR_DATA(A),           SGML_EMPTY,   T_A, 0, 0},
    + { P(ABBR),        ATTR_DATA(GEN),         SGML_MIXED,   T_ABBR, 0, 0},
    + { P(ACRONYM),     ATTR_DATA(GEN),         SGML_MIXED,   T_ACRONYM, 0, 0},
    + { P(ADDRESS),     ATTR_DATA(ADDRESS),     SGML_MIXED,   T_ADDRESS, 0, 0},
    + { P(APPLET),      ATTR_DATA(APPLET),      SGML_MIXED,   T_APPLET, 0, 0},
    + { P(AREA),        ATTR_DATA(AREA),        SGML_EMPTY,   T_AREA, 0, 0},
    + { P(ARTICLE),     ATTR_DATA(GEN5),        SGML_MIXED,   T_ARTICLE, 0, 0},
    + { P(ASIDE),       ATTR_DATA(GEN5),        SGML_MIXED,   T_ASIDE, 0, 0},
    + { P(AU),          ATTR_DATA(GEN),         SGML_MIXED,   T_AU, 0, 0},
    + { P(AUTHOR),      ATTR_DATA(GEN),         SGML_MIXED,   T_AUTHOR, 0, 0},
    + { P(B),           ATTR_DATA(GEN),         SGML_EMPTY,   T_B, 0, 0},
    + { P0(BANNER),     ATTR_DATA(GEN),         SGML_MIXED,   T_BANNER, 0, 0},
    + { P(BASE),        ATTR_DATA(BASE),        SGML_EMPTY,   T_BASE, 0, 0},
    + { P(BASEFONT),    ATTR_DATA(FONT),        SGML_EMPTY,   T_BASEFONT, 0, 0},
    + { P(BDO),         ATTR_DATA(GEN),         SGML_MIXED,   T_BDO, 0, 0},
    + { P(BGSOUND),     ATTR_DATA(BGSOUND),     SGML_EMPTY,   T_BGSOUND, 0, 0},
    + { P(BIG),         ATTR_DATA(GEN),         SGML_MIXED,   T_BIG, 0, 0},
    + { P(BLINK),       ATTR_DATA(GEN),         SGML_EMPTY,   T_BLINK, 0, 0},
    + { P(BLOCKQUOTE),  ATTR_DATA(BQ),          SGML_MIXED,   T_BLOCKQUOTE, 0, 0},
    + { P(BODY),        ATTR_DATA(BODY),        SGML_MIXED,   T_BODY, 0, 0},
    + { P(BODYTEXT),    ATTR_DATA(BODYTEXT),    SGML_MIXED,   T_BODYTEXT, 0, 0},
    + { P(BQ),          ATTR_DATA(BQ),          SGML_MIXED,   T_BQ, 0, 0},
    + { P(BR),          ATTR_DATA(GEN),         SGML_EMPTY,   T_BR, 0, 0},
    + { P(BUTTON),      ATTR_DATA(BUTTON),      SGML_MIXED,   T_BUTTON, 0, 0},
    + { P(CAPTION),     ATTR_DATA(CAPTION),     SGML_MIXED,   T_CAPTION, 0, 0},
    + { P(CENTER),      ATTR_DATA(DIV),         SGML_MIXED,   T_CENTER, 0, 0},
    + { P(CITE),        ATTR_DATA(GEN),         SGML_EMPTY,   T_CITE, 0, 0},
    + { P(CODE),        ATTR_DATA(GEN),         SGML_MIXED,   T_CODE, 0, 0},
    + { P(COL),         ATTR_DATA(COL),         SGML_EMPTY,   T_COL, 0, 0},
    + { P(COLGROUP),    ATTR_DATA(COL),         SGML_EMPTY,   T_COLGROUP, 0, 0},
    + { P(COMMENT),     ATTR_DATA(GEN),         SGML_MIXED,   T_COMMENT, 0, 0},
    + { P(CREDIT),      ATTR_DATA(GEN),         SGML_MIXED,   T_CREDIT, 0, 0},
    + { P(DD),          ATTR_DATA(GEN),         SGML_EMPTY,   T_DD, 0, 0},
    + { P(DEL),         ATTR_DATA(DEL),         SGML_MIXED,   T_DEL, 0, 1},
    + { P(DEL),         ATTR_DATA(DEL),         SGML_MIXED,   T_DEL_2, 1, 1},
    + { P(DFN),         ATTR_DATA(GEN),         SGML_MIXED,   T_DFN, 0, 0},
    + { P(DIR),         ATTR_DATA(UL),          SGML_MIXED,   T_DIR, 0, 0},
    + { P(DIV),         ATTR_DATA(DIV),         SGML_MIXED,   T_DIV, 0, 0},
    + { P(DL),          ATTR_DATA(DL),          SGML_MIXED,   T_DL, 0, 0},
    + { P(DLC),         ATTR_DATA(DL),          SGML_MIXED,   T_DLC, 0, 0},
    + { P(DT),          ATTR_DATA(GEN),         SGML_EMPTY,   T_DT, 0, 0},
    + { P(EM),          ATTR_DATA(GEN),         SGML_EMPTY,   T_EM, 0, 0},
    + { P(EMBED),       ATTR_DATA(EMBED),       SGML_EMPTY,   T_EMBED, 0, 0},
    + { P(FIELDSET),    ATTR_DATA(GEN),         SGML_MIXED,   T_FIELDSET, 0, 0},
    + { P(FIG),         ATTR_DATA(FIG),         SGML_MIXED,   T_FIG, 0, 0},
    + { P(FIGURE),      ATTR_DATA(GEN5),        SGML_MIXED,   T_FIGURE, 0, 0},
    + { P(FN),          ATTR_DATA(GEN),         SGML_MIXED,   T_FN, 0, 0},
    + { P(FONT),        ATTR_DATA(FONT),        SGML_EMPTY,   T_FONT, 0, 0},
    + { P(FOOTER),      ATTR_DATA(GEN5),        SGML_MIXED,   T_FOOTER, 0, 0},
    + { P(FORM),        ATTR_DATA(FORM),        SGML_EMPTY,   T_FORM, 0, 0},
    + { P(FRAME),       ATTR_DATA(FRAME),       SGML_EMPTY,   T_FRAME, 0, 0},
    + { P(FRAMESET),    ATTR_DATA(FRAMESET),    SGML_MIXED,   T_FRAMESET, 0, 0},
    + { P0(H1),         ATTR_DATA(H),           SGML_MIXED,   T_H1, 0, 0},
    + { P0(H2),         ATTR_DATA(H),           SGML_MIXED,   T_H2, 0, 0},
    + { P0(H3),         ATTR_DATA(H),           SGML_MIXED,   T_H3, 0, 0},
    + { P0(H4),         ATTR_DATA(H),           SGML_MIXED,   T_H4, 0, 0},
    + { P0(H5),         ATTR_DATA(H),           SGML_MIXED,   T_H5, 0, 0},
    + { P0(H6),         ATTR_DATA(H),           SGML_MIXED,   T_H6, 0, 0},
    + { P(HEAD),        ATTR_DATA(GEN),         SGML_MIXED,   T_HEAD, 0, 0},
    + { P(HEADER),      ATTR_DATA(GEN5),        SGML_MIXED,   T_HEADER, 0, 0},
    + { P(HR),          ATTR_DATA(HR),          SGML_EMPTY,   T_HR, 0, 0},
    + { P(HTML),        ATTR_DATA(GEN),         SGML_MIXED,   T_HTML, 0, 0},
    + { P(HY),          ATTR_DATA(GEN),         SGML_EMPTY,   T_HY, 0, 0},
    + { P(I),           ATTR_DATA(GEN),         SGML_EMPTY,   T_I, 0, 0},
    + { P(IFRAME),      ATTR_DATA(IFRAME),      SGML_MIXED,   T_IFRAME, 0, 0},
    + { P(IMG),         ATTR_DATA(IMG),         SGML_EMPTY,   T_IMG, 0, 0},
    + { P(INPUT),       ATTR_DATA(INPUT),       SGML_EMPTY,   T_INPUT, 0, 0},
    + { P(INS),         ATTR_DATA(DEL),         SGML_MIXED,   T_INS, 0, 1},
    + { P(INS),         ATTR_DATA(DEL),         SGML_MIXED,   T_INS_2, 1, 1},
    + { P(ISINDEX),     ATTR_DATA(ISINDEX),     SGML_EMPTY,   T_ISINDEX, 0, 0},
    + { P(KBD),         ATTR_DATA(GEN),         SGML_MIXED,   T_KBD, 0, 0},
    + { P(KEYGEN),      ATTR_DATA(KEYGEN),      SGML_EMPTY,   T_KEYGEN, 0, 0},
    + { P(LABEL),       ATTR_DATA(LABEL),       SGML_MIXED,   T_LABEL, 0, 0},
    + { P(LEGEND),      ATTR_DATA(CAPTION),     SGML_MIXED,   T_LEGEND, 0, 0},
    + { P(LH),          ATTR_DATA(GEN),         SGML_EMPTY,   T_LH, 0, 0},
    + { P(LI),          ATTR_DATA(LI),          SGML_EMPTY,   T_LI, 0, 0},
    + { P(LINK),        ATTR_DATA(LINK),        SGML_EMPTY,   T_LINK, 0, 0},
    + { P(LISTING),     ATTR_DATA(GEN),         SGML_LITTERAL,T_LISTING, 0, 0},
    + { P(MAIN),        ATTR_DATA(GEN5),        SGML_MIXED,   T_MAIN, 0, 0},
    + { P(MAP),         ATTR_DATA(MAP),         SGML_MIXED,   T_MAP, 0, 0},
    + { P(MARQUEE),     ATTR_DATA(GEN),         SGML_MIXED,   T_MARQUEE, 0, 0},
    + { P(MATH),        ATTR_DATA(MATH),        SGML_LITTERAL,T_MATH, 0, 0},
    + { P(MENU),        ATTR_DATA(UL),          SGML_MIXED,   T_MENU, 0, 0},
    + { P(META),        ATTR_DATA(META),        SGML_EMPTY,   T_META, 0, 0},
    + { P(NAV),         ATTR_DATA(GEN5),        SGML_MIXED,   T_NAV, 0, 0},
    + { P(NEXTID),      ATTR_DATA(NEXTID),      SGML_EMPTY,   T_NEXTID, 0, 0},
    + { P(NOFRAMES),    ATTR_DATA(GEN),         SGML_MIXED,   T_NOFRAMES, 0, 0},
    + { P(NOTE),        ATTR_DATA(NOTE),        SGML_MIXED,   T_NOTE, 0, 0},
    + { P(OBJECT),      ATTR_DATA(OBJECT),      SGML_LITTERAL,T_OBJECT, 0, 0},
    + { P(OL),          ATTR_DATA(OL),          SGML_MIXED,   T_OL, 0, 0},
    + { P(OPTION),      ATTR_DATA(OPTION),      SGML_EMPTY,   T_OPTION, 0, 0},
    + { P(OVERLAY),     ATTR_DATA(OVERLAY),     SGML_EMPTY,   T_OVERLAY, 0, 0},
    + { P(P),           ATTR_DATA(P),           SGML_EMPTY,   T_P, 0, 0},
    + { P(PARAM),       ATTR_DATA(PARAM),       SGML_EMPTY,   T_PARAM, 0, 0},
    + { P(PLAINTEXT),   ATTR_DATA(GEN),         SGML_LITTERAL,T_PLAINTEXT, 0, 0},
    + { P0(PRE),        ATTR_DATA(GEN),         SGML_MIXED,   T_PRE, 0, 0},
    + { P(Q),           ATTR_DATA(Q),           SGML_MIXED,   T_Q, 0, 0},
    + { P(S),           ATTR_DATA(GEN),         SGML_MIXED,   T_S, 0, 0},
    + { P(SAMP),        ATTR_DATA(GEN),         SGML_MIXED,   T_SAMP, 0, 0},
    + { P(SCRIPT),      ATTR_DATA(SCRIPT),      SGML_LITTERAL,T_SCRIPT, 0, 0},
    + { P(SECTION),     ATTR_DATA(GEN5),        SGML_MIXED,   T_SECTION, 0, 0},
    + { P(SELECT),      ATTR_DATA(SELECT),      SGML_MIXED,   T_SELECT, 0, 0},
    + { P(SHY),         ATTR_DATA(GEN),         SGML_EMPTY,   T_SHY, 0, 0},
    + { P(SMALL),       ATTR_DATA(GEN),         SGML_MIXED,   T_SMALL, 0, 0},
    + { P(SPAN),        ATTR_DATA(GEN),         SGML_MIXED,   T_SPAN, 0, 0},
    + { P(SPOT),        ATTR_DATA(GEN),         SGML_EMPTY,   T_SPOT, 0, 0},
    + { P(STRIKE),      ATTR_DATA(GEN),         SGML_MIXED,   T_STRIKE, 0, 0},
    + { P(STRONG),      ATTR_DATA(GEN),         SGML_EMPTY,   T_STRONG, 0, 0},
    + { P(STYLE),       ATTR_DATA(STYLE),       SGML_LITTERAL,T_STYLE, 0, 0},
    + { P(SUB),         ATTR_DATA(GEN),         SGML_MIXED,   T_SUB, 0, 0},
    + { P(SUP),         ATTR_DATA(GEN),         SGML_MIXED,   T_SUP, 0, 0},
    + { P(TAB),         ATTR_DATA(TAB),         SGML_EMPTY,   T_TAB, 0, 0},
    + { P(TABLE),       ATTR_DATA(TABLE),       SGML_MIXED,   T_TABLE, 0, 0},
    + { P(TBODY),       ATTR_DATA(TR),          SGML_EMPTY,   T_TBODY, 0, 0},
    + { P(TD),          ATTR_DATA(TD),          SGML_EMPTY,   T_TD, 0, 0},
    + { P(TEXTAREA),    ATTR_DATA(TEXTAREA),    SGML_LITTERAL,T_TEXTAREA, 0, 0},
    + { P(TEXTFLOW),    ATTR_DATA(BODYTEXT),    SGML_MIXED,   T_TEXTFLOW, 0, 0},
    + { P(TFOOT),       ATTR_DATA(TR),          SGML_EMPTY,   T_TFOOT, 0, 0},
    + { P(TH),          ATTR_DATA(TD),          SGML_EMPTY,   T_TH, 0, 0},
    + { P(THEAD),       ATTR_DATA(TR),          SGML_EMPTY,   T_THEAD, 0, 0},
    + { P(TITLE),       ATTR_DATA(GEN),         SGML_RCDATA,  T_TITLE, 0, 0},
    + { P(TR),          ATTR_DATA(TR),          SGML_EMPTY,   T_TR, 0, 0},
    + { P(TT),          ATTR_DATA(GEN),         SGML_EMPTY,   T_TT, 0, 0},
    + { P(U),           ATTR_DATA(GEN),         SGML_EMPTY,   T_U, 0, 0},
    + { P(UL),          ATTR_DATA(UL),          SGML_MIXED,   T_UL, 0, 0},
    + { P(VAR),         ATTR_DATA(GEN),         SGML_MIXED,   T_VAR, 0, 0},
    + { P(WBR),         ATTR_DATA(GEN),         SGML_EMPTY,   T_WBR, 0, 0},
    + { P0(XMP),        ATTR_DATA(GEN),         SGML_LITTERAL,T_XMP, 0, 0},
    +/* additional (alternative variants), not counted in HTML_ELEMENTS: */
    +/* This one will be used as a temporary substitute within the parser when
    +   it has been signalled to parse OBJECT content as MIXED. - kw */
    + { P(OBJECT),      ATTR_DATA(OBJECT),      SGML_MIXED,   T_OBJECT_PCDATA, 0, 0},
    +};
    +/* *INDENT-ON* */
    +
    +#endif /* src_HTMLDTD_H0 */
    diff --git a/WWW/Library/Implementation/src0_HTMLDTD.txt b/WWW/Library/Implementation/src0_HTMLDTD.txt
    new file mode 100644
    index 0000000..a3c51d9
    --- /dev/null
    +++ b/WWW/Library/Implementation/src0_HTMLDTD.txt
    @@ -0,0 +1,3901 @@
    +60 attr_types
    +	0:align
    +		1 attributes:
    +			0:0:ALIGN
    +	1:bgcolor
    +		1 attributes:
    +			0:0:BGCOLOR
    +	2:cellalign
    +		4 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:0:VALIGN
    +	3:core
    +		4 attributes:
    +			0:4:CLASS
    +			1:1:ID
    +			2:0:STYLE
    +			3:0:TITLE
    +	4:events
    +		10 attributes:
    +			0:0:ONCLICK
    +			1:0:ONDBLCLICK
    +			2:0:ONKEYDOWN
    +			3:0:ONKEYPRESS
    +			4:0:ONKEYUP
    +			5:0:ONMOUSEDOWN
    +			6:0:ONMOUSEMOVE
    +			7:0:ONMOUSEOUT
    +			8:0:ONMOUSEOVER
    +			9:0:ONMOUSEUP
    +	5:i18n
    +		2 attributes:
    +			0:0:DIR
    +			1:0:LANG
    +	6:A
    +		19 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CHARSET
    +			2:0:CLEAR
    +			3:0:COORDS
    +			4:2:HREF
    +			5:0:HREFLANG
    +			6:0:ISMAP
    +			7:0:MD
    +			8:1:NAME
    +			9:0:NOTAB
    +			10:0:ONBLUR
    +			11:0:ONFOCUS
    +			12:0:REL
    +			13:0:REV
    +			14:0:SHAPE
    +			15:0:TABINDEX
    +			16:0:TARGET
    +			17:0:TYPE
    +			18:0:URN
    +	7:ADDRESS
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:NOWRAP
    +	8:APPLET
    +		10 attributes:
    +			0:0:ALT
    +			1:0:CLEAR
    +			2:0:CODE
    +			3:2:CODEBASE
    +			4:0:DOWNLOAD
    +			5:0:HEIGHT
    +			6:0:HSPACE
    +			7:1:NAME
    +			8:0:VSPACE
    +			9:0:WIDTH
    +	9:AREA
    +		12 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALT
    +			2:0:CLEAR
    +			3:0:COORDS
    +			4:2:HREF
    +			5:0:NOHREF
    +			6:0:NOTAB
    +			7:0:ONBLUR
    +			8:0:ONFOCUS
    +			9:0:SHAPE
    +			10:0:TABINDEX
    +			11:0:TARGET
    +	10:BASE
    +		2 attributes:
    +			0:2:HREF
    +			1:0:TARGET
    +	11:BGSOUND
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:LOOP
    +			2:2:SRC
    +	12:BODY
    +		8 attributes:
    +			0:0:ALINK
    +			1:2:BACKGROUND
    +			2:0:CLEAR
    +			3:0:LINK
    +			4:0:ONLOAD
    +			5:0:ONUNLOAD
    +			6:0:TEXT
    +			7:0:VLINK
    +	13:BODYTEXT
    +		8 attributes:
    +			0:0:CLEAR
    +			1:0:DATA
    +			2:0:NAME
    +			3:0:OBJECT
    +			4:0:REF
    +			5:0:TYPE
    +			6:0:VALUE
    +			7:0:VALUETYPE
    +	14:BQ
    +		3 attributes:
    +			0:2:CITE
    +			1:0:CLEAR
    +			2:0:NOWRAP
    +	15:BUTTON
    +		11 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +			2:0:DISABLED
    +			3:0:FORMACTION
    +			4:0:NAME
    +			5:0:ONBLUR
    +			6:0:ONFOCUS
    +			7:0:READONLY
    +			8:0:TABINDEX
    +			9:0:TYPE
    +			10:0:VALUE
    +	16:CAPTION
    +		2 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +	17:COL
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:SPAN
    +			2:0:WIDTH
    +	18:DEL
    +		2 attributes:
    +			0:0:CITE
    +			1:0:DATETIME
    +	19:DIV
    +		1 attributes:
    +			0:0:CLEAR
    +	20:DL
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +	21:EMBED
    +		14 attributes:
    +			0:0:ALT
    +			1:0:BORDER
    +			2:0:CLEAR
    +			3:0:HEIGHT
    +			4:0:IMAGEMAP
    +			5:0:ISMAP
    +			6:0:MD
    +			7:1:NAME
    +			8:0:NOFLOW
    +			9:0:PARAMS
    +			10:2:SRC
    +			11:0:UNITS
    +			12:0:USEMAP
    +			13:0:WIDTH
    +	22:FIG
    +		10 attributes:
    +			0:0:BORDER
    +			1:0:CLEAR
    +			2:0:HEIGHT
    +			3:0:IMAGEMAP
    +			4:0:ISOBJECT
    +			5:0:MD
    +			6:0:NOFLOW
    +			7:2:SRC
    +			8:0:UNITS
    +			9:0:WIDTH
    +	23:FONT
    +		5 attributes:
    +			0:0:CLEAR
    +			1:0:COLOR
    +			2:0:END
    +			3:0:FACE
    +			4:0:SIZE
    +	24:FORM
    +		11 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:2:ACTION
    +			3:0:CLEAR
    +			4:0:ENCTYPE
    +			5:0:METHOD
    +			6:0:ONRESET
    +			7:0:ONSUBMIT
    +			8:0:SCRIPT
    +			9:0:SUBJECT
    +			10:0:TARGET
    +	25:FRAME
    +		8 attributes:
    +			0:0:FRAMEBORDER
    +			1:2:LONGDESC
    +			2:0:MARGINHEIGHT
    +			3:0:MARGINWIDTH
    +			4:0:NAME
    +			5:0:NORESIZE
    +			6:0:SCROLLING
    +			7:2:SRC
    +	26:FRAMESET
    +		4 attributes:
    +			0:0:COLS
    +			1:0:ONLOAD
    +			2:0:ONUNLOAD
    +			3:0:ROWS
    +	27:GEN
    +		1 attributes:
    +			0:0:CLEAR
    +	28:GEN5
    +		1 attributes:
    +			0:0:ROLE
    +	29:H
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DINGBAT
    +			2:0:MD
    +			3:0:NOWRAP
    +			4:0:SEQNUM
    +			5:0:SKIP
    +			6:2:SRC
    +	30:HR
    +		6 attributes:
    +			0:0:CLEAR
    +			1:0:MD
    +			2:0:NOSHADE
    +			3:0:SIZE
    +			4:2:SRC
    +			5:0:WIDTH
    +	31:IFRAME
    +		9 attributes:
    +			0:0:FRAMEBORDER
    +			1:0:HEIGHT
    +			2:2:LONGDESC
    +			3:0:MARGINHEIGHT
    +			4:0:MARGINWIDTH
    +			5:0:NAME
    +			6:0:SCROLLING
    +			7:2:SRC
    +			8:0:WIDTH
    +	32:IMG
    +		15 attributes:
    +			0:0:ALT
    +			1:0:BORDER
    +			2:0:CLEAR
    +			3:0:HEIGHT
    +			4:0:HSPACE
    +			5:0:ISMAP
    +			6:0:ISOBJECT
    +			7:2:LONGDESC
    +			8:0:MD
    +			9:0:NAME
    +			10:2:SRC
    +			11:0:UNITS
    +			12:2:USEMAP
    +			13:0:VSPACE
    +			14:0:WIDTH
    +	33:INPUT
    +		28 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCESSKEY
    +			3:0:ALT
    +			4:0:CHECKED
    +			5:0:CLEAR
    +			6:0:DISABLED
    +			7:0:ERROR
    +			8:0:HEIGHT
    +			9:0:ISMAP
    +			10:0:MAX
    +			11:0:MAXLENGTH
    +			12:0:MD
    +			13:0:MIN
    +			14:0:NAME
    +			15:0:NOTAB
    +			16:0:ONBLUR
    +			17:0:ONCHANGE
    +			18:0:ONFOCUS
    +			19:0:ONSELECT
    +			20:0:READONLY
    +			21:0:SIZE
    +			22:2:SRC
    +			23:0:TABINDEX
    +			24:0:TYPE
    +			25:0:USEMAP
    +			26:0:VALUE
    +			27:0:WIDTH
    +	34:ISINDEX
    +		3 attributes:
    +			0:2:ACTION
    +			1:2:HREF
    +			2:0:PROMPT
    +	35:KEYGEN
    +		2 attributes:
    +			0:0:CHALLENGE
    +			1:0:NAME
    +	36:LABEL
    +		5 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +			2:0:FOR
    +			3:0:ONBLUR
    +			4:0:ONFOCUS
    +	37:LI
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DINGBAT
    +			2:0:MD
    +			3:0:SKIP
    +			4:2:SRC
    +			5:0:TYPE
    +			6:0:VALUE
    +	38:LINK
    +		8 attributes:
    +			0:0:CHARSET
    +			1:2:HREF
    +			2:0:HREFLANG
    +			3:0:MEDIA
    +			4:0:REL
    +			5:0:REV
    +			6:0:TARGET
    +			7:0:TYPE
    +	39:MAP
    +		2 attributes:
    +			0:0:CLEAR
    +			1:1:NAME
    +	40:MATH
    +		2 attributes:
    +			0:0:BOX
    +			1:0:CLEAR
    +	41:META
    +		5 attributes:
    +			0:0:CHARSET
    +			1:0:CONTENT
    +			2:0:HTTP-EQUIV
    +			3:0:NAME
    +			4:0:SCHEME
    +	42:NEXTID
    +		1 attributes:
    +			0:0:N
    +	43:NOTE
    +		4 attributes:
    +			0:0:CLEAR
    +			1:0:MD
    +			2:8:ROLE
    +			3:2:SRC
    +	44:OBJECT
    +		19 attributes:
    +			0:0:ARCHIVE
    +			1:0:BORDER
    +			2:2:CLASSID
    +			3:2:CODEBASE
    +			4:0:CODETYPE
    +			5:2:DATA
    +			6:0:DECLARE
    +			7:0:HEIGHT
    +			8:0:HSPACE
    +			9:0:ISMAP
    +			10:0:NAME
    +			11:0:NOTAB
    +			12:0:SHAPES
    +			13:0:STANDBY
    +			14:0:TABINDEX
    +			15:0:TYPE
    +			16:2:USEMAP
    +			17:0:VSPACE
    +			18:0:WIDTH
    +	45:OL
    +		6 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +			2:0:CONTINUE
    +			3:0:SEQNUM
    +			4:0:START
    +			5:0:TYPE
    +	46:OPTION
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DISABLED
    +			2:0:ERROR
    +			3:0:LABEL
    +			4:0:SELECTED
    +			5:0:SHAPE
    +			6:0:VALUE
    +	47:OVERLAY
    +		8 attributes:
    +			0:0:HEIGHT
    +			1:0:IMAGEMAP
    +			2:0:MD
    +			3:2:SRC
    +			4:0:UNITS
    +			5:0:WIDTH
    +			6:0:X
    +			7:0:Y
    +	48:P
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:NOWRAP
    +	49:PARAM
    +		12 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCEPT-ENCODING
    +			3:0:CLEAR
    +			4:0:DATA
    +			5:0:NAME
    +			6:0:OBJECT
    +			7:0:REF
    +			8:0:TYPE
    +			9:0:VALUE
    +			10:0:VALUEREF
    +			11:0:VALUETYPE
    +	50:Q
    +		2 attributes:
    +			0:2:CITE
    +			1:0:CLEAR
    +	51:SCRIPT
    +		10 attributes:
    +			0:0:CHARSET
    +			1:0:CLEAR
    +			2:0:DEFER
    +			3:0:EVENT
    +			4:0:FOR
    +			5:0:LANGUAGE
    +			6:0:NAME
    +			7:0:SCRIPTENGINE
    +			8:2:SRC
    +			9:0:TYPE
    +	52:SELECT
    +		15 attributes:
    +			0:0:CLEAR
    +			1:0:DISABLED
    +			2:0:ERROR
    +			3:0:HEIGHT
    +			4:0:MD
    +			5:0:MULTIPLE
    +			6:0:NAME
    +			7:0:NOTAB
    +			8:0:ONBLUR
    +			9:0:ONCHANGE
    +			10:0:ONFOCUS
    +			11:0:SIZE
    +			12:0:TABINDEX
    +			13:0:UNITS
    +			14:0:WIDTH
    +	53:STYLE
    +		3 attributes:
    +			0:0:MEDIA
    +			1:0:NOTATION
    +			2:0:TYPE
    +	54:TAB
    +		4 attributes:
    +			0:0:CLEAR
    +			1:0:DP
    +			2:0:INDENT
    +			3:0:TO
    +	55:TABLE
    +		15 attributes:
    +			0:2:BACKGROUND
    +			1:0:BORDER
    +			2:0:CELLPADDING
    +			3:0:CELLSPACING
    +			4:0:CLEAR
    +			5:0:COLS
    +			6:0:COLSPEC
    +			7:0:DP
    +			8:0:FRAME
    +			9:0:NOFLOW
    +			10:0:NOWRAP
    +			11:0:RULES
    +			12:0:SUMMARY
    +			13:0:UNITS
    +			14:0:WIDTH
    +	56:TD
    +		13 attributes:
    +			0:0:ABBR
    +			1:0:AXES
    +			2:0:AXIS
    +			3:2:BACKGROUND
    +			4:0:CLEAR
    +			5:0:COLSPAN
    +			6:0:DP
    +			7:0:HEADERS
    +			8:0:HEIGHT
    +			9:0:NOWRAP
    +			10:0:ROWSPAN
    +			11:0:SCOPE
    +			12:0:WIDTH
    +	57:TEXTAREA
    +		15 attributes:
    +			0:0:ACCEPT-CHARSET
    +			1:0:ACCESSKEY
    +			2:0:CLEAR
    +			3:0:COLS
    +			4:0:DISABLED
    +			5:0:ERROR
    +			6:0:NAME
    +			7:0:NOTAB
    +			8:0:ONBLUR
    +			9:0:ONCHANGE
    +			10:0:ONFOCUS
    +			11:0:ONSELECT
    +			12:0:READONLY
    +			13:0:ROWS
    +			14:0:TABINDEX
    +	58:TR
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:DP
    +			2:0:NOWRAP
    +	59:UL
    +		8 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +			2:0:DINGBAT
    +			3:0:MD
    +			4:0:PLAIN
    +			5:2:SRC
    +			6:0:TYPE
    +			7:0:WRAP
    +128 tags
    +	0:A
    +		justify
    +		25 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CHARSET
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:COORDS
    +			5:0:DIR
    +			6:2:HREF
    +			7:0:HREFLANG
    +			8:1:ID
    +			9:0:ISMAP
    +			10:0:LANG
    +			11:0:MD
    +			12:1:NAME
    +			13:0:NOTAB
    +			14:0:ONBLUR
    +			15:0:ONFOCUS
    +			16:0:REL
    +			17:0:REV
    +			18:0:SHAPE
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TARGET
    +			22:0:TITLE
    +			23:0:TYPE
    +			24:0:URN
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			A
    +		contents: SGML_EMPTY
    +		tagclass: Alike
    +		contains: FONTlike EMlike MATHlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike formula Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike APPLETlike HRlike same
    +		flags: mafse nreie
    +	1:ABBR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	2:ACRONYM
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	3:ADDRESS
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:NOWRAP
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			ADDRESS
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike Plike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike DIVlike same
    +		flags:
    +	4:APPLET
    +		justify
    +		17 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:CODE
    +			5:2:CODEBASE
    +			6:0:DIR
    +			7:0:DOWNLOAD
    +			8:0:HEIGHT
    +			9:0:HSPACE
    +			10:1:ID
    +			11:0:LANG
    +			12:1:NAME
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:VSPACE
    +			16:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			APPLET
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike BRlike APPLETlike same
    +		flags:
    +	5:AREA
    +		justify
    +		18 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALT
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:COORDS
    +			5:0:DIR
    +			6:2:HREF
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOHREF
    +			10:0:NOTAB
    +			11:0:ONBLUR
    +			12:0:ONFOCUS
    +			13:0:SHAPE
    +			14:0:STYLE
    +			15:0:TABINDEX
    +			16:0:TARGET
    +			17:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			AREA
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: MAPlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike ULlike
    +		flags: endO
    +	6:ARTICLE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	7:ASIDE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	8:AU
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	9:AUTHOR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	10:B
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	11:BANNER
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: outer BODYlike DELlike
    +		icontained: outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	12:BASE
    +		justify
    +		6 attributes:
    +			0:4:CLASS
    +			1:2:HREF
    +			2:1:ID
    +			3:0:STYLE
    +			4:0:TARGET
    +			5:0:TITLE
    +		2 attr_types
    +			core
    +			BASE
    +		contents: SGML_EMPTY
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike same
    +		flags: endO
    +	13:BASEFONT
    +		justify
    +		11 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COLOR
    +			3:0:DIR
    +			4:0:END
    +			5:0:FACE
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SIZE
    +			9:0:STYLE
    +			10:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FONT
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: BRlike APPLETlike HRlike MAPlike same
    +		flags: endO
    +	14:BDO
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike Plike DIVlike
    +		flags:
    +	15:BGSOUND
    +		justify
    +		9 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:LOOP
    +			6:2:SRC
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BGSOUND
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike Plike DIVlike BRlike APPLETlike HRlike same
    +		flags: endO
    +	16:BIG
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	17:BLINK
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	18:BLOCKQUOTE
    +		justify
    +		9 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BQ
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	19:BODY
    +		justify
    +		15 attributes:
    +			0:0:ALINK
    +			1:2:BACKGROUND
    +			2:0:BGCOLOR
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:LINK
    +			9:0:ONLOAD
    +			10:0:ONUNLOAD
    +			11:0:STYLE
    +			12:0:TEXT
    +			13:0:TITLE
    +			14:0:VLINK
    +		4 attr_types
    +			bgcolor
    +			core
    +			i18n
    +			BODY
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		contained: outer BODYlike
    +		icontained: outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer HEADstuff same
    +		flags: endO startO
    +	20:BODYTEXT
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DATA
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NAME
    +			7:0:OBJECT
    +			8:0:REF
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +			13:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			BODYTEXT
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same DELlike
    +		contained: DIVlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike BRlike APPLETlike HRlike MAPlike same
    +		flags: endO startO
    +	21:BQ
    +		justify
    +		9 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BQ
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	22:BR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	23:BUTTON
    +		justify
    +		17 attributes:
    +			0:0:ACCESSKEY
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DISABLED
    +			5:0:FORMACTION
    +			6:1:ID
    +			7:0:LANG
    +			8:0:NAME
    +			9:0:ONBLUR
    +			10:0:ONFOCUS
    +			11:0:READONLY
    +			12:0:STYLE
    +			13:0:TABINDEX
    +			14:0:TITLE
    +			15:0:TYPE
    +			16:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			BUTTON
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike formula TRlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike BRlike same
    +		flags:
    +	24:CAPTION
    +		justify
    +		9 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALIGN
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:STYLE
    +			8:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			CAPTION
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike ULlike APPLETlike HRlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike same
    +		flags:
    +	25:CENTER
    +		justify
    +		8 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			DIV
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags:
    +	26:CITE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags: nreie
    +	27:CODE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags:
    +	28:COL
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SPAN
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:VALIGN
    +			12:0:WIDTH
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			COL
    +		contents: SGML_EMPTY
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: TRlike ULlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags: endO
    +	29:COLGROUP
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SPAN
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:VALIGN
    +			12:0:WIDTH
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			COL
    +		contents: SGML_EMPTY
    +		tagclass: TRlike
    +		contains: HRlike
    +		icontains: HRlike
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike MAPlike same
    +		flags: endO
    +	30:COMMENT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: MATHlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike
    +		flags:
    +	31:CREDIT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike ULlike APPLETlike HRlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike Plike DIVlike same
    +		flags:
    +	32:DD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	33:DEL
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	34:DEL!
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: DELlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	35:DFN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	36:DIR
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike HRlike same
    +		flags:
    +	37:DIV
    +		justify
    +		8 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			DIV
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike DIVlike same
    +		flags: mafse
    +	38:DL
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			DL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula SELECTlike Plike DIVlike LIlike
    +		flags:
    +	39:DLC
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			DL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike
    +		flags:
    +	40:DT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike BRlike APPLETlike MAPlike
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	41:EM
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags: nreie
    +	42:EMBED
    +		justify
    +		21 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:HEIGHT
    +			7:1:ID
    +			8:0:IMAGEMAP
    +			9:0:ISMAP
    +			10:0:LANG
    +			11:0:MD
    +			12:1:NAME
    +			13:0:NOFLOW
    +			14:0:PARAMS
    +			15:2:SRC
    +			16:0:STYLE
    +			17:0:TITLE
    +			18:0:UNITS
    +			19:0:USEMAP
    +			20:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			EMBED
    +		contents: SGML_EMPTY
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Plike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		flags: endO
    +	43:FIELDSET
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike APPLETlike HRlike same DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike MAPlike same
    +		flags:
    +	44:FIG
    +		justify
    +		17 attributes:
    +			0:0:ALIGN
    +			1:0:BORDER
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:0:HEIGHT
    +			6:1:ID
    +			7:0:IMAGEMAP
    +			8:0:ISOBJECT
    +			9:0:LANG
    +			10:0:MD
    +			11:0:NOFLOW
    +			12:2:SRC
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:UNITS
    +			16:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			FIG
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike Plike DIVlike MAPlike same
    +		flags:
    +	45:FIGURE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	46:FN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike Plike BRlike same
    +		flags:
    +	47:FONT
    +		justify
    +		11 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COLOR
    +			3:0:DIR
    +			4:0:END
    +			5:0:FACE
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SIZE
    +			9:0:STYLE
    +			10:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FONT
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	48:FOOTER
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	49:FORM
    +		justify
    +		17 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:2:ACTION
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:ENCTYPE
    +			7:1:ID
    +			8:0:LANG
    +			9:0:METHOD
    +			10:0:ONRESET
    +			11:0:ONSUBMIT
    +			12:0:SCRIPT
    +			13:0:STYLE
    +			14:0:SUBJECT
    +			15:0:TARGET
    +			16:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FORM
    +		contents: SGML_EMPTY
    +		tagclass: FORMlike
    +		contains: FONTlike EMlike MATHlike Alike TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Plike DIVlike LIlike ULlike APPLETlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike LIlike ULlike MAPlike same
    +		flags:
    +	50:FRAME
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:FRAMEBORDER
    +			2:1:ID
    +			3:2:LONGDESC
    +			4:0:MARGINHEIGHT
    +			5:0:MARGINWIDTH
    +			6:0:NAME
    +			7:0:NORESIZE
    +			8:0:SCROLLING
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +		2 attr_types
    +			core
    +			FRAME
    +		contents: SGML_EMPTY
    +		tagclass: outer
    +		contains:
    +		icontains:
    +		contained: outer
    +		icontained: outer
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags: endO
    +	51:FRAMESET
    +		justify
    +		4 attributes:
    +			0:0:COLS
    +			1:0:ONLOAD
    +			2:0:ONUNLOAD
    +			3:0:ROWS
    +		1 attr_types
    +			FRAMESET
    +		contents: SGML_MIXED
    +		tagclass: outer
    +		contains: outer same
    +		icontains: outer same
    +		contained: outer same
    +		icontained: BRlike APPLETlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags:
    +	52:H1
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	53:H2
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	54:H3
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	55:H4
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	56:H5
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	57:H6
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	58:HEAD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: HEADstuff
    +		contains: BRlike APPLETlike HRlike MAPlike HEADstuff
    +		icontains: BRlike APPLETlike HRlike HEADstuff
    +		contained: outer
    +		icontained: outer
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags: endO startO mafse
    +	59:HEADER
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	60:HR
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:MD
    +			7:0:NOSHADE
    +			8:0:SIZE
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			HR
    +		contents: SGML_EMPTY
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike formula TRlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		flags: endO
    +	61:HTML
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: outer
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		contained:
    +		icontained:
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		flags: endO startO
    +	62:HY
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	63:I
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	64:IFRAME
    +		justify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:FRAMEBORDER
    +			3:0:HEIGHT
    +			4:1:ID
    +			5:2:LONGDESC
    +			6:0:MARGINHEIGHT
    +			7:0:MARGINWIDTH
    +			8:0:NAME
    +			9:0:SCROLLING
    +			10:2:SRC
    +			11:0:STYLE
    +			12:0:TITLE
    +			13:0:WIDTH
    +		3 attr_types
    +			align
    +			core
    +			IFRAME
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike BRlike APPLETlike outer HEADstuff same
    +		flags:
    +	65:IMG
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:HEIGHT
    +			7:0:HSPACE
    +			8:1:ID
    +			9:0:ISMAP
    +			10:0:ISOBJECT
    +			11:0:LANG
    +			12:2:LONGDESC
    +			13:0:MD
    +			14:0:NAME
    +			15:2:SRC
    +			16:0:STYLE
    +			17:0:TITLE
    +			18:0:UNITS
    +			19:2:USEMAP
    +			20:0:VSPACE
    +			21:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			IMG
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: same
    +		flags: endO
    +	66:INPUT
    +		justify
    +		35 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCESSKEY
    +			3:0:ALIGN
    +			4:0:ALT
    +			5:0:CHECKED
    +			6:4:CLASS
    +			7:0:CLEAR
    +			8:0:DIR
    +			9:0:DISABLED
    +			10:0:ERROR
    +			11:0:HEIGHT
    +			12:1:ID
    +			13:0:ISMAP
    +			14:0:LANG
    +			15:0:MAX
    +			16:0:MAXLENGTH
    +			17:0:MD
    +			18:0:MIN
    +			19:0:NAME
    +			20:0:NOTAB
    +			21:0:ONBLUR
    +			22:0:ONCHANGE
    +			23:0:ONFOCUS
    +			24:0:ONSELECT
    +			25:0:READONLY
    +			26:0:SIZE
    +			27:2:SRC
    +			28:0:STYLE
    +			29:0:TABINDEX
    +			30:0:TITLE
    +			31:0:TYPE
    +			32:0:USEMAP
    +			33:0:VALUE
    +			34:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			INPUT
    +		contents: SGML_EMPTY
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike BRlike MAPlike same
    +		flags: endO
    +	67:INS
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	68:INS!
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: DELlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	69:ISINDEX
    +		justify
    +		9 attributes:
    +			0:2:ACTION
    +			1:4:CLASS
    +			2:0:DIR
    +			3:2:HREF
    +			4:1:ID
    +			5:0:LANG
    +			6:0:PROMPT
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			ISINDEX
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike same
    +		flags: endO
    +	70:KBD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike
    +		flags:
    +	71:KEYGEN
    +		justify
    +		8 attributes:
    +			0:0:CHALLENGE
    +			1:4:CLASS
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:NAME
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			KEYGEN
    +		contents: SGML_EMPTY
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: formula TRlike SELECTlike same
    +		flags: endO
    +	72:LABEL
    +		justify
    +		11 attributes:
    +			0:0:ACCESSKEY
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:FOR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:ONBLUR
    +			8:0:ONFOCUS
    +			9:0:STYLE
    +			10:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LABEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	73:LEGEND
    +		justify
    +		9 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALIGN
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:STYLE
    +			8:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			CAPTION
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike
    +		flags:
    +	74:LH
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	75:LI
    +		justify
    +		13 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DINGBAT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:MD
    +			7:0:SKIP
    +			8:2:SRC
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LI
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	76:LINK
    +		justify
    +		14 attributes:
    +			0:0:CHARSET
    +			1:4:CLASS
    +			2:0:DIR
    +			3:2:HREF
    +			4:0:HREFLANG
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MEDIA
    +			8:0:REL
    +			9:0:REV
    +			10:0:STYLE
    +			11:0:TARGET
    +			12:0:TITLE
    +			13:0:TYPE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LINK
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		flags: endO
    +	77:LISTING
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: ULlike
    +		contains:
    +		icontains:
    +		contained: DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike ULlike same
    +		flags:
    +	78:MAIN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	79:MAP
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:1:NAME
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			MAP
    +		contents: SGML_MIXED
    +		tagclass: MAPlike
    +		contains: MAPlike
    +		icontains: MAPlike
    +		contained: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike LIlike
    +		flags:
    +	80:MARQUEE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: HRlike
    +		contains: FONTlike EMlike MATHlike Alike
    +		icontains: FONTlike EMlike MATHlike Alike formula BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: MATHlike Alike formula BRlike APPLETlike same
    +		flags:
    +	81:MATH
    +		justify
    +		8 attributes:
    +			0:0:BOX
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			MATH
    +		contents: SGML_LITTERAL
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike Alike formula
    +		flags:
    +	82:MENU
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags:
    +	83:META
    +		justify
    +		5 attributes:
    +			0:0:CHARSET
    +			1:0:CONTENT
    +			2:0:HTTP-EQUIV
    +			3:0:NAME
    +			4:0:SCHEME
    +		1 attr_types
    +			META
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		flags: endO
    +	84:NAV
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	85:NEXTID
    +		justify
    +		1 attributes:
    +			0:0:N
    +		1 attr_types
    +			NEXTID
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		canclose: FONTlike
    +		flags: endO
    +	86:NOFRAMES
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike DELlike
    +		contained: BRlike APPLETlike HRlike outer
    +		icontained: BRlike APPLETlike HRlike outer
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike
    +		flags:
    +	87:NOTE
    +		justify
    +		10 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:MD
    +			6:8:ROLE
    +			7:2:SRC
    +			8:0:STYLE
    +			9:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			NOTE
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: formula TRlike FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	88:OBJECT
    +		justify
    +		26 attributes:
    +			0:0:ALIGN
    +			1:0:ARCHIVE
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:2:CLASSID
    +			5:2:CODEBASE
    +			6:0:CODETYPE
    +			7:2:DATA
    +			8:0:DECLARE
    +			9:0:DIR
    +			10:0:HEIGHT
    +			11:0:HSPACE
    +			12:1:ID
    +			13:0:ISMAP
    +			14:0:LANG
    +			15:0:NAME
    +			16:0:NOTAB
    +			17:0:SHAPES
    +			18:0:STANDBY
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TITLE
    +			22:0:TYPE
    +			23:2:USEMAP
    +			24:0:VSPACE
    +			25:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			OBJECT
    +		contents: SGML_LITTERAL
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike BRlike APPLETlike same
    +		flags: frecyc
    +	89:OL
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:CONTINUE
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:SEQNUM
    +			8:0:START
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			OL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags:
    +	90:OPTION
    +		justify
    +		13 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:0:DISABLED
    +			4:0:ERROR
    +			5:1:ID
    +			6:0:LABEL
    +			7:0:LANG
    +			8:0:SELECTED
    +			9:0:SHAPE
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			OPTION
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: SELECTlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: endO
    +	91:OVERLAY
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:HEIGHT
    +			2:1:ID
    +			3:0:IMAGEMAP
    +			4:0:MD
    +			5:2:SRC
    +			6:0:STYLE
    +			7:0:TITLE
    +			8:0:UNITS
    +			9:0:WIDTH
    +			10:0:X
    +			11:0:Y
    +		2 attr_types
    +			core
    +			OVERLAY
    +		contents: SGML_EMPTY
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: DIVlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike same
    +		flags: endO
    +	92:P
    +		justify
    +		9 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			P
    +		contents: SGML_EMPTY
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags: endO
    +	93:PARAM
    +		justify
    +		18 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCEPT-ENCODING
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DATA
    +			6:0:DIR
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NAME
    +			10:0:OBJECT
    +			11:0:REF
    +			12:0:STYLE
    +			13:0:TITLE
    +			14:0:TYPE
    +			15:0:VALUE
    +			16:0:VALUEREF
    +			17:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			PARAM
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: Plike LIlike BRlike APPLETlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: TRlike SELECTlike Plike LIlike BRlike same
    +		flags: endO
    +	94:PLAINTEXT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: outer
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff same
    +		contained: outer same
    +		icontained: outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		flags: endO
    +	95:PRE
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike HRlike MAPlike
    +		icontains: EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags:
    +	96:Q
    +		justify
    +		8 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			Q
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	97:S
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	98:SAMP
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags: nreie
    +	99:SCRIPT
    +		justify
    +		16 attributes:
    +			0:0:CHARSET
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DEFER
    +			4:0:DIR
    +			5:0:EVENT
    +			6:0:FOR
    +			7:1:ID
    +			8:0:LANG
    +			9:0:LANGUAGE
    +			10:0:NAME
    +			11:0:SCRIPTENGINE
    +			12:2:SRC
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			SCRIPT
    +		contents: SGML_LITTERAL
    +		tagclass: APPLETlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike BRlike APPLETlike HRlike same
    +		flags:
    +	100:SECTION
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	101:SELECT
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DISABLED
    +			5:0:ERROR
    +			6:0:HEIGHT
    +			7:1:ID
    +			8:0:LANG
    +			9:0:MD
    +			10:0:MULTIPLE
    +			11:0:NAME
    +			12:0:NOTAB
    +			13:0:ONBLUR
    +			14:0:ONCHANGE
    +			15:0:ONFOCUS
    +			16:0:SIZE
    +			17:0:STYLE
    +			18:0:TABINDEX
    +			19:0:TITLE
    +			20:0:UNITS
    +			21:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			SELECT
    +		contents: SGML_MIXED
    +		tagclass: SELECTlike
    +		contains: MAPlike
    +		icontains: MAPlike
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike same
    +		flags: strict
    +	102:SHY
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	103:SMALL
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	104:SPAN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike same
    +		flags:
    +	105:SPOT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: Alike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: Alike
    +		flags: endO
    +	106:STRIKE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	107:STRONG
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags: nreie
    +	108:STYLE
    +		justify
    +		9 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:MEDIA
    +			5:0:NOTATION
    +			6:0:STYLE
    +			7:0:TITLE
    +			8:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			STYLE
    +		contents: SGML_LITTERAL
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula same
    +		flags:
    +	109:SUB
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	110:SUP
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	111:TAB
    +		justify
    +		11 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DP
    +			5:1:ID
    +			6:0:INDENT
    +			7:0:LANG
    +			8:0:STYLE
    +			9:0:TITLE
    +			10:0:TO
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			TAB
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer HEADstuff
    +		canclose: FONTlike
    +		flags: endO
    +	112:TABLE
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:2:BACKGROUND
    +			2:0:BORDER
    +			3:0:CELLPADDING
    +			4:0:CELLSPACING
    +			5:4:CLASS
    +			6:0:CLEAR
    +			7:0:COLS
    +			8:0:COLSPEC
    +			9:0:DIR
    +			10:0:DP
    +			11:0:FRAME
    +			12:1:ID
    +			13:0:LANG
    +			14:0:NOFLOW
    +			15:0:NOWRAP
    +			16:0:RULES
    +			17:0:STYLE
    +			18:0:SUMMARY
    +			19:0:TITLE
    +			20:0:UNITS
    +			21:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			TABLE
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: TRlike SELECTlike FORMlike Plike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike LIlike HRlike MAPlike same
    +		flags:
    +	113:TBODY
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_EMPTY
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO startO
    +	114:TD
    +		justify
    +		23 attributes:
    +			0:0:ABBR
    +			1:0:ALIGN
    +			2:0:AXES
    +			3:0:AXIS
    +			4:2:BACKGROUND
    +			5:0:CHAR
    +			6:0:CHAROFF
    +			7:4:CLASS
    +			8:0:CLEAR
    +			9:0:COLSPAN
    +			10:0:DIR
    +			11:0:DP
    +			12:0:HEADERS
    +			13:0:HEIGHT
    +			14:1:ID
    +			15:0:LANG
    +			16:0:NOWRAP
    +			17:0:ROWSPAN
    +			18:0:SCOPE
    +			19:0:STYLE
    +			20:0:TITLE
    +			21:0:VALIGN
    +			22:0:WIDTH
    +		4 attr_types
    +			cellalign
    +			core
    +			i18n
    +			TD
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO
    +	115:TEXTAREA
    +		justify
    +		22 attributes:
    +			0:0:ACCEPT-CHARSET
    +			1:0:ACCESSKEY
    +			2:0:ALIGN
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:COLS
    +			6:0:DIR
    +			7:0:DISABLED
    +			8:0:ERROR
    +			9:1:ID
    +			10:0:LANG
    +			11:0:NAME
    +			12:0:NOTAB
    +			13:0:ONBLUR
    +			14:0:ONCHANGE
    +			15:0:ONFOCUS
    +			16:0:ONSELECT
    +			17:0:READONLY
    +			18:0:ROWS
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			TEXTAREA
    +		contents: SGML_LITTERAL
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike same
    +		flags: nolyspcl
    +	116:TEXTFLOW
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DATA
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NAME
    +			7:0:OBJECT
    +			8:0:REF
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +			13:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			BODYTEXT
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same DELlike
    +		contained: formula TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer same
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike BRlike APPLETlike MAPlike outer same
    +		flags: endO startO
    +	117:TFOOT
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_EMPTY
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	118:TH
    +		justify
    +		23 attributes:
    +			0:0:ABBR
    +			1:0:ALIGN
    +			2:0:AXES
    +			3:0:AXIS
    +			4:2:BACKGROUND
    +			5:0:CHAR
    +			6:0:CHAROFF
    +			7:4:CLASS
    +			8:0:CLEAR
    +			9:0:COLSPAN
    +			10:0:DIR
    +			11:0:DP
    +			12:0:HEADERS
    +			13:0:HEIGHT
    +			14:1:ID
    +			15:0:LANG
    +			16:0:NOWRAP
    +			17:0:ROWSPAN
    +			18:0:SCOPE
    +			19:0:STYLE
    +			20:0:TITLE
    +			21:0:VALIGN
    +			22:0:WIDTH
    +		4 attr_types
    +			cellalign
    +			core
    +			i18n
    +			TD
    +		contents: SGML_EMPTY
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: TRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	119:THEAD
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_EMPTY
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	120:TITLE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_RCDATA
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike
    +		flags: mafse strict
    +	121:TR
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_EMPTY
    +		tagclass: TRlike
    +		contains: LIlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO
    +	122:TT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: nreie
    +	123:U
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	124:UL
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike same
    +		flags:
    +	125:VAR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	126:WBR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	127:XMP
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: ULlike
    +		contains:
    +		icontains:
    +		contained: TRlike SELECTlike FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike MAPlike
    +		flags: endO
    diff --git a/WWW/Library/Implementation/src1_HTMLDTD.h b/WWW/Library/Implementation/src1_HTMLDTD.h
    new file mode 100644
    index 0000000..39c9594
    --- /dev/null
    +++ b/WWW/Library/Implementation/src1_HTMLDTD.h
    @@ -0,0 +1,2478 @@
    +/* $LynxId: src1_HTMLDTD.h,v 1.49 2022/09/30 00:03:49 tom Exp $ */
    +#ifndef src_HTMLDTD_H1
    +#define src_HTMLDTD_H1 1
    +
    +#ifndef once_HTMLDTD
    +#define once_HTMLDTD 1
    +
    +#define T_A             0x00008,0x0B007,0x0FF17,0x37787,0x77BA7,0x8604F,0x00014
    +#define T_ABBR          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_ACRONYM       0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_ADDRESS       0x00200,0x0F14F,0x8FFFF,0x136680,0x1B6FAF,0x80317,0x00000
    +#define T_APPLET        0x02000,0x0B0CF,0x8FFFF,0x37F9F,0xB7FBF,0x8300F,0x00000
    +#define T_AREA          0x08000,0x00000,0x00000,0x08000,0x3FFFF,0x00F1F,0x00001
    +#define T_ARTICLE       0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_ASIDE         0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_AU            0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_AUTHOR        0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00003,0x00000
    +#define T_B             0x00001,0x8B04F,0xAFFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_BANNER        0x00200,0x0FB8F,0x0FFFF,0x130000,0x130000,0x8031F,0x00000
    +#define T_BASE          0x40000,0x00000,0x00000,0x50000,0x50000,0x8000F,0x00001
    +#define T_BASEFONT      0x01000,0x00000,0x00000,0x377AF,0x37FAF,0x8F000,0x00001
    +#define T_BDO           0x00100,0x0B04F,0x8FFFF,0x136680,0x1B6FAF,0x0033F,0x00000
    +#define T_BGSOUND       0x01000,0x00000,0x00000,0x777AF,0x77FAF,0x8730F,0x00001
    +#define T_BIG           0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_BLINK         0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00001,0x00014
    +#define T_BLOCKQUOTE    0x00200,0xAFBCF,0xAFFFF,0x1B6680,0x1B6FAF,0x8031F,0x00000
    +#define T_BODY          0x20000,0x12FB8F,0x12FFFF,0x30000,0x30000,0xDFF7F,0x00003
    +#define T_BODYTEXT      0x20000,0x10FB8F,0x1AFFFF,0x30200,0xB7FAF,0x8F17F,0x00003
    +#define T_BQ            0x00200,0xAFBCF,0xAFFFF,0x1B6680,0x1B6FAF,0x8031F,0x00000
    +#define T_BR            0x01000,0x00000,0x00000,0x377BF,0x77FBF,0x8101F,0x00001
    +#define T_BUTTON        0x02000,0x0BB07,0x0FF37,0x0378F,0x37FBF,0x8115F,0x00000
    +#define T_CAPTION       0x00100,0x0B04F,0x8FFFF,0x106A00,0x1B6FA7,0x8035F,0x00000
    +#define T_CENTER        0x00200,0x8FBCF,0x8FFFF,0x1B6680,0x1B6FA7,0x8071F,0x00000
    +#define T_CITE          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00010
    +#define T_CODE          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00000
    +#define T_COL           0x04000,0x00000,0x00000,0x00820,0x36FA7,0x88F5F,0x00001
    +#define T_COLGROUP      0x00020,0x04000,0x04000,0x00800,0x36FA7,0x8875F,0x00001
    +#define T_COMMENT       0x00004,0x00000,0x00000,0xA77AF,0x7FFFF,0x00003,0x00000
    +#define T_CREDIT        0x00100,0x0B04F,0x8FFFF,0x106A00,0x1B7FBF,0x8030F,0x00000
    +#define T_DD            0x00400,0x0FBCF,0x8FFFF,0x00800,0xB6FFF,0x8071F,0x00001
    +#define T_DEL           0x00002,0x8B04F,0x8FEFF,0x87F8F,0xF7FBF,0x100003,0x00000
    +#define T_DEL_2         0x100000,0x8BBCF,0x8FFFF,0xA7F8F,0xF7FBF,0x100003,0x00000
    +#define T_DFN           0x00002,0x8B0CF,0x8FFFF,0x8778F,0xF7FBF,0x00003,0x00000
    +#define T_DIR           0x00800,0x0B400,0x0F75F,0x137680,0x136FB7,0x84F7F,0x00000
    +#define T_DIV           0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x80317,0x00004
    +#define T_DL            0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FB7,0x00757,0x00000
    +#define T_DLC           0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FB7,0x0075F,0x00000
    +#define T_DT            0x00400,0x0B04F,0x0B1FF,0x00800,0x17FFF,0x8071F,0x00001
    +#define T_EM            0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00010
    +#define T_EMBED         0x02000,0x8F107,0x8FFF7,0xB6FBF,0xB7FBF,0x1FF7F,0x00001
    +#define T_FIELDSET      0x00200,0x8FB4F,0x8FF7F,0x186787,0x1B7FF7,0x8805F,0x00000
    +#define T_FIG           0x00200,0x0FB00,0x8FFFF,0x136680,0x1B6FBF,0x8834F,0x00000
    +#define T_FIGURE        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_FN            0x00200,0x8FBCF,0x8FFFF,0x1B6680,0x1B7EBF,0x8114F,0x00000
    +#define T_FONT          0x00001,0x8B04F,0x8FFFF,0xB778F,0xF7FBF,0x00001,0x00014
    +#define T_FOOTER        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_FORM          0x00080,0x0FF6F,0x0FF7F,0x136E07,0x132F07,0x88DFF,0x00000
    +#define T_FRAME         0x10000,0x00000,0x00000,0x10000,0x10000,0x9FFFF,0x00001
    +#define T_FRAMESET      0x10000,0x90000,0x90000,0x90000,0x93000,0x9FFFF,0x00000
    +#define T_H1            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H2            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H3            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H4            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H5            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_H6            0x00100,0x0B04F,0x0B05F,0x136680,0x137FAF,0x80117,0x00000
    +#define T_HEAD          0x40000,0x4F000,0x47000,0x10000,0x10000,0x9FF7F,0x00007
    +#define T_HEADER        0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_HR            0x04000,0x00000,0x00000,0x3FE80,0x3FFBF,0x87F37,0x00001
    +#define T_HTML          0x10000,0x7FB8F,0x7FFFF,0x00000,0x00000,0x1FFFF,0x00003
    +#define T_HY            0x01000,0x00000,0x00000,0x3779F,0x77FBF,0x8101F,0x00001
    +#define T_I             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_IFRAME        0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0xD315F,0x00000
    +#define T_IMG           0x01000,0x00000,0x00000,0x3779F,0x37FBF,0x80000,0x00001
    +#define T_INPUT         0x00040,0x00000,0x00000,0x03F87,0x37F87,0x8904F,0x00001
    +#define T_INS           0x00002,0x8B04F,0x8FEFF,0x87F8F,0xF7FBF,0x100003,0x00000
    +#define T_INS_2         0x100000,0x8BBCF,0x8FFFF,0xA7F8F,0xF7FBF,0x100003,0x00000
    +#define T_ISINDEX       0x08000,0x00000,0x00000,0x7778F,0x7FFAF,0x80007,0x00001
    +#define T_KBD           0x00002,0x00000,0x00000,0x2778F,0x77FBF,0x00003,0x00000
    +#define T_KEYGEN        0x00040,0x00000,0x00000,0x07FB7,0x37FB7,0x80070,0x00001
    +#define T_LABEL         0x00002,0x0304F,0x0FFFF,0x0679F,0x36FBF,0x00007,0x00000
    +#define T_LEGEND        0x00002,0x0B04F,0x8FF7F,0x00200,0xB7FA7,0x00003,0x00000
    +#define T_LH            0x00400,0x0BB7F,0x8FFFF,0x00800,0x97FFF,0x8071F,0x00001
    +#define T_LI            0x00400,0x0BBFF,0x8FFFF,0x00800,0x97FFF,0x8071F,0x00001
    +#define T_LINK          0x08000,0x00000,0x00000,0x50000,0x50000,0x0FF7F,0x00001
    +#define T_LISTING       0x00800,0x00000,0x00000,0x136600,0x136F00,0x80F1F,0x00000
    +#define T_MAIN          0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_MAP           0x08000,0x08000,0x08000,0x37FCF,0x37FBF,0x0051F,0x00000
    +#define T_MARQUEE       0x04000,0x0000F,0x8F01F,0x37787,0xB7FA7,0x8301C,0x00000
    +#define T_MATH          0x00004,0x0B05F,0x8FFFF,0x2778F,0xF7FBF,0x0001F,0x00000
    +#define T_MENU          0x00800,0x0B400,0x0F75F,0x117680,0x136FB7,0x88F7F,0x00000
    +#define T_META          0x08000,0x00000,0x00000,0x50000,0x50000,0x0FF7F,0x00001
    +#define T_NAV           0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_NEXTID        0x01000,0x00000,0x00000,0x50000,0x1FFF7,0x00001,0x00001
    +#define T_NOFRAMES      0x20000,0x12FB8F,0x10FFFF,0x17000,0x17000,0x0CF5F,0x00000
    +#define T_NOTE          0x00200,0x0BBAF,0x8FFFF,0x1376B0,0x1B7FFF,0x8031F,0x00000
    +#define T_OBJECT        0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0x83D5F,0x00020
    +#define T_OL            0x00800,0x0C400,0x8FFFF,0x137680,0x1B7FB7,0x88F7F,0x00000
    +#define T_OPTION        0x08000,0x00000,0x00000,0x00040,0x37FFF,0x8031F,0x00001
    +#define T_OVERLAY       0x04000,0x00000,0x00000,0x00200,0x37FBF,0x83F7F,0x00001
    +#define T_P             0x00100,0x0B04F,0x8FFFF,0x136680,0x1B6FA7,0x80117,0x00001
    +#define T_PARAM         0x01000,0x00000,0x00000,0x33500,0x37FFF,0x81560,0x00001
    +#define T_PLAINTEXT     0x10000,0xFFFFF,0xFFFFF,0x90000,0x90000,0x3FFFF,0x00001
    +#define T_PRE           0x00200,0x0F04F,0x0F05E,0x136680,0x136FF0,0x8071E,0x00000
    +#define T_Q             0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00000
    +#define T_S             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_SAMP          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00002,0x00010
    +#define T_SCRIPT        0x02000,0x00000,0x00000,0x77F9F,0x77FFF,0x87D5F,0x00000
    +#define T_SECTION       0x00200,0x8FBCF,0x8FFFF,0x1B66A0,0x1B7FFF,0x8031F,0x00004
    +#define T_SELECT        0x00040,0x08000,0x08000,0x03FAF,0x33FBF,0x80D5F,0x00008
    +#define T_SHY           0x01000,0x00000,0x00000,0x3779F,0x77FBF,0x8101F,0x00001
    +#define T_SMALL         0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_SPAN          0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x80003,0x00000
    +#define T_SPOT          0x00008,0x00000,0x00000,0x3FFF7,0x3FFF7,0x00008,0x00001
    +#define T_STRIKE        0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_STRONG        0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FAF,0x00003,0x00010
    +#define T_STYLE         0x40000,0x00000,0x00000,0x7638F,0x76FAF,0x8001F,0x00000
    +#define T_SUB           0x00004,0x8B05F,0x8FFFF,0x8779F,0xF7FBF,0x00007,0x00000
    +#define T_SUP           0x00004,0x8B05F,0x8FFFF,0x8779F,0xF7FBF,0x00007,0x00000
    +#define T_TAB           0x01000,0x00000,0x00000,0x3778F,0x57FAF,0x00001,0x00001
    +#define T_TABLE         0x00800,0x0F1E0,0x8FFFF,0x136680,0x1B6FA7,0x8C57F,0x00000
    +#define T_TBODY         0x00020,0x00020,0x8FFFF,0x00880,0xB7FB7,0x8C75F,0x00003
    +#define T_TD            0x00400,0x0FBCF,0x8FFFF,0x00020,0xB7FB7,0x8C75F,0x00001
    +#define T_TEXTAREA      0x00040,0x00000,0x00000,0x07F8F,0x33FBF,0x80D5F,0x00040
    +#define T_TEXTFLOW      0x20000,0x18FBFF,0x19FFFF,0x977B0,0xB7FB7,0x9B00F,0x00003
    +#define T_TFOOT         0x00020,0x00020,0x8FFFF,0x00800,0xB7FB7,0x8CF5F,0x00001
    +#define T_TH            0x00400,0x0FBCF,0x0FFFF,0x00020,0xB7FB7,0x8CF5F,0x00001
    +#define T_THEAD         0x00020,0x00020,0x8FFFF,0x00800,0xB7FB7,0x8CF5F,0x00001
    +#define T_TITLE         0x40000,0x00000,0x00000,0x50000,0x50000,0x0031F,0x0000C
    +#define T_TR            0x00020,0x00400,0x8FFFF,0x00820,0xB7FB7,0x8C75F,0x00001
    +#define T_TT            0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00010
    +#define T_U             0x00001,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00014
    +#define T_UL            0x00800,0x0C480,0x8FFFF,0x136680,0x1B7FFF,0x8075F,0x00000
    +#define T_VAR           0x00002,0x8B04F,0x8FFFF,0xA778F,0xF7FBF,0x00001,0x00000
    +#define T_WBR           0x00001,0x00000,0x00000,0x3778F,0x77FBF,0x8101F,0x00001
    +#define T_XMP           0x00800,0x00000,0x00000,0x1367E0,0x136FFF,0x0875F,0x00001
    +#define T_OBJECT_PCDATA 0x02000,0x8FBCF,0x8FFFF,0xB679F,0xB6FBF,0x83D5F,0x00008
    +#define T__UNREC_	0x00000,0x00000,0x00000,0x00000,0x00000,0x00000,0x00000
    +#ifdef USE_PRETTYSRC
    +# define N HTMLA_NORMAL
    +# define i HTMLA_ANAME
    +# define h HTMLA_HREF
    +# define c HTMLA_CLASS
    +# define x HTMLA_AUXCLASS
    +# define T(t) , t
    +#else
    +# define T(t)			/*nothing */
    +#endif
    +/* *INDENT-OFF* */
    +
    +#define ATTR_TYPE(name) #name, name##_attr_list
    +
    +/* generic attributes, used in different tags */
    +static const attr core_attr_list[] = {
    +	{ "CLASS"         T(c) },
    +	{ "ID"            T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType core_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ 0, 0 },
    +};
    +
    +static const attr i18n_attr_list[] = {
    +	{ "DIR"           T(N) },
    +	{ "LANG"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType i18n_attr_type[] = {
    +	{ ATTR_TYPE(i18n) },
    +	{ 0, 0 },
    +};
    +
    +static const attr events_attr_list[] = {
    +	{ "ONCLICK"       T(N) },
    +	{ "ONDBLCLICK"    T(N) },
    +	{ "ONKEYDOWN"     T(N) },
    +	{ "ONKEYPRESS"    T(N) },
    +	{ "ONKEYUP"       T(N) },
    +	{ "ONMOUSEDOWN"   T(N) },
    +	{ "ONMOUSEMOVE"   T(N) },
    +	{ "ONMOUSEOUT"    T(N) },
    +	{ "ONMOUSEOVER"   T(N) },
    +	{ "ONMOUSEUP"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType events_attr_type[] = {
    +	{ ATTR_TYPE(events) },
    +	{ 0, 0 },
    +};
    +
    +static const attr align_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType align_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ 0, 0 },
    +};
    +
    +static const attr cellalign_attr_list[] = {
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType cellalign_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ 0, 0 },
    +};
    +
    +static const attr bgcolor_attr_list[] = {
    +	{ "BGCOLOR"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType bgcolor_attr_type[] = {
    +	{ ATTR_TYPE(bgcolor) },
    +	{ 0, 0 },
    +};
    +
    +
    +/* tables defining attributes per-tag in terms of generic attributes (editable) */
    +static const attr A_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CHARSET"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "URN"           T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType A_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(A) },
    +	{ 0, 0 },
    +};
    +
    +static const attr ADDRESS_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType ADDRESS_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(ADDRESS) },
    +	{ 0, 0 },
    +};
    +
    +static const attr APPLET_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "CODE"          T(N) },
    +	{ "CODEBASE"      T(h) },
    +	{ "DOWNLOAD"      T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "NAME"          T(i) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType APPLET_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(APPLET) },
    +	{ 0, 0 },
    +};
    +
    +static const attr AREA_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "HREF"          T(h) },
    +	{ "NOHREF"        T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType AREA_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(AREA) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BASE_attr_list[] = {
    +	{ "HREF"          T(h) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BASE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(BASE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BGSOUND_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "LOOP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BGSOUND_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BGSOUND) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BODY_attr_list[] = {
    +	{ "ALINK"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "LINK"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "TEXT"          T(N) },
    +	{ "VLINK"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BODY_attr_type[] = {
    +	{ ATTR_TYPE(bgcolor) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BODY) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BODYTEXT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BODYTEXT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BODYTEXT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BQ_attr_list[] = {
    +	{ "CITE"          T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BQ_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BQ) },
    +	{ 0, 0 },
    +};
    +
    +static const attr BUTTON_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "FORMACTION"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType BUTTON_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(BUTTON) },
    +	{ 0, 0 },
    +};
    +
    +static const attr CAPTION_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType CAPTION_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(CAPTION) },
    +	{ 0, 0 },
    +};
    +
    +static const attr COL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "SPAN"          T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType COL_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(COL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DEL_attr_list[] = {
    +	{ "CITE"          T(N) },
    +	{ "DATETIME"      T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DEL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DEL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DIV_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DIV_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DIV) },
    +	{ 0, 0 },
    +};
    +
    +static const attr DL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType DL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(DL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr EMBED_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOFLOW"        T(N) },
    +	{ "PARAMS"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType EMBED_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(EMBED) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FIG_attr_list[] = {
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FIG_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FIG) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FONT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COLOR"         T(N) },
    +	{ "END"           T(N) },
    +	{ "FACE"          T(N) },
    +	{ "SIZE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FONT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FONT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FORM_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACTION"        T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "ENCTYPE"       T(N) },
    +	{ "METHOD"        T(N) },
    +	{ "ONRESET"       T(N) },
    +	{ "ONSUBMIT"      T(N) },
    +	{ "SCRIPT"        T(N) },
    +	{ "SUBJECT"       T(N) },
    +	{ "TARGET"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FORM_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(FORM) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FRAME_attr_list[] = {
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NORESIZE"      T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FRAME_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(FRAME) },
    +	{ 0, 0 },
    +};
    +
    +static const attr FRAMESET_attr_list[] = {
    +	{ "COLS"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType FRAMESET_attr_type[] = {
    +	{ ATTR_TYPE(FRAMESET) },
    +	{ 0, 0 },
    +};
    +
    +static const attr GEN_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType GEN_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(GEN) },
    +	{ 0, 0 },
    +};
    +
    +static const attr GEN5_attr_list[] = {
    +	{ "ROLE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType GEN5_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(GEN5) },
    +	{ 0, 0 },
    +};
    +
    +static const attr H_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType H_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(H) },
    +	{ 0, 0 },
    +};
    +
    +static const attr HR_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOSHADE"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType HR_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(HR) },
    +	{ 0, 0 },
    +};
    +
    +static const attr IFRAME_attr_list[] = {
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType IFRAME_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(IFRAME) },
    +	{ 0, 0 },
    +};
    +
    +static const attr IMG_attr_list[] = {
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType IMG_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(IMG) },
    +	{ 0, 0 },
    +};
    +
    +static const attr INPUT_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CHECKED"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "MAX"           T(N) },
    +	{ "MAXLENGTH"     T(N) },
    +	{ "MD"            T(N) },
    +	{ "MIN"           T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType INPUT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(INPUT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr ISINDEX_attr_list[] = {
    +	{ "ACTION"        T(h) },
    +	{ "HREF"          T(h) },
    +	{ "PROMPT"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType ISINDEX_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(ISINDEX) },
    +	{ 0, 0 },
    +};
    +
    +static const attr KEYGEN_attr_list[] = {
    +	{ "CHALLENGE"     T(N) },
    +	{ "NAME"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType KEYGEN_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(KEYGEN) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LABEL_attr_list[] = {
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LABEL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LABEL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LI_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LI_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LI) },
    +	{ 0, 0 },
    +};
    +
    +static const attr LINK_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType LINK_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(LINK) },
    +	{ 0, 0 },
    +};
    +
    +static const attr MAP_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NAME"          T(i) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType MAP_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(MAP) },
    +	{ 0, 0 },
    +};
    +
    +static const attr MATH_attr_list[] = {
    +	{ "BOX"           T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType MATH_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(MATH) },
    +	{ 0, 0 },
    +};
    +
    +static const attr META_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "CONTENT"       T(N) },
    +	{ "HTTP-EQUIV"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCHEME"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType META_attr_type[] = {
    +	{ ATTR_TYPE(META) },
    +	{ 0, 0 },
    +};
    +
    +static const attr NEXTID_attr_list[] = {
    +	{ "N"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType NEXTID_attr_type[] = {
    +	{ ATTR_TYPE(NEXTID) },
    +	{ 0, 0 },
    +};
    +
    +static const attr NOTE_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "MD"            T(N) },
    +	{ "ROLE"          T(x) },
    +	{ "SRC"           T(h) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType NOTE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(NOTE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OBJECT_attr_list[] = {
    +	{ "ARCHIVE"       T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASSID"       T(h) },
    +	{ "CODEBASE"      T(h) },
    +	{ "CODETYPE"      T(N) },
    +	{ "DATA"          T(h) },
    +	{ "DECLARE"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "SHAPES"        T(N) },
    +	{ "STANDBY"       T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OBJECT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OBJECT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "CONTINUE"      T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "START"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OL) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OPTION_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "LABEL"         T(N) },
    +	{ "SELECTED"      T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OPTION_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(OPTION) },
    +	{ 0, 0 },
    +};
    +
    +static const attr OVERLAY_attr_list[] = {
    +	{ "HEIGHT"        T(N) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "SRC"           T(h) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ "X"             T(N) },
    +	{ "Y"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType OVERLAY_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(OVERLAY) },
    +	{ 0, 0 },
    +};
    +
    +static const attr P_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType P_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(P) },
    +	{ 0, 0 },
    +};
    +
    +static const attr PARAM_attr_list[] = {
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCEPT-ENCODING" T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUEREF"      T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType PARAM_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(PARAM) },
    +	{ 0, 0 },
    +};
    +
    +static const attr Q_attr_list[] = {
    +	{ "CITE"          T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType Q_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(Q) },
    +	{ 0, 0 },
    +};
    +
    +static const attr SCRIPT_attr_list[] = {
    +	{ "CHARSET"       T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "DEFER"         T(N) },
    +	{ "EVENT"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "LANGUAGE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCRIPTENGINE"  T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType SCRIPT_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(SCRIPT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr SELECT_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "MD"            T(N) },
    +	{ "MULTIPLE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType SELECT_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(SELECT) },
    +	{ 0, 0 },
    +};
    +
    +static const attr STYLE_attr_list[] = {
    +	{ "MEDIA"         T(N) },
    +	{ "NOTATION"      T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType STYLE_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(STYLE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TAB_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DP"            T(N) },
    +	{ "INDENT"        T(N) },
    +	{ "TO"            T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TAB_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TAB) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TABLE_attr_list[] = {
    +	{ "BACKGROUND"    T(h) },
    +	{ "BORDER"        T(N) },
    +	{ "CELLPADDING"   T(N) },
    +	{ "CELLSPACING"   T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "COLSPEC"       T(N) },
    +	{ "DP"            T(N) },
    +	{ "FRAME"         T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "RULES"         T(N) },
    +	{ "SUMMARY"       T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TABLE_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TABLE) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TD_attr_list[] = {
    +	{ "ABBR"          T(N) },
    +	{ "AXES"          T(N) },
    +	{ "AXIS"          T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLSPAN"       T(N) },
    +	{ "DP"            T(N) },
    +	{ "HEADERS"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "ROWSPAN"       T(N) },
    +	{ "SCOPE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TD_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TD) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TEXTAREA_attr_list[] = {
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TEXTAREA_attr_type[] = {
    +	{ ATTR_TYPE(align) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TEXTAREA) },
    +	{ 0, 0 },
    +};
    +
    +static const attr TR_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "DP"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType TR_attr_type[] = {
    +	{ ATTR_TYPE(cellalign) },
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(events) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(TR) },
    +	{ 0, 0 },
    +};
    +
    +static const attr UL_attr_list[] = {
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "MD"            T(N) },
    +	{ "PLAIN"         T(N) },
    +	{ "SRC"           T(h) },
    +	{ "TYPE"          T(N) },
    +	{ "WRAP"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const AttrType UL_attr_type[] = {
    +	{ ATTR_TYPE(core) },
    +	{ ATTR_TYPE(i18n) },
    +	{ ATTR_TYPE(UL) },
    +	{ 0, 0 },
    +};
    +
    +
    +/* attribute lists for the runtime (generated by dtd_util) */
    +static const attr A_attr[] = {          /* A attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "URN"           T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr ADDRESS_attr[] = {    /* ADDRESS attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr APPLET_attr[] = {     /* APPLET attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "CODE"          T(N) },
    +	{ "CODEBASE"      T(h) },
    +	{ "DIR"           T(N) },
    +	{ "DOWNLOAD"      T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr AREA_attr[] = {       /* AREA attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COORDS"        T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOHREF"        T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BASE_attr[] = {       /* BASE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BGSOUND_attr[] = {    /* BGSOUND attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LOOP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BODY_attr[] = {       /* BODY attributes */
    +	{ "ALINK"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "BGCOLOR"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LINK"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TEXT"          T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VLINK"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BODYTEXT_attr[] = {   /* BODYTEXT attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BQ_attr[] = {         /* BLOCKQUOTE attributes */
    +	{ "CITE"          T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr BUTTON_attr[] = {     /* BUTTON attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "FORMACTION"    T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr CAPTION_attr[] = {    /* CAPTION attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr COL_attr[] = {        /* COL attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SPAN"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DEL_attr[] = {        /* DEL attributes */
    +	{ "CITE"          T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DATETIME"      T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DIV_attr[] = {        /* CENTER attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr DL_attr[] = {         /* DL attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr EMBED_attr[] = {      /* EMBED attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(i) },
    +	{ "NOFLOW"        T(N) },
    +	{ "PARAMS"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FIG_attr[] = {        /* FIG attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FONT_attr[] = {       /* BASEFONT attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLOR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "END"           T(N) },
    +	{ "FACE"          T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FORM_attr[] = {       /* FORM attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACTION"        T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ENCTYPE"       T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "METHOD"        T(N) },
    +	{ "ONRESET"       T(N) },
    +	{ "ONSUBMIT"      T(N) },
    +	{ "SCRIPT"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "SUBJECT"       T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FRAME_attr[] = {      /* FRAME attributes */
    +	{ "CLASS"         T(c) },
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "ID"            T(i) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NORESIZE"      T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr FRAMESET_attr[] = {   /* FRAMESET attributes */
    +	{ "COLS"          T(N) },
    +	{ "ONLOAD"        T(N) },
    +	{ "ONUNLOAD"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr GEN_attr[] = {        /* ABBR attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr GEN5_attr[] = {       /* ARTICLE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "ROLE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr H_attr[] = {          /* H1 attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr HR_attr[] = {         /* HR attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "NOSHADE"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr IFRAME_attr[] = {     /* IFRAME attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "FRAMEBORDER"   T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MARGINHEIGHT"  T(N) },
    +	{ "MARGINWIDTH"   T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCROLLING"     T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr IMG_attr[] = {        /* IMG attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "ISOBJECT"      T(N) },
    +	{ "LANG"          T(N) },
    +	{ "LONGDESC"      T(h) },
    +	{ "MD"            T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr INPUT_attr[] = {      /* INPUT attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "ALT"           T(N) },
    +	{ "CHECKED"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "MAX"           T(N) },
    +	{ "MAXLENGTH"     T(N) },
    +	{ "MD"            T(N) },
    +	{ "MIN"           T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr ISINDEX_attr[] = {    /* ISINDEX attributes */
    +	{ "ACTION"        T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "PROMPT"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr KEYGEN_attr[] = {     /* KEYGEN attributes */
    +	{ "CHALLENGE"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LABEL_attr[] = {      /* LABEL attributes */
    +	{ "ACCESSKEY"     T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LI_attr[] = {         /* LI attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "SKIP"          T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr LINK_attr[] = {       /* LINK attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "HREF"          T(h) },
    +	{ "HREFLANG"      T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "REL"           T(N) },
    +	{ "REV"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TARGET"        T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr MAP_attr[] = {        /* MAP attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(i) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr MATH_attr[] = {       /* MATH attributes */
    +	{ "BOX"           T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr META_attr[] = {       /* META attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CONTENT"       T(N) },
    +	{ "HTTP-EQUIV"    T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCHEME"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr NEXTID_attr[] = {     /* NEXTID attributes */
    +	{ "N"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr NOTE_attr[] = {       /* NOTE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "ROLE"          T(x) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OBJECT_attr[] = {     /* OBJECT attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "ARCHIVE"       T(N) },
    +	{ "BORDER"        T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLASSID"       T(h) },
    +	{ "CODEBASE"      T(h) },
    +	{ "CODETYPE"      T(N) },
    +	{ "DATA"          T(h) },
    +	{ "DECLARE"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "HSPACE"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "ISMAP"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "SHAPES"        T(N) },
    +	{ "STANDBY"       T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "USEMAP"        T(h) },
    +	{ "VSPACE"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OL_attr[] = {         /* OL attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "CONTINUE"      T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "SEQNUM"        T(N) },
    +	{ "START"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OPTION_attr[] = {     /* OPTION attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LABEL"         T(N) },
    +	{ "LANG"          T(N) },
    +	{ "SELECTED"      T(N) },
    +	{ "SHAPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALUE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr OVERLAY_attr[] = {    /* OVERLAY attributes */
    +	{ "CLASS"         T(c) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "IMAGEMAP"      T(N) },
    +	{ "MD"            T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ "X"             T(N) },
    +	{ "Y"             T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr P_attr[] = {          /* P attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr PARAM_attr[] = {      /* PARAM attributes */
    +	{ "ACCEPT"        T(N) },
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCEPT-ENCODING" T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DATA"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "OBJECT"        T(N) },
    +	{ "REF"           T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "VALUE"         T(N) },
    +	{ "VALUEREF"      T(N) },
    +	{ "VALUETYPE"     T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr Q_attr[] = {          /* Q attributes */
    +	{ "CITE"          T(h) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr SCRIPT_attr[] = {     /* SCRIPT attributes */
    +	{ "CHARSET"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DEFER"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "EVENT"         T(N) },
    +	{ "FOR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "LANGUAGE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "SCRIPTENGINE"  T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr SELECT_attr[] = {     /* SELECT attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "MULTIPLE"      T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "SIZE"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr STYLE_attr[] = {      /* STYLE attributes */
    +	{ "CLASS"         T(c) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MEDIA"         T(N) },
    +	{ "NOTATION"      T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TAB_attr[] = {        /* TAB attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "ID"            T(i) },
    +	{ "INDENT"        T(N) },
    +	{ "LANG"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TO"            T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TABLE_attr[] = {      /* TABLE attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "BORDER"        T(N) },
    +	{ "CELLPADDING"   T(N) },
    +	{ "CELLSPACING"   T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "COLSPEC"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "FRAME"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOFLOW"        T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "RULES"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "SUMMARY"       T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "UNITS"         T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TD_attr[] = {         /* TD attributes */
    +	{ "ABBR"          T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "AXES"          T(N) },
    +	{ "AXIS"          T(N) },
    +	{ "BACKGROUND"    T(h) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLSPAN"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "HEADERS"       T(N) },
    +	{ "HEIGHT"        T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "ROWSPAN"       T(N) },
    +	{ "SCOPE"         T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ "WIDTH"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TEXTAREA_attr[] = {   /* TEXTAREA attributes */
    +	{ "ACCEPT-CHARSET" T(N) },
    +	{ "ACCESSKEY"     T(N) },
    +	{ "ALIGN"         T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COLS"          T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DISABLED"      T(N) },
    +	{ "ERROR"         T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NAME"          T(N) },
    +	{ "NOTAB"         T(N) },
    +	{ "ONBLUR"        T(N) },
    +	{ "ONCHANGE"      T(N) },
    +	{ "ONFOCUS"       T(N) },
    +	{ "ONSELECT"      T(N) },
    +	{ "READONLY"      T(N) },
    +	{ "ROWS"          T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TABINDEX"      T(N) },
    +	{ "TITLE"         T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr TR_attr[] = {         /* TBODY attributes */
    +	{ "ALIGN"         T(N) },
    +	{ "CHAR"          T(N) },
    +	{ "CHAROFF"       T(N) },
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "DIR"           T(N) },
    +	{ "DP"            T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "NOWRAP"        T(N) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "VALIGN"        T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +static const attr UL_attr[] = {         /* DIR attributes */
    +	{ "CLASS"         T(c) },
    +	{ "CLEAR"         T(N) },
    +	{ "COMPACT"       T(N) },
    +	{ "DINGBAT"       T(N) },
    +	{ "DIR"           T(N) },
    +	{ "ID"            T(i) },
    +	{ "LANG"          T(N) },
    +	{ "MD"            T(N) },
    +	{ "PLAIN"         T(N) },
    +	{ "SRC"           T(h) },
    +	{ "STYLE"         T(N) },
    +	{ "TITLE"         T(N) },
    +	{ "TYPE"          T(N) },
    +	{ "WRAP"          T(N) },
    +	{ 0               T(N) }	/* Terminate list */
    +};
    +
    +/* *INDENT-ON* */
    +
    +/* justification-flags */
    +#undef N
    +#undef i
    +#undef h
    +#undef c
    +#undef x
    +
    +#undef T
    +
    +/* tag-names */
    +#undef A
    +#undef ABBR
    +#undef ACRONYM
    +#undef ADDRESS
    +#undef APPLET
    +#undef AREA
    +#undef ARTICLE
    +#undef ASIDE
    +#undef AU
    +#undef AUTHOR
    +#undef B
    +#undef BANNER
    +#undef BASE
    +#undef BASEFONT
    +#undef BDO
    +#undef BGSOUND
    +#undef BIG
    +#undef BLINK
    +#undef BLOCKQUOTE
    +#undef BODY
    +#undef BODYTEXT
    +#undef BQ
    +#undef BR
    +#undef BUTTON
    +#undef CAPTION
    +#undef CENTER
    +#undef CITE
    +#undef CODE
    +#undef COL
    +#undef COLGROUP
    +#undef COMMENT
    +#undef CREDIT
    +#undef DD
    +#undef DEL
    +#undef DEL
    +#undef DFN
    +#undef DIR
    +#undef DIV
    +#undef DL
    +#undef DLC
    +#undef DT
    +#undef EM
    +#undef EMBED
    +#undef FIELDSET
    +#undef FIG
    +#undef FIGURE
    +#undef FN
    +#undef FONT
    +#undef FOOTER
    +#undef FORM
    +#undef FRAME
    +#undef FRAMESET
    +#undef H1
    +#undef H2
    +#undef H3
    +#undef H4
    +#undef H5
    +#undef H6
    +#undef HEAD
    +#undef HEADER
    +#undef HR
    +#undef HTML
    +#undef HY
    +#undef I
    +#undef IFRAME
    +#undef IMG
    +#undef INPUT
    +#undef INS
    +#undef INS
    +#undef ISINDEX
    +#undef KBD
    +#undef KEYGEN
    +#undef LABEL
    +#undef LEGEND
    +#undef LH
    +#undef LI
    +#undef LINK
    +#undef LISTING
    +#undef MAIN
    +#undef MAP
    +#undef MARQUEE
    +#undef MATH
    +#undef MENU
    +#undef META
    +#undef NAV
    +#undef NEXTID
    +#undef NOFRAMES
    +#undef NOTE
    +#undef OBJECT
    +#undef OL
    +#undef OPTION
    +#undef OVERLAY
    +#undef P
    +#undef PARAM
    +#undef PLAINTEXT
    +#undef PRE
    +#undef Q
    +#undef S
    +#undef SAMP
    +#undef SCRIPT
    +#undef SECTION
    +#undef SELECT
    +#undef SHY
    +#undef SMALL
    +#undef SPAN
    +#undef SPOT
    +#undef STRIKE
    +#undef STRONG
    +#undef STYLE
    +#undef SUB
    +#undef SUP
    +#undef TAB
    +#undef TABLE
    +#undef TBODY
    +#undef TD
    +#undef TEXTAREA
    +#undef TEXTFLOW
    +#undef TFOOT
    +#undef TH
    +#undef THEAD
    +#undef TITLE
    +#undef TR
    +#undef TT
    +#undef U
    +#undef UL
    +#undef VAR
    +#undef WBR
    +#undef XMP
    +#undef OBJECT_PCDATA
    +
    +/* these definitions are used in the tags-tables */
    +#undef P
    +#undef P_
    +#ifdef USE_COLOR_STYLE
    +#define P_(x) #x, (sizeof #x) -1
    +#define NULL_HTTag_ NULL, 0
    +#else
    +#define P_(x) #x
    +#define NULL_HTTag_ NULL
    +#endif
    +
    +#ifdef USE_JUSTIFY_ELTS
    +#define P(x) P_(x), 1
    +#define P0(x) P_(x), 0
    +#define NULL_HTTag NULL_HTTag_,0
    +#else
    +#define P(x) P_(x)
    +#define P0(x) P_(x)
    +#define NULL_HTTag NULL_HTTag_
    +#endif
    +
    +#define ATTR_DATA(name) name##_attr, HTML_##name##_ATTRIBUTES, name##_attr_type
    +
    +#endif /* once_HTMLDTD */
    +/* *INDENT-OFF* */
    +static const HTTag tags_table1[HTML_ALL_ELEMENTS] = {
    + { P(A),           ATTR_DATA(A),           SGML_MIXED,   T_A, 0, 0},
    + { P(ABBR),        ATTR_DATA(GEN),         SGML_MIXED,   T_ABBR, 0, 0},
    + { P(ACRONYM),     ATTR_DATA(GEN),         SGML_MIXED,   T_ACRONYM, 0, 0},
    + { P(ADDRESS),     ATTR_DATA(ADDRESS),     SGML_MIXED,   T_ADDRESS, 0, 0},
    + { P(APPLET),      ATTR_DATA(APPLET),      SGML_MIXED,   T_APPLET, 0, 0},
    + { P(AREA),        ATTR_DATA(AREA),        SGML_EMPTY,   T_AREA, 0, 0},
    + { P(ARTICLE),     ATTR_DATA(GEN5),        SGML_MIXED,   T_ARTICLE, 0, 0},
    + { P(ASIDE),       ATTR_DATA(GEN5),        SGML_MIXED,   T_ASIDE, 0, 0},
    + { P(AU),          ATTR_DATA(GEN),         SGML_MIXED,   T_AU, 0, 0},
    + { P(AUTHOR),      ATTR_DATA(GEN),         SGML_MIXED,   T_AUTHOR, 0, 0},
    + { P(B),           ATTR_DATA(GEN),         SGML_MIXED,   T_B, 0, 0},
    + { P0(BANNER),     ATTR_DATA(GEN),         SGML_MIXED,   T_BANNER, 0, 0},
    + { P(BASE),        ATTR_DATA(BASE),        SGML_EMPTY,   T_BASE, 0, 0},
    + { P(BASEFONT),    ATTR_DATA(FONT),        SGML_EMPTY,   T_BASEFONT, 0, 0},
    + { P(BDO),         ATTR_DATA(GEN),         SGML_MIXED,   T_BDO, 0, 0},
    + { P(BGSOUND),     ATTR_DATA(BGSOUND),     SGML_EMPTY,   T_BGSOUND, 0, 0},
    + { P(BIG),         ATTR_DATA(GEN),         SGML_MIXED,   T_BIG, 0, 0},
    + { P(BLINK),       ATTR_DATA(GEN),         SGML_MIXED,   T_BLINK, 0, 0},
    + { P(BLOCKQUOTE),  ATTR_DATA(BQ),          SGML_MIXED,   T_BLOCKQUOTE, 0, 0},
    + { P(BODY),        ATTR_DATA(BODY),        SGML_MIXED,   T_BODY, 0, 0},
    + { P(BODYTEXT),    ATTR_DATA(BODYTEXT),    SGML_MIXED,   T_BODYTEXT, 0, 0},
    + { P(BQ),          ATTR_DATA(BQ),          SGML_MIXED,   T_BQ, 0, 0},
    + { P(BR),          ATTR_DATA(GEN),         SGML_EMPTY,   T_BR, 0, 0},
    + { P(BUTTON),      ATTR_DATA(BUTTON),      SGML_MIXED,   T_BUTTON, 0, 0},
    + { P(CAPTION),     ATTR_DATA(CAPTION),     SGML_MIXED,   T_CAPTION, 0, 0},
    + { P(CENTER),      ATTR_DATA(DIV),         SGML_MIXED,   T_CENTER, 0, 0},
    + { P(CITE),        ATTR_DATA(GEN),         SGML_MIXED,   T_CITE, 0, 0},
    + { P(CODE),        ATTR_DATA(GEN),         SGML_MIXED,   T_CODE, 0, 0},
    + { P(COL),         ATTR_DATA(COL),         SGML_EMPTY,   T_COL, 0, 0},
    + { P(COLGROUP),    ATTR_DATA(COL),         SGML_ELEMENT, T_COLGROUP, 0, 0},
    + { P(COMMENT),     ATTR_DATA(GEN),         SGML_PCDATA,  T_COMMENT, 0, 0},
    + { P(CREDIT),      ATTR_DATA(GEN),         SGML_MIXED,   T_CREDIT, 0, 0},
    + { P(DD),          ATTR_DATA(GEN),         SGML_MIXED,   T_DD, 0, 0},
    + { P(DEL),         ATTR_DATA(DEL),         SGML_MIXED,   T_DEL, 0, 1},
    + { P(DEL),         ATTR_DATA(DEL),         SGML_MIXED,   T_DEL_2, 1, 1},
    + { P(DFN),         ATTR_DATA(GEN),         SGML_MIXED,   T_DFN, 0, 0},
    + { P(DIR),         ATTR_DATA(UL),          SGML_MIXED,   T_DIR, 0, 0},
    + { P(DIV),         ATTR_DATA(DIV),         SGML_MIXED,   T_DIV, 0, 0},
    + { P(DL),          ATTR_DATA(DL),          SGML_MIXED,   T_DL, 0, 0},
    + { P(DLC),         ATTR_DATA(DL),          SGML_MIXED,   T_DLC, 0, 0},
    + { P(DT),          ATTR_DATA(GEN),         SGML_MIXED,   T_DT, 0, 0},
    + { P(EM),          ATTR_DATA(GEN),         SGML_MIXED,   T_EM, 0, 0},
    + { P(EMBED),       ATTR_DATA(EMBED),       SGML_EMPTY,   T_EMBED, 0, 0},
    + { P(FIELDSET),    ATTR_DATA(GEN),         SGML_MIXED,   T_FIELDSET, 0, 0},
    + { P(FIG),         ATTR_DATA(FIG),         SGML_MIXED,   T_FIG, 0, 0},
    + { P(FIGURE),      ATTR_DATA(GEN5),        SGML_MIXED,   T_FIGURE, 0, 0},
    + { P(FN),          ATTR_DATA(GEN),         SGML_MIXED,   T_FN, 0, 0},
    + { P(FONT),        ATTR_DATA(FONT),        SGML_MIXED,   T_FONT, 0, 0},
    + { P(FOOTER),      ATTR_DATA(GEN5),        SGML_MIXED,   T_FOOTER, 0, 0},
    + { P(FORM),        ATTR_DATA(FORM),        SGML_MIXED,   T_FORM, 0, 0},
    + { P(FRAME),       ATTR_DATA(FRAME),       SGML_EMPTY,   T_FRAME, 0, 0},
    + { P(FRAMESET),    ATTR_DATA(FRAMESET),    SGML_ELEMENT, T_FRAMESET, 0, 0},
    + { P0(H1),         ATTR_DATA(H),           SGML_MIXED,   T_H1, 0, 0},
    + { P0(H2),         ATTR_DATA(H),           SGML_MIXED,   T_H2, 0, 0},
    + { P0(H3),         ATTR_DATA(H),           SGML_MIXED,   T_H3, 0, 0},
    + { P0(H4),         ATTR_DATA(H),           SGML_MIXED,   T_H4, 0, 0},
    + { P0(H5),         ATTR_DATA(H),           SGML_MIXED,   T_H5, 0, 0},
    + { P0(H6),         ATTR_DATA(H),           SGML_MIXED,   T_H6, 0, 0},
    + { P(HEAD),        ATTR_DATA(GEN),         SGML_ELEMENT, T_HEAD, 0, 0},
    + { P(HEADER),      ATTR_DATA(GEN5),        SGML_MIXED,   T_HEADER, 0, 0},
    + { P(HR),          ATTR_DATA(HR),          SGML_EMPTY,   T_HR, 0, 0},
    + { P(HTML),        ATTR_DATA(GEN),         SGML_MIXED,   T_HTML, 0, 0},
    + { P(HY),          ATTR_DATA(GEN),         SGML_EMPTY,   T_HY, 0, 0},
    + { P(I),           ATTR_DATA(GEN),         SGML_MIXED,   T_I, 0, 0},
    + { P(IFRAME),      ATTR_DATA(IFRAME),      SGML_MIXED,   T_IFRAME, 0, 0},
    + { P(IMG),         ATTR_DATA(IMG),         SGML_EMPTY,   T_IMG, 0, 0},
    + { P(INPUT),       ATTR_DATA(INPUT),       SGML_EMPTY,   T_INPUT, 0, 0},
    + { P(INS),         ATTR_DATA(DEL),         SGML_MIXED,   T_INS, 0, 1},
    + { P(INS),         ATTR_DATA(DEL),         SGML_MIXED,   T_INS_2, 1, 1},
    + { P(ISINDEX),     ATTR_DATA(ISINDEX),     SGML_EMPTY,   T_ISINDEX, 0, 0},
    + { P(KBD),         ATTR_DATA(GEN),         SGML_MIXED,   T_KBD, 0, 0},
    + { P(KEYGEN),      ATTR_DATA(KEYGEN),      SGML_EMPTY,   T_KEYGEN, 0, 0},
    + { P(LABEL),       ATTR_DATA(LABEL),       SGML_MIXED,   T_LABEL, 0, 0},
    + { P(LEGEND),      ATTR_DATA(CAPTION),     SGML_MIXED,   T_LEGEND, 0, 0},
    + { P(LH),          ATTR_DATA(GEN),         SGML_MIXED,   T_LH, 0, 0},
    + { P(LI),          ATTR_DATA(LI),          SGML_MIXED,   T_LI, 0, 0},
    + { P(LINK),        ATTR_DATA(LINK),        SGML_EMPTY,   T_LINK, 0, 0},
    + { P(LISTING),     ATTR_DATA(GEN),         SGML_LITTERAL,T_LISTING, 0, 0},
    + { P(MAIN),        ATTR_DATA(GEN5),        SGML_MIXED,   T_MAIN, 0, 0},
    + { P(MAP),         ATTR_DATA(MAP),         SGML_ELEMENT, T_MAP, 0, 0},
    + { P(MARQUEE),     ATTR_DATA(GEN),         SGML_MIXED,   T_MARQUEE, 0, 0},
    + { P(MATH),        ATTR_DATA(MATH),        SGML_PCDATA,  T_MATH, 0, 0},
    + { P(MENU),        ATTR_DATA(UL),          SGML_MIXED,   T_MENU, 0, 0},
    + { P(META),        ATTR_DATA(META),        SGML_EMPTY,   T_META, 0, 0},
    + { P(NAV),         ATTR_DATA(GEN5),        SGML_MIXED,   T_NAV, 0, 0},
    + { P(NEXTID),      ATTR_DATA(NEXTID),      SGML_EMPTY,   T_NEXTID, 0, 0},
    + { P(NOFRAMES),    ATTR_DATA(GEN),         SGML_MIXED,   T_NOFRAMES, 0, 0},
    + { P(NOTE),        ATTR_DATA(NOTE),        SGML_MIXED,   T_NOTE, 0, 0},
    + { P(OBJECT),      ATTR_DATA(OBJECT),      SGML_LITTERAL,T_OBJECT, 0, 0},
    + { P(OL),          ATTR_DATA(OL),          SGML_MIXED,   T_OL, 0, 0},
    + { P(OPTION),      ATTR_DATA(OPTION),      SGML_PCDATA,  T_OPTION, 0, 0},
    + { P(OVERLAY),     ATTR_DATA(OVERLAY),     SGML_PCDATA,  T_OVERLAY, 0, 0},
    + { P(P),           ATTR_DATA(P),           SGML_MIXED,   T_P, 0, 0},
    + { P(PARAM),       ATTR_DATA(PARAM),       SGML_EMPTY,   T_PARAM, 0, 0},
    + { P(PLAINTEXT),   ATTR_DATA(GEN),         SGML_LITTERAL,T_PLAINTEXT, 0, 0},
    + { P0(PRE),        ATTR_DATA(GEN),         SGML_MIXED,   T_PRE, 0, 0},
    + { P(Q),           ATTR_DATA(Q),           SGML_MIXED,   T_Q, 0, 0},
    + { P(S),           ATTR_DATA(GEN),         SGML_MIXED,   T_S, 0, 0},
    + { P(SAMP),        ATTR_DATA(GEN),         SGML_MIXED,   T_SAMP, 0, 0},
    + { P(SCRIPT),      ATTR_DATA(SCRIPT),      SGML_SCRIPT,  T_SCRIPT, 0, 0},
    + { P(SECTION),     ATTR_DATA(GEN5),        SGML_MIXED,   T_SECTION, 0, 0},
    + { P(SELECT),      ATTR_DATA(SELECT),      SGML_ELEMENT, T_SELECT, 0, 0},
    + { P(SHY),         ATTR_DATA(GEN),         SGML_EMPTY,   T_SHY, 0, 0},
    + { P(SMALL),       ATTR_DATA(GEN),         SGML_MIXED,   T_SMALL, 0, 0},
    + { P(SPAN),        ATTR_DATA(GEN),         SGML_MIXED,   T_SPAN, 0, 0},
    + { P(SPOT),        ATTR_DATA(GEN),         SGML_EMPTY,   T_SPOT, 0, 0},
    + { P(STRIKE),      ATTR_DATA(GEN),         SGML_MIXED,   T_STRIKE, 0, 0},
    + { P(STRONG),      ATTR_DATA(GEN),         SGML_MIXED,   T_STRONG, 0, 0},
    + { P(STYLE),       ATTR_DATA(STYLE),       SGML_CDATA,   T_STYLE, 0, 0},
    + { P(SUB),         ATTR_DATA(GEN),         SGML_MIXED,   T_SUB, 0, 0},
    + { P(SUP),         ATTR_DATA(GEN),         SGML_MIXED,   T_SUP, 0, 0},
    + { P(TAB),         ATTR_DATA(TAB),         SGML_EMPTY,   T_TAB, 0, 0},
    + { P(TABLE),       ATTR_DATA(TABLE),       SGML_ELEMENT, T_TABLE, 0, 0},
    + { P(TBODY),       ATTR_DATA(TR),          SGML_ELEMENT, T_TBODY, 0, 0},
    + { P(TD),          ATTR_DATA(TD),          SGML_MIXED,   T_TD, 0, 0},
    + { P(TEXTAREA),    ATTR_DATA(TEXTAREA),    SGML_PCDATA,  T_TEXTAREA, 0, 0},
    + { P(TEXTFLOW),    ATTR_DATA(BODYTEXT),    SGML_MIXED,   T_TEXTFLOW, 0, 0},
    + { P(TFOOT),       ATTR_DATA(TR),          SGML_ELEMENT, T_TFOOT, 0, 0},
    + { P(TH),          ATTR_DATA(TD),          SGML_MIXED,   T_TH, 0, 0},
    + { P(THEAD),       ATTR_DATA(TR),          SGML_ELEMENT, T_THEAD, 0, 0},
    + { P(TITLE),       ATTR_DATA(GEN),         SGML_PCDATA,  T_TITLE, 0, 0},
    + { P(TR),          ATTR_DATA(TR),          SGML_MIXED,   T_TR, 0, 0},
    + { P(TT),          ATTR_DATA(GEN),         SGML_MIXED,   T_TT, 0, 0},
    + { P(U),           ATTR_DATA(GEN),         SGML_MIXED,   T_U, 0, 0},
    + { P(UL),          ATTR_DATA(UL),          SGML_MIXED,   T_UL, 0, 0},
    + { P(VAR),         ATTR_DATA(GEN),         SGML_MIXED,   T_VAR, 0, 0},
    + { P(WBR),         ATTR_DATA(GEN),         SGML_EMPTY,   T_WBR, 0, 0},
    + { P0(XMP),        ATTR_DATA(GEN),         SGML_LITTERAL,T_XMP, 0, 0},
    +/* additional (alternative variants), not counted in HTML_ELEMENTS: */
    +/* This one will be used as a temporary substitute within the parser when
    +   it has been signalled to parse OBJECT content as MIXED. - kw */
    + { P(OBJECT),      ATTR_DATA(OBJECT),      SGML_MIXED,   T_OBJECT_PCDATA, 0, 0},
    +};
    +/* *INDENT-ON* */
    +
    +#endif /* src_HTMLDTD_H1 */
    diff --git a/WWW/Library/Implementation/src1_HTMLDTD.txt b/WWW/Library/Implementation/src1_HTMLDTD.txt
    new file mode 100644
    index 0000000..df1521b
    --- /dev/null
    +++ b/WWW/Library/Implementation/src1_HTMLDTD.txt
    @@ -0,0 +1,3901 @@
    +60 attr_types
    +	0:align
    +		1 attributes:
    +			0:0:ALIGN
    +	1:bgcolor
    +		1 attributes:
    +			0:0:BGCOLOR
    +	2:cellalign
    +		4 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:0:VALIGN
    +	3:core
    +		4 attributes:
    +			0:4:CLASS
    +			1:1:ID
    +			2:0:STYLE
    +			3:0:TITLE
    +	4:events
    +		10 attributes:
    +			0:0:ONCLICK
    +			1:0:ONDBLCLICK
    +			2:0:ONKEYDOWN
    +			3:0:ONKEYPRESS
    +			4:0:ONKEYUP
    +			5:0:ONMOUSEDOWN
    +			6:0:ONMOUSEMOVE
    +			7:0:ONMOUSEOUT
    +			8:0:ONMOUSEOVER
    +			9:0:ONMOUSEUP
    +	5:i18n
    +		2 attributes:
    +			0:0:DIR
    +			1:0:LANG
    +	6:A
    +		19 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CHARSET
    +			2:0:CLEAR
    +			3:0:COORDS
    +			4:2:HREF
    +			5:0:HREFLANG
    +			6:0:ISMAP
    +			7:0:MD
    +			8:1:NAME
    +			9:0:NOTAB
    +			10:0:ONBLUR
    +			11:0:ONFOCUS
    +			12:0:REL
    +			13:0:REV
    +			14:0:SHAPE
    +			15:0:TABINDEX
    +			16:0:TARGET
    +			17:0:TYPE
    +			18:0:URN
    +	7:ADDRESS
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:NOWRAP
    +	8:APPLET
    +		10 attributes:
    +			0:0:ALT
    +			1:0:CLEAR
    +			2:0:CODE
    +			3:2:CODEBASE
    +			4:0:DOWNLOAD
    +			5:0:HEIGHT
    +			6:0:HSPACE
    +			7:1:NAME
    +			8:0:VSPACE
    +			9:0:WIDTH
    +	9:AREA
    +		12 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALT
    +			2:0:CLEAR
    +			3:0:COORDS
    +			4:2:HREF
    +			5:0:NOHREF
    +			6:0:NOTAB
    +			7:0:ONBLUR
    +			8:0:ONFOCUS
    +			9:0:SHAPE
    +			10:0:TABINDEX
    +			11:0:TARGET
    +	10:BASE
    +		2 attributes:
    +			0:2:HREF
    +			1:0:TARGET
    +	11:BGSOUND
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:LOOP
    +			2:2:SRC
    +	12:BODY
    +		8 attributes:
    +			0:0:ALINK
    +			1:2:BACKGROUND
    +			2:0:CLEAR
    +			3:0:LINK
    +			4:0:ONLOAD
    +			5:0:ONUNLOAD
    +			6:0:TEXT
    +			7:0:VLINK
    +	13:BODYTEXT
    +		8 attributes:
    +			0:0:CLEAR
    +			1:0:DATA
    +			2:0:NAME
    +			3:0:OBJECT
    +			4:0:REF
    +			5:0:TYPE
    +			6:0:VALUE
    +			7:0:VALUETYPE
    +	14:BQ
    +		3 attributes:
    +			0:2:CITE
    +			1:0:CLEAR
    +			2:0:NOWRAP
    +	15:BUTTON
    +		11 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +			2:0:DISABLED
    +			3:0:FORMACTION
    +			4:0:NAME
    +			5:0:ONBLUR
    +			6:0:ONFOCUS
    +			7:0:READONLY
    +			8:0:TABINDEX
    +			9:0:TYPE
    +			10:0:VALUE
    +	16:CAPTION
    +		2 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +	17:COL
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:SPAN
    +			2:0:WIDTH
    +	18:DEL
    +		2 attributes:
    +			0:0:CITE
    +			1:0:DATETIME
    +	19:DIV
    +		1 attributes:
    +			0:0:CLEAR
    +	20:DL
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +	21:EMBED
    +		14 attributes:
    +			0:0:ALT
    +			1:0:BORDER
    +			2:0:CLEAR
    +			3:0:HEIGHT
    +			4:0:IMAGEMAP
    +			5:0:ISMAP
    +			6:0:MD
    +			7:1:NAME
    +			8:0:NOFLOW
    +			9:0:PARAMS
    +			10:2:SRC
    +			11:0:UNITS
    +			12:0:USEMAP
    +			13:0:WIDTH
    +	22:FIG
    +		10 attributes:
    +			0:0:BORDER
    +			1:0:CLEAR
    +			2:0:HEIGHT
    +			3:0:IMAGEMAP
    +			4:0:ISOBJECT
    +			5:0:MD
    +			6:0:NOFLOW
    +			7:2:SRC
    +			8:0:UNITS
    +			9:0:WIDTH
    +	23:FONT
    +		5 attributes:
    +			0:0:CLEAR
    +			1:0:COLOR
    +			2:0:END
    +			3:0:FACE
    +			4:0:SIZE
    +	24:FORM
    +		11 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:2:ACTION
    +			3:0:CLEAR
    +			4:0:ENCTYPE
    +			5:0:METHOD
    +			6:0:ONRESET
    +			7:0:ONSUBMIT
    +			8:0:SCRIPT
    +			9:0:SUBJECT
    +			10:0:TARGET
    +	25:FRAME
    +		8 attributes:
    +			0:0:FRAMEBORDER
    +			1:2:LONGDESC
    +			2:0:MARGINHEIGHT
    +			3:0:MARGINWIDTH
    +			4:0:NAME
    +			5:0:NORESIZE
    +			6:0:SCROLLING
    +			7:2:SRC
    +	26:FRAMESET
    +		4 attributes:
    +			0:0:COLS
    +			1:0:ONLOAD
    +			2:0:ONUNLOAD
    +			3:0:ROWS
    +	27:GEN
    +		1 attributes:
    +			0:0:CLEAR
    +	28:GEN5
    +		1 attributes:
    +			0:0:ROLE
    +	29:H
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DINGBAT
    +			2:0:MD
    +			3:0:NOWRAP
    +			4:0:SEQNUM
    +			5:0:SKIP
    +			6:2:SRC
    +	30:HR
    +		6 attributes:
    +			0:0:CLEAR
    +			1:0:MD
    +			2:0:NOSHADE
    +			3:0:SIZE
    +			4:2:SRC
    +			5:0:WIDTH
    +	31:IFRAME
    +		9 attributes:
    +			0:0:FRAMEBORDER
    +			1:0:HEIGHT
    +			2:2:LONGDESC
    +			3:0:MARGINHEIGHT
    +			4:0:MARGINWIDTH
    +			5:0:NAME
    +			6:0:SCROLLING
    +			7:2:SRC
    +			8:0:WIDTH
    +	32:IMG
    +		15 attributes:
    +			0:0:ALT
    +			1:0:BORDER
    +			2:0:CLEAR
    +			3:0:HEIGHT
    +			4:0:HSPACE
    +			5:0:ISMAP
    +			6:0:ISOBJECT
    +			7:2:LONGDESC
    +			8:0:MD
    +			9:0:NAME
    +			10:2:SRC
    +			11:0:UNITS
    +			12:2:USEMAP
    +			13:0:VSPACE
    +			14:0:WIDTH
    +	33:INPUT
    +		28 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCESSKEY
    +			3:0:ALT
    +			4:0:CHECKED
    +			5:0:CLEAR
    +			6:0:DISABLED
    +			7:0:ERROR
    +			8:0:HEIGHT
    +			9:0:ISMAP
    +			10:0:MAX
    +			11:0:MAXLENGTH
    +			12:0:MD
    +			13:0:MIN
    +			14:0:NAME
    +			15:0:NOTAB
    +			16:0:ONBLUR
    +			17:0:ONCHANGE
    +			18:0:ONFOCUS
    +			19:0:ONSELECT
    +			20:0:READONLY
    +			21:0:SIZE
    +			22:2:SRC
    +			23:0:TABINDEX
    +			24:0:TYPE
    +			25:0:USEMAP
    +			26:0:VALUE
    +			27:0:WIDTH
    +	34:ISINDEX
    +		3 attributes:
    +			0:2:ACTION
    +			1:2:HREF
    +			2:0:PROMPT
    +	35:KEYGEN
    +		2 attributes:
    +			0:0:CHALLENGE
    +			1:0:NAME
    +	36:LABEL
    +		5 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CLEAR
    +			2:0:FOR
    +			3:0:ONBLUR
    +			4:0:ONFOCUS
    +	37:LI
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DINGBAT
    +			2:0:MD
    +			3:0:SKIP
    +			4:2:SRC
    +			5:0:TYPE
    +			6:0:VALUE
    +	38:LINK
    +		8 attributes:
    +			0:0:CHARSET
    +			1:2:HREF
    +			2:0:HREFLANG
    +			3:0:MEDIA
    +			4:0:REL
    +			5:0:REV
    +			6:0:TARGET
    +			7:0:TYPE
    +	39:MAP
    +		2 attributes:
    +			0:0:CLEAR
    +			1:1:NAME
    +	40:MATH
    +		2 attributes:
    +			0:0:BOX
    +			1:0:CLEAR
    +	41:META
    +		5 attributes:
    +			0:0:CHARSET
    +			1:0:CONTENT
    +			2:0:HTTP-EQUIV
    +			3:0:NAME
    +			4:0:SCHEME
    +	42:NEXTID
    +		1 attributes:
    +			0:0:N
    +	43:NOTE
    +		4 attributes:
    +			0:0:CLEAR
    +			1:0:MD
    +			2:8:ROLE
    +			3:2:SRC
    +	44:OBJECT
    +		19 attributes:
    +			0:0:ARCHIVE
    +			1:0:BORDER
    +			2:2:CLASSID
    +			3:2:CODEBASE
    +			4:0:CODETYPE
    +			5:2:DATA
    +			6:0:DECLARE
    +			7:0:HEIGHT
    +			8:0:HSPACE
    +			9:0:ISMAP
    +			10:0:NAME
    +			11:0:NOTAB
    +			12:0:SHAPES
    +			13:0:STANDBY
    +			14:0:TABINDEX
    +			15:0:TYPE
    +			16:2:USEMAP
    +			17:0:VSPACE
    +			18:0:WIDTH
    +	45:OL
    +		6 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +			2:0:CONTINUE
    +			3:0:SEQNUM
    +			4:0:START
    +			5:0:TYPE
    +	46:OPTION
    +		7 attributes:
    +			0:0:CLEAR
    +			1:0:DISABLED
    +			2:0:ERROR
    +			3:0:LABEL
    +			4:0:SELECTED
    +			5:0:SHAPE
    +			6:0:VALUE
    +	47:OVERLAY
    +		8 attributes:
    +			0:0:HEIGHT
    +			1:0:IMAGEMAP
    +			2:0:MD
    +			3:2:SRC
    +			4:0:UNITS
    +			5:0:WIDTH
    +			6:0:X
    +			7:0:Y
    +	48:P
    +		2 attributes:
    +			0:0:CLEAR
    +			1:0:NOWRAP
    +	49:PARAM
    +		12 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCEPT-ENCODING
    +			3:0:CLEAR
    +			4:0:DATA
    +			5:0:NAME
    +			6:0:OBJECT
    +			7:0:REF
    +			8:0:TYPE
    +			9:0:VALUE
    +			10:0:VALUEREF
    +			11:0:VALUETYPE
    +	50:Q
    +		2 attributes:
    +			0:2:CITE
    +			1:0:CLEAR
    +	51:SCRIPT
    +		10 attributes:
    +			0:0:CHARSET
    +			1:0:CLEAR
    +			2:0:DEFER
    +			3:0:EVENT
    +			4:0:FOR
    +			5:0:LANGUAGE
    +			6:0:NAME
    +			7:0:SCRIPTENGINE
    +			8:2:SRC
    +			9:0:TYPE
    +	52:SELECT
    +		15 attributes:
    +			0:0:CLEAR
    +			1:0:DISABLED
    +			2:0:ERROR
    +			3:0:HEIGHT
    +			4:0:MD
    +			5:0:MULTIPLE
    +			6:0:NAME
    +			7:0:NOTAB
    +			8:0:ONBLUR
    +			9:0:ONCHANGE
    +			10:0:ONFOCUS
    +			11:0:SIZE
    +			12:0:TABINDEX
    +			13:0:UNITS
    +			14:0:WIDTH
    +	53:STYLE
    +		3 attributes:
    +			0:0:MEDIA
    +			1:0:NOTATION
    +			2:0:TYPE
    +	54:TAB
    +		4 attributes:
    +			0:0:CLEAR
    +			1:0:DP
    +			2:0:INDENT
    +			3:0:TO
    +	55:TABLE
    +		15 attributes:
    +			0:2:BACKGROUND
    +			1:0:BORDER
    +			2:0:CELLPADDING
    +			3:0:CELLSPACING
    +			4:0:CLEAR
    +			5:0:COLS
    +			6:0:COLSPEC
    +			7:0:DP
    +			8:0:FRAME
    +			9:0:NOFLOW
    +			10:0:NOWRAP
    +			11:0:RULES
    +			12:0:SUMMARY
    +			13:0:UNITS
    +			14:0:WIDTH
    +	56:TD
    +		13 attributes:
    +			0:0:ABBR
    +			1:0:AXES
    +			2:0:AXIS
    +			3:2:BACKGROUND
    +			4:0:CLEAR
    +			5:0:COLSPAN
    +			6:0:DP
    +			7:0:HEADERS
    +			8:0:HEIGHT
    +			9:0:NOWRAP
    +			10:0:ROWSPAN
    +			11:0:SCOPE
    +			12:0:WIDTH
    +	57:TEXTAREA
    +		15 attributes:
    +			0:0:ACCEPT-CHARSET
    +			1:0:ACCESSKEY
    +			2:0:CLEAR
    +			3:0:COLS
    +			4:0:DISABLED
    +			5:0:ERROR
    +			6:0:NAME
    +			7:0:NOTAB
    +			8:0:ONBLUR
    +			9:0:ONCHANGE
    +			10:0:ONFOCUS
    +			11:0:ONSELECT
    +			12:0:READONLY
    +			13:0:ROWS
    +			14:0:TABINDEX
    +	58:TR
    +		3 attributes:
    +			0:0:CLEAR
    +			1:0:DP
    +			2:0:NOWRAP
    +	59:UL
    +		8 attributes:
    +			0:0:CLEAR
    +			1:0:COMPACT
    +			2:0:DINGBAT
    +			3:0:MD
    +			4:0:PLAIN
    +			5:2:SRC
    +			6:0:TYPE
    +			7:0:WRAP
    +128 tags
    +	0:A
    +		justify
    +		25 attributes:
    +			0:0:ACCESSKEY
    +			1:0:CHARSET
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:COORDS
    +			5:0:DIR
    +			6:2:HREF
    +			7:0:HREFLANG
    +			8:1:ID
    +			9:0:ISMAP
    +			10:0:LANG
    +			11:0:MD
    +			12:1:NAME
    +			13:0:NOTAB
    +			14:0:ONBLUR
    +			15:0:ONFOCUS
    +			16:0:REL
    +			17:0:REV
    +			18:0:SHAPE
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TARGET
    +			22:0:TITLE
    +			23:0:TYPE
    +			24:0:URN
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			A
    +		contents: SGML_MIXED
    +		tagclass: Alike
    +		contains: FONTlike EMlike MATHlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike formula Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike APPLETlike HRlike same
    +		flags: mafse nreie
    +	1:ABBR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	2:ACRONYM
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	3:ADDRESS
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:NOWRAP
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			ADDRESS
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike Plike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike DIVlike same
    +		flags:
    +	4:APPLET
    +		justify
    +		17 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:CODE
    +			5:2:CODEBASE
    +			6:0:DIR
    +			7:0:DOWNLOAD
    +			8:0:HEIGHT
    +			9:0:HSPACE
    +			10:1:ID
    +			11:0:LANG
    +			12:1:NAME
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:VSPACE
    +			16:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			APPLET
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike BRlike APPLETlike same
    +		flags:
    +	5:AREA
    +		justify
    +		18 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALT
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:COORDS
    +			5:0:DIR
    +			6:2:HREF
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOHREF
    +			10:0:NOTAB
    +			11:0:ONBLUR
    +			12:0:ONFOCUS
    +			13:0:SHAPE
    +			14:0:STYLE
    +			15:0:TABINDEX
    +			16:0:TARGET
    +			17:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			AREA
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: MAPlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike ULlike
    +		flags: endO
    +	6:ARTICLE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	7:ASIDE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	8:AU
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	9:AUTHOR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	10:B
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	11:BANNER
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: outer BODYlike DELlike
    +		icontained: outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	12:BASE
    +		justify
    +		6 attributes:
    +			0:4:CLASS
    +			1:2:HREF
    +			2:1:ID
    +			3:0:STYLE
    +			4:0:TARGET
    +			5:0:TITLE
    +		2 attr_types
    +			core
    +			BASE
    +		contents: SGML_EMPTY
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike same
    +		flags: endO
    +	13:BASEFONT
    +		justify
    +		11 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COLOR
    +			3:0:DIR
    +			4:0:END
    +			5:0:FACE
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SIZE
    +			9:0:STYLE
    +			10:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FONT
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: BRlike APPLETlike HRlike MAPlike same
    +		flags: endO
    +	14:BDO
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike Plike DIVlike
    +		flags:
    +	15:BGSOUND
    +		justify
    +		9 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:LOOP
    +			6:2:SRC
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BGSOUND
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike Plike DIVlike BRlike APPLETlike HRlike same
    +		flags: endO
    +	16:BIG
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	17:BLINK
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	18:BLOCKQUOTE
    +		justify
    +		9 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BQ
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	19:BODY
    +		justify
    +		15 attributes:
    +			0:0:ALINK
    +			1:2:BACKGROUND
    +			2:0:BGCOLOR
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:LINK
    +			9:0:ONLOAD
    +			10:0:ONUNLOAD
    +			11:0:STYLE
    +			12:0:TEXT
    +			13:0:TITLE
    +			14:0:VLINK
    +		4 attr_types
    +			bgcolor
    +			core
    +			i18n
    +			BODY
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		contained: outer BODYlike
    +		icontained: outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer HEADstuff same
    +		flags: endO startO
    +	20:BODYTEXT
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DATA
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NAME
    +			7:0:OBJECT
    +			8:0:REF
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +			13:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			BODYTEXT
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same DELlike
    +		contained: DIVlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike BRlike APPLETlike HRlike MAPlike same
    +		flags: endO startO
    +	21:BQ
    +		justify
    +		9 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			BQ
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	22:BR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	23:BUTTON
    +		justify
    +		17 attributes:
    +			0:0:ACCESSKEY
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DISABLED
    +			5:0:FORMACTION
    +			6:1:ID
    +			7:0:LANG
    +			8:0:NAME
    +			9:0:ONBLUR
    +			10:0:ONFOCUS
    +			11:0:READONLY
    +			12:0:STYLE
    +			13:0:TABINDEX
    +			14:0:TITLE
    +			15:0:TYPE
    +			16:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			BUTTON
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike formula TRlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike BRlike same
    +		flags:
    +	24:CAPTION
    +		justify
    +		9 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALIGN
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:STYLE
    +			8:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			CAPTION
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike ULlike APPLETlike HRlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike same
    +		flags:
    +	25:CENTER
    +		justify
    +		8 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			DIV
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags:
    +	26:CITE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags: nreie
    +	27:CODE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags:
    +	28:COL
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SPAN
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:VALIGN
    +			12:0:WIDTH
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			COL
    +		contents: SGML_EMPTY
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: TRlike ULlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags: endO
    +	29:COLGROUP
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SPAN
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:VALIGN
    +			12:0:WIDTH
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			COL
    +		contents: SGML_ELEMENT
    +		tagclass: TRlike
    +		contains: HRlike
    +		icontains: HRlike
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike MAPlike same
    +		flags: endO
    +	30:COMMENT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_PCDATA
    +		tagclass: MATHlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike
    +		flags:
    +	31:CREDIT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike ULlike APPLETlike HRlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike Plike DIVlike same
    +		flags:
    +	32:DD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	33:DEL
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	34:DEL!
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: DELlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	35:DFN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	36:DIR
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike HRlike same
    +		flags:
    +	37:DIV
    +		justify
    +		8 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			DIV
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike DIVlike same
    +		flags: mafse
    +	38:DL
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			DL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula SELECTlike Plike DIVlike LIlike
    +		flags:
    +	39:DLC
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			DL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike
    +		flags:
    +	40:DT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike BRlike APPLETlike MAPlike
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	41:EM
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags: nreie
    +	42:EMBED
    +		justify
    +		21 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:HEIGHT
    +			7:1:ID
    +			8:0:IMAGEMAP
    +			9:0:ISMAP
    +			10:0:LANG
    +			11:0:MD
    +			12:1:NAME
    +			13:0:NOFLOW
    +			14:0:PARAMS
    +			15:2:SRC
    +			16:0:STYLE
    +			17:0:TITLE
    +			18:0:UNITS
    +			19:0:USEMAP
    +			20:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			EMBED
    +		contents: SGML_EMPTY
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Plike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		flags: endO
    +	43:FIELDSET
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike APPLETlike HRlike same DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike MAPlike same
    +		flags:
    +	44:FIG
    +		justify
    +		17 attributes:
    +			0:0:ALIGN
    +			1:0:BORDER
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:0:HEIGHT
    +			6:1:ID
    +			7:0:IMAGEMAP
    +			8:0:ISOBJECT
    +			9:0:LANG
    +			10:0:MD
    +			11:0:NOFLOW
    +			12:2:SRC
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:UNITS
    +			16:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			FIG
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike Plike DIVlike MAPlike same
    +		flags:
    +	45:FIGURE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	46:FN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike Plike BRlike same
    +		flags:
    +	47:FONT
    +		justify
    +		11 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COLOR
    +			3:0:DIR
    +			4:0:END
    +			5:0:FACE
    +			6:1:ID
    +			7:0:LANG
    +			8:0:SIZE
    +			9:0:STYLE
    +			10:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FONT
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	48:FOOTER
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	49:FORM
    +		justify
    +		17 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:2:ACTION
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:ENCTYPE
    +			7:1:ID
    +			8:0:LANG
    +			9:0:METHOD
    +			10:0:ONRESET
    +			11:0:ONSUBMIT
    +			12:0:SCRIPT
    +			13:0:STYLE
    +			14:0:SUBJECT
    +			15:0:TARGET
    +			16:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			FORM
    +		contents: SGML_MIXED
    +		tagclass: FORMlike
    +		contains: FONTlike EMlike MATHlike Alike TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Plike DIVlike LIlike ULlike APPLETlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike LIlike ULlike MAPlike same
    +		flags:
    +	50:FRAME
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:FRAMEBORDER
    +			2:1:ID
    +			3:2:LONGDESC
    +			4:0:MARGINHEIGHT
    +			5:0:MARGINWIDTH
    +			6:0:NAME
    +			7:0:NORESIZE
    +			8:0:SCROLLING
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +		2 attr_types
    +			core
    +			FRAME
    +		contents: SGML_EMPTY
    +		tagclass: outer
    +		contains:
    +		icontains:
    +		contained: outer
    +		icontained: outer
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags: endO
    +	51:FRAMESET
    +		justify
    +		4 attributes:
    +			0:0:COLS
    +			1:0:ONLOAD
    +			2:0:ONUNLOAD
    +			3:0:ROWS
    +		1 attr_types
    +			FRAMESET
    +		contents: SGML_ELEMENT
    +		tagclass: outer
    +		contains: outer same
    +		icontains: outer same
    +		contained: outer same
    +		icontained: BRlike APPLETlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags:
    +	52:H1
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	53:H2
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	54:H3
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	55:H4
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	56:H5
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	57:H6
    +		nojustify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:NOWRAP
    +			9:0:SEQNUM
    +			10:0:SKIP
    +			11:2:SRC
    +			12:0:STYLE
    +			13:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			H
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags:
    +	58:HEAD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_ELEMENT
    +		tagclass: HEADstuff
    +		contains: BRlike APPLETlike HRlike MAPlike HEADstuff
    +		icontains: BRlike APPLETlike HRlike HEADstuff
    +		contained: outer
    +		icontained: outer
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same
    +		flags: endO startO mafse
    +	59:HEADER
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	60:HR
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:MD
    +			7:0:NOSHADE
    +			8:0:SIZE
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			HR
    +		contents: SGML_EMPTY
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike formula TRlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		flags: endO
    +	61:HTML
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: outer
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		contained:
    +		icontained:
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		flags: endO startO
    +	62:HY
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	63:I
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	64:IFRAME
    +		justify
    +		14 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:FRAMEBORDER
    +			3:0:HEIGHT
    +			4:1:ID
    +			5:2:LONGDESC
    +			6:0:MARGINHEIGHT
    +			7:0:MARGINWIDTH
    +			8:0:NAME
    +			9:0:SCROLLING
    +			10:2:SRC
    +			11:0:STYLE
    +			12:0:TITLE
    +			13:0:WIDTH
    +		3 attr_types
    +			align
    +			core
    +			IFRAME
    +		contents: SGML_MIXED
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike BRlike APPLETlike outer HEADstuff same
    +		flags:
    +	65:IMG
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:0:ALT
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:HEIGHT
    +			7:0:HSPACE
    +			8:1:ID
    +			9:0:ISMAP
    +			10:0:ISOBJECT
    +			11:0:LANG
    +			12:2:LONGDESC
    +			13:0:MD
    +			14:0:NAME
    +			15:2:SRC
    +			16:0:STYLE
    +			17:0:TITLE
    +			18:0:UNITS
    +			19:2:USEMAP
    +			20:0:VSPACE
    +			21:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			IMG
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: same
    +		flags: endO
    +	66:INPUT
    +		justify
    +		35 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCESSKEY
    +			3:0:ALIGN
    +			4:0:ALT
    +			5:0:CHECKED
    +			6:4:CLASS
    +			7:0:CLEAR
    +			8:0:DIR
    +			9:0:DISABLED
    +			10:0:ERROR
    +			11:0:HEIGHT
    +			12:1:ID
    +			13:0:ISMAP
    +			14:0:LANG
    +			15:0:MAX
    +			16:0:MAXLENGTH
    +			17:0:MD
    +			18:0:MIN
    +			19:0:NAME
    +			20:0:NOTAB
    +			21:0:ONBLUR
    +			22:0:ONCHANGE
    +			23:0:ONFOCUS
    +			24:0:ONSELECT
    +			25:0:READONLY
    +			26:0:SIZE
    +			27:2:SRC
    +			28:0:STYLE
    +			29:0:TABINDEX
    +			30:0:TITLE
    +			31:0:TYPE
    +			32:0:USEMAP
    +			33:0:VALUE
    +			34:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			INPUT
    +		contents: SGML_EMPTY
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike SELECTlike BRlike MAPlike same
    +		flags: endO
    +	67:INS
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	68:INS!
    +		justify
    +		8 attributes:
    +			0:0:CITE
    +			1:4:CLASS
    +			2:0:DATETIME
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			DEL
    +		contents: SGML_MIXED
    +		tagclass: DELlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike DELlike
    +		flags:
    +	69:ISINDEX
    +		justify
    +		9 attributes:
    +			0:2:ACTION
    +			1:4:CLASS
    +			2:0:DIR
    +			3:2:HREF
    +			4:1:ID
    +			5:0:LANG
    +			6:0:PROMPT
    +			7:0:STYLE
    +			8:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			ISINDEX
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike same
    +		flags: endO
    +	70:KBD
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike
    +		flags:
    +	71:KEYGEN
    +		justify
    +		8 attributes:
    +			0:0:CHALLENGE
    +			1:4:CLASS
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:NAME
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			KEYGEN
    +		contents: SGML_EMPTY
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: formula TRlike SELECTlike same
    +		flags: endO
    +	72:LABEL
    +		justify
    +		11 attributes:
    +			0:0:ACCESSKEY
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:FOR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:ONBLUR
    +			8:0:ONFOCUS
    +			9:0:STYLE
    +			10:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LABEL
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	73:LEGEND
    +		justify
    +		9 attributes:
    +			0:0:ACCESSKEY
    +			1:0:ALIGN
    +			2:4:CLASS
    +			3:0:CLEAR
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:STYLE
    +			8:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			CAPTION
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: DIVlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike
    +		flags:
    +	74:LH
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	75:LI
    +		justify
    +		13 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DINGBAT
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:MD
    +			7:0:SKIP
    +			8:2:SRC
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LI
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags: endO
    +	76:LINK
    +		justify
    +		14 attributes:
    +			0:0:CHARSET
    +			1:4:CLASS
    +			2:0:DIR
    +			3:2:HREF
    +			4:0:HREFLANG
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MEDIA
    +			8:0:REL
    +			9:0:REV
    +			10:0:STYLE
    +			11:0:TARGET
    +			12:0:TITLE
    +			13:0:TYPE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			LINK
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		flags: endO
    +	77:LISTING
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: ULlike
    +		contains:
    +		icontains:
    +		contained: DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike LIlike ULlike same
    +		flags:
    +	78:MAIN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	79:MAP
    +		justify
    +		8 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:1:NAME
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			MAP
    +		contents: SGML_ELEMENT
    +		tagclass: MAPlike
    +		contains: MAPlike
    +		icontains: MAPlike
    +		contained: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike LIlike
    +		flags:
    +	80:MARQUEE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: HRlike
    +		contains: FONTlike EMlike MATHlike Alike
    +		icontains: FONTlike EMlike MATHlike Alike formula BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: MATHlike Alike formula BRlike APPLETlike same
    +		flags:
    +	81:MATH
    +		justify
    +		8 attributes:
    +			0:0:BOX
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			MATH
    +		contents: SGML_PCDATA
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike Alike formula
    +		flags:
    +	82:MENU
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags:
    +	83:META
    +		justify
    +		5 attributes:
    +			0:0:CHARSET
    +			1:0:CONTENT
    +			2:0:HTTP-EQUIV
    +			3:0:NAME
    +			4:0:SCHEME
    +		1 attr_types
    +			META
    +		contents: SGML_EMPTY
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		flags: endO
    +	84:NAV
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	85:NEXTID
    +		justify
    +		1 attributes:
    +			0:0:N
    +		1 attr_types
    +			NEXTID
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer
    +		canclose: FONTlike
    +		flags: endO
    +	86:NOFRAMES
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike BODYlike DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike DELlike
    +		contained: BRlike APPLETlike HRlike outer
    +		icontained: BRlike APPLETlike HRlike outer
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike
    +		flags:
    +	87:NOTE
    +		justify
    +		10 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:MD
    +			6:8:ROLE
    +			7:2:SRC
    +			8:0:STYLE
    +			9:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			NOTE
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike ULlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: formula TRlike FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags:
    +	88:OBJECT
    +		justify
    +		26 attributes:
    +			0:0:ALIGN
    +			1:0:ARCHIVE
    +			2:0:BORDER
    +			3:4:CLASS
    +			4:2:CLASSID
    +			5:2:CODEBASE
    +			6:0:CODETYPE
    +			7:2:DATA
    +			8:0:DECLARE
    +			9:0:DIR
    +			10:0:HEIGHT
    +			11:0:HSPACE
    +			12:1:ID
    +			13:0:ISMAP
    +			14:0:LANG
    +			15:0:NAME
    +			16:0:NOTAB
    +			17:0:SHAPES
    +			18:0:STANDBY
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TITLE
    +			22:0:TYPE
    +			23:2:USEMAP
    +			24:0:VSPACE
    +			25:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			OBJECT
    +		contents: SGML_LITTERAL
    +		tagclass: APPLETlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike BRlike APPLETlike same
    +		flags: frecyc
    +	89:OL
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:CONTINUE
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:SEQNUM
    +			8:0:START
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			OL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike MAPlike same
    +		flags:
    +	90:OPTION
    +		justify
    +		13 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:0:DISABLED
    +			4:0:ERROR
    +			5:1:ID
    +			6:0:LABEL
    +			7:0:LANG
    +			8:0:SELECTED
    +			9:0:SHAPE
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALUE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			OPTION
    +		contents: SGML_PCDATA
    +		tagclass: MAPlike
    +		contains:
    +		icontains:
    +		contained: SELECTlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: endO
    +	91:OVERLAY
    +		justify
    +		12 attributes:
    +			0:4:CLASS
    +			1:0:HEIGHT
    +			2:1:ID
    +			3:0:IMAGEMAP
    +			4:0:MD
    +			5:2:SRC
    +			6:0:STYLE
    +			7:0:TITLE
    +			8:0:UNITS
    +			9:0:WIDTH
    +			10:0:X
    +			11:0:Y
    +		2 attr_types
    +			core
    +			OVERLAY
    +		contents: SGML_PCDATA
    +		tagclass: HRlike
    +		contains:
    +		icontains:
    +		contained: DIVlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike DIVlike LIlike ULlike BRlike APPLETlike same
    +		flags: endO
    +	92:P
    +		justify
    +		9 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NOWRAP
    +			7:0:STYLE
    +			8:0:TITLE
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			P
    +		contents: SGML_MIXED
    +		tagclass: Plike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike formula Plike same
    +		flags: endO
    +	93:PARAM
    +		justify
    +		18 attributes:
    +			0:0:ACCEPT
    +			1:0:ACCEPT-CHARSET
    +			2:0:ACCEPT-ENCODING
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DATA
    +			6:0:DIR
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NAME
    +			10:0:OBJECT
    +			11:0:REF
    +			12:0:STYLE
    +			13:0:TITLE
    +			14:0:TYPE
    +			15:0:VALUE
    +			16:0:VALUEREF
    +			17:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			PARAM
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: Plike LIlike BRlike APPLETlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike
    +		canclose: TRlike SELECTlike Plike LIlike BRlike same
    +		flags: endO
    +	94:PLAINTEXT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: outer
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike HEADstuff same
    +		contained: outer same
    +		icontained: outer same
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		flags: endO
    +	95:PRE
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike HRlike MAPlike
    +		icontains: EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike HRlike MAPlike
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: EMlike MATHlike Alike formula Plike DIVlike LIlike same
    +		flags:
    +	96:Q
    +		justify
    +		8 attributes:
    +			0:2:CITE
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:STYLE
    +			7:0:TITLE
    +		3 attr_types
    +			core
    +			i18n
    +			Q
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags:
    +	97:S
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	98:SAMP
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: EMlike
    +		flags: nreie
    +	99:SCRIPT
    +		justify
    +		16 attributes:
    +			0:0:CHARSET
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DEFER
    +			4:0:DIR
    +			5:0:EVENT
    +			6:0:FOR
    +			7:1:ID
    +			8:0:LANG
    +			9:0:LANGUAGE
    +			10:0:NAME
    +			11:0:SCRIPTENGINE
    +			12:2:SRC
    +			13:0:STYLE
    +			14:0:TITLE
    +			15:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			SCRIPT
    +		contents: SGML_SCRIPT
    +		tagclass: APPLETlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike BRlike APPLETlike HRlike same
    +		flags:
    +	100:SECTION
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:ROLE
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN5
    +		contents: SGML_MIXED
    +		tagclass: DIVlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike same DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike same
    +		flags: mafse
    +	101:SELECT
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DISABLED
    +			5:0:ERROR
    +			6:0:HEIGHT
    +			7:1:ID
    +			8:0:LANG
    +			9:0:MD
    +			10:0:MULTIPLE
    +			11:0:NAME
    +			12:0:NOTAB
    +			13:0:ONBLUR
    +			14:0:ONCHANGE
    +			15:0:ONFOCUS
    +			16:0:SIZE
    +			17:0:STYLE
    +			18:0:TABINDEX
    +			19:0:TITLE
    +			20:0:UNITS
    +			21:0:WIDTH
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			SELECT
    +		contents: SGML_ELEMENT
    +		tagclass: SELECTlike
    +		contains: MAPlike
    +		icontains: MAPlike
    +		contained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike same
    +		flags: strict
    +	102:SHY
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	103:SMALL
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	104:SPAN
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike same
    +		flags:
    +	105:SPOT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: Alike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer BODYlike
    +		canclose: Alike
    +		flags: endO
    +	106:STRIKE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	107:STRONG
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike
    +		flags: nreie
    +	108:STYLE
    +		justify
    +		9 attributes:
    +			0:4:CLASS
    +			1:0:DIR
    +			2:1:ID
    +			3:0:LANG
    +			4:0:MEDIA
    +			5:0:NOTATION
    +			6:0:STYLE
    +			7:0:TITLE
    +			8:0:TYPE
    +		3 attr_types
    +			core
    +			i18n
    +			STYLE
    +		contents: SGML_CDATA
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike APPLETlike HRlike outer BODYlike HEADstuff
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula same
    +		flags:
    +	109:SUB
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	110:SUP
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: MATHlike
    +		contains: FONTlike EMlike MATHlike Alike formula SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike formula FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike EMlike MATHlike
    +		flags:
    +	111:TAB
    +		justify
    +		11 attributes:
    +			0:0:ALIGN
    +			1:4:CLASS
    +			2:0:CLEAR
    +			3:0:DIR
    +			4:0:DP
    +			5:1:ID
    +			6:0:INDENT
    +			7:0:LANG
    +			8:0:STYLE
    +			9:0:TITLE
    +			10:0:TO
    +		4 attr_types
    +			align
    +			core
    +			i18n
    +			TAB
    +		contents: SGML_EMPTY
    +		tagclass: BRlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer HEADstuff
    +		canclose: FONTlike
    +		flags: endO
    +	112:TABLE
    +		justify
    +		22 attributes:
    +			0:0:ALIGN
    +			1:2:BACKGROUND
    +			2:0:BORDER
    +			3:0:CELLPADDING
    +			4:0:CELLSPACING
    +			5:4:CLASS
    +			6:0:CLEAR
    +			7:0:COLS
    +			8:0:COLSPEC
    +			9:0:DIR
    +			10:0:DP
    +			11:0:FRAME
    +			12:1:ID
    +			13:0:LANG
    +			14:0:NOFLOW
    +			15:0:NOWRAP
    +			16:0:RULES
    +			17:0:STYLE
    +			18:0:SUMMARY
    +			19:0:TITLE
    +			20:0:UNITS
    +			21:0:WIDTH
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			TABLE
    +		contents: SGML_ELEMENT
    +		tagclass: ULlike
    +		contains: TRlike SELECTlike FORMlike Plike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike TRlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike Plike LIlike HRlike MAPlike same
    +		flags:
    +	113:TBODY
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_ELEMENT
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO startO
    +	114:TD
    +		justify
    +		23 attributes:
    +			0:0:ABBR
    +			1:0:ALIGN
    +			2:0:AXES
    +			3:0:AXIS
    +			4:2:BACKGROUND
    +			5:0:CHAR
    +			6:0:CHAROFF
    +			7:4:CLASS
    +			8:0:CLEAR
    +			9:0:COLSPAN
    +			10:0:DIR
    +			11:0:DP
    +			12:0:HEADERS
    +			13:0:HEIGHT
    +			14:1:ID
    +			15:0:LANG
    +			16:0:NOWRAP
    +			17:0:ROWSPAN
    +			18:0:SCOPE
    +			19:0:STYLE
    +			20:0:TITLE
    +			21:0:VALIGN
    +			22:0:WIDTH
    +		4 attr_types
    +			cellalign
    +			core
    +			i18n
    +			TD
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO
    +	115:TEXTAREA
    +		justify
    +		22 attributes:
    +			0:0:ACCEPT-CHARSET
    +			1:0:ACCESSKEY
    +			2:0:ALIGN
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:COLS
    +			6:0:DIR
    +			7:0:DISABLED
    +			8:0:ERROR
    +			9:1:ID
    +			10:0:LANG
    +			11:0:NAME
    +			12:0:NOTAB
    +			13:0:ONBLUR
    +			14:0:ONCHANGE
    +			15:0:ONFOCUS
    +			16:0:ONSELECT
    +			17:0:READONLY
    +			18:0:ROWS
    +			19:0:STYLE
    +			20:0:TABINDEX
    +			21:0:TITLE
    +		5 attr_types
    +			align
    +			core
    +			events
    +			i18n
    +			TEXTAREA
    +		contents: SGML_PCDATA
    +		tagclass: SELECTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike outer BODYlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike LIlike ULlike same
    +		flags: nolyspcl
    +	116:TEXTFLOW
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DATA
    +			3:0:DIR
    +			4:1:ID
    +			5:0:LANG
    +			6:0:NAME
    +			7:0:OBJECT
    +			8:0:REF
    +			9:0:STYLE
    +			10:0:TITLE
    +			11:0:TYPE
    +			12:0:VALUE
    +			13:0:VALUETYPE
    +		3 attr_types
    +			core
    +			i18n
    +			BODYTEXT
    +		contents: SGML_MIXED
    +		tagclass: BODYlike
    +		contains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike same DELlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike outer same DELlike
    +		contained: formula TRlike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer same
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike BRlike APPLETlike MAPlike outer same
    +		flags: endO startO
    +	117:TFOOT
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_ELEMENT
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	118:TH
    +		justify
    +		23 attributes:
    +			0:0:ABBR
    +			1:0:ALIGN
    +			2:0:AXES
    +			3:0:AXIS
    +			4:2:BACKGROUND
    +			5:0:CHAR
    +			6:0:CHAROFF
    +			7:4:CLASS
    +			8:0:CLEAR
    +			9:0:COLSPAN
    +			10:0:DIR
    +			11:0:DP
    +			12:0:HEADERS
    +			13:0:HEIGHT
    +			14:1:ID
    +			15:0:LANG
    +			16:0:NOWRAP
    +			17:0:ROWSPAN
    +			18:0:SCOPE
    +			19:0:STYLE
    +			20:0:TITLE
    +			21:0:VALIGN
    +			22:0:WIDTH
    +		4 attr_types
    +			cellalign
    +			core
    +			i18n
    +			TD
    +		contents: SGML_MIXED
    +		tagclass: LIlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike FORMlike Plike DIVlike ULlike BRlike APPLETlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike
    +		contained: TRlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	119:THEAD
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_ELEMENT
    +		tagclass: TRlike
    +		contains: TRlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike ULlike HRlike MAPlike same
    +		flags: endO
    +	120:TITLE
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_PCDATA
    +		tagclass: HEADstuff
    +		contains:
    +		icontains:
    +		contained: outer HEADstuff
    +		icontained: outer HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula Plike DIVlike
    +		flags: mafse strict
    +	121:TR
    +		justify
    +		13 attributes:
    +			0:0:ALIGN
    +			1:0:CHAR
    +			2:0:CHAROFF
    +			3:4:CLASS
    +			4:0:CLEAR
    +			5:0:DIR
    +			6:0:DP
    +			7:1:ID
    +			8:0:LANG
    +			9:0:NOWRAP
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:VALIGN
    +		5 attr_types
    +			cellalign
    +			core
    +			events
    +			i18n
    +			TR
    +		contents: SGML_MIXED
    +		tagclass: TRlike
    +		contains: LIlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: TRlike ULlike
    +		icontained: FONTlike EMlike MATHlike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike HRlike MAPlike same
    +		flags: endO
    +	122:TT
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: nreie
    +	123:U
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: FONTlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags: mafse nreie
    +	124:UL
    +		justify
    +		14 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:COMPACT
    +			3:0:DINGBAT
    +			4:0:DIR
    +			5:1:ID
    +			6:0:LANG
    +			7:0:MD
    +			8:0:PLAIN
    +			9:2:SRC
    +			10:0:STYLE
    +			11:0:TITLE
    +			12:0:TYPE
    +			13:0:WRAP
    +		3 attr_types
    +			core
    +			i18n
    +			UL
    +		contents: SGML_MIXED
    +		tagclass: ULlike
    +		contains: FORMlike LIlike HRlike MAPlike
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FORMlike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike same DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike same
    +		flags:
    +	125:VAR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_MIXED
    +		tagclass: EMlike
    +		contains: FONTlike EMlike MATHlike Alike SELECTlike BRlike APPLETlike MAPlike same
    +		icontains: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike MAPlike same
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike BODYlike same
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff same
    +		canclose: FONTlike
    +		flags:
    +	126:WBR
    +		justify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_EMPTY
    +		tagclass: FONTlike
    +		contains:
    +		icontains:
    +		contained: FONTlike EMlike MATHlike Alike FORMlike Plike DIVlike LIlike BRlike APPLETlike HRlike outer BODYlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike FORMlike Plike DIVlike LIlike ULlike BRlike APPLETlike HRlike outer BODYlike HEADstuff
    +		canclose: FONTlike EMlike MATHlike Alike formula BRlike same
    +		flags: endO
    +	127:XMP
    +		nojustify
    +		7 attributes:
    +			0:4:CLASS
    +			1:0:CLEAR
    +			2:0:DIR
    +			3:1:ID
    +			4:0:LANG
    +			5:0:STYLE
    +			6:0:TITLE
    +		4 attr_types
    +			core
    +			events
    +			i18n
    +			GEN
    +		contents: SGML_LITTERAL
    +		tagclass: ULlike
    +		contains:
    +		icontains:
    +		contained: TRlike SELECTlike FORMlike Plike DIVlike LIlike APPLETlike HRlike outer BODYlike DELlike
    +		icontained: FONTlike EMlike MATHlike Alike formula TRlike SELECTlike FORMlike Plike DIVlike LIlike ULlike APPLETlike HRlike outer BODYlike DELlike
    +		canclose: FONTlike EMlike MATHlike Alike formula SELECTlike Plike DIVlike LIlike MAPlike
    +		flags: endO
    diff --git a/WWW/Library/Implementation/tidy_tls.h b/WWW/Library/Implementation/tidy_tls.h
    new file mode 100644
    index 0000000..56b3636
    --- /dev/null
    +++ b/WWW/Library/Implementation/tidy_tls.h
    @@ -0,0 +1,147 @@
    +/*
    + * $LynxId: tidy_tls.h,v 1.8 2015/10/12 00:28:18 tom Exp $
    + * Copyright 2008-2013,2015 Thomas E. Dickey
    + */
    +#ifndef TIDY_TLS_H
    +#define TIDY_TLS_H
    +
    +#include 
    +
    +#define OPENSSL_VERSION_NUMBER (0x0090604F)
    +#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER
    +
    +#define SSLeay_add_ssl_algorithms()   SSL_library_init()
    +
    +#define SSL_ST_OK       (1)
    +
    +#define SSL_OP_ALL      (0x000FFFFF)
    +#define SSL_OP_NO_SSLv2 (0x00100000)
    +#define SSL_OP_NO_SSLv3 (0x00200000)
    +#define SSL_OP_NO_TLSv1 (0x00400000)
    +
    +#define SSL_get_cipher_name(ssl)    SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))
    +#define SSL_get_cipher(ssl)         SSL_get_cipher_name(ssl)
    +#define SSL_get_cipher_bits(ssl,bp) SSL_CIPHER_get_bits(SSL_get_current_cipher(ssl),(bp))
    +#define SSL_get_cipher_version(ssl) SSL_CIPHER_get_version(SSL_get_current_cipher(ssl))
    +
    +#define TIDY_TLS_BUFSIZE 256
    +
    +typedef struct {
    +    char common_name[TIDY_TLS_BUFSIZE];
    +    char country[TIDY_TLS_BUFSIZE];
    +    char email[TIDY_TLS_BUFSIZE];
    +    char locality_name[TIDY_TLS_BUFSIZE];
    +    char organization[TIDY_TLS_BUFSIZE];
    +    char organizational_unit_name[TIDY_TLS_BUFSIZE];
    +    char state_or_province_name[TIDY_TLS_BUFSIZE];
    +} X509_NAME;
    +
    +typedef struct _SSL SSL;
    +
    +typedef gnutls_datum_t X509;
    +
    +typedef struct {
    +    unsigned connend;
    +    struct {
    +	int protocol[GNUTLS_MAX_ALGORITHM_NUM];
    +	int encrypts[GNUTLS_MAX_ALGORITHM_NUM];
    +	int compress[GNUTLS_MAX_ALGORITHM_NUM];
    +	int key_xchg[GNUTLS_MAX_ALGORITHM_NUM];
    +	int msg_code[GNUTLS_MAX_ALGORITHM_NUM];
    +    } priority;
    +} SSL_METHOD;
    +
    +typedef struct {
    +    SSL *ssl;
    +    int error;
    +    const gnutls_datum_t *cert_list;
    +#define current_cert cert_list
    +} X509_STORE_CTX;
    +
    +typedef struct {
    +    gnutls_certificate_type_t cert;
    +    gnutls_cipher_algorithm_t encrypts;
    +    gnutls_compression_method_t compress;
    +    gnutls_kx_algorithm_t key_xchg;
    +    gnutls_mac_algorithm_t msg_code;
    +    gnutls_protocol_t protocol;
    +} SSL_CIPHER;
    +
    +typedef struct _SSL_CTX {
    +    SSL_METHOD *method;
    +    char *certfile;
    +    int certfile_type;
    +    char *keyfile;
    +    int keyfile_type;
    +    unsigned long options;
    +
    +    int (*verify_callback) (int, X509_STORE_CTX *);
    +    int verify_mode;
    +
    +    char *client_certfile;
    +    int client_certfile_type;
    +    char *client_keyfile;
    +    int client_keyfile_type;
    +
    +} SSL_CTX;
    +
    +struct _SSL {
    +    gnutls_session_t gnutls_state;
    +
    +    gnutls_certificate_client_credentials gnutls_cred;
    +
    +    SSL_CTX *ctx;
    +    SSL_CIPHER ciphersuite;
    +
    +    int last_error;
    +    int shutdown;
    +    int state;
    +    unsigned long options;
    +
    +    int (*verify_callback) (int, X509_STORE_CTX *);
    +    int verify_mode;
    +
    +    gnutls_transport_ptr_t rfd;
    +    gnutls_transport_ptr_t wfd;
    +
    +    void *sendbuffer;
    +    size_t bytes_sent;
    +};
    +
    +/* use either SSL_VERIFY_NONE or SSL_VERIFY_PEER, the last 2 options
    + * are 'ored' with SSL_VERIFY_PEER if they are desired */
    +#define SSL_VERIFY_PEER			0x01
    +/* *INDENT-OFF* */
    +extern SSL *SSL_new(SSL_CTX * ctx);
    +extern SSL_CIPHER *SSL_get_current_cipher(SSL * ssl);
    +extern SSL_CTX *SSL_CTX_new(SSL_METHOD * method);
    +extern SSL_METHOD *SSLv23_client_method(void);
    +extern const X509 *SSL_get_peer_certificate(SSL * ssl);
    +extern X509_NAME *X509_get_issuer_name(const X509 * cert);
    +extern X509_NAME *X509_get_subject_name(const X509 * cert);
    +extern char *X509_NAME_oneline(X509_NAME * name, char *buf, int len);
    +extern const char *ERR_error_string(unsigned long e, char *buf);
    +extern const char *RAND_file_name(char *buf, size_t len);
    +extern const char *SSL_CIPHER_get_name(SSL_CIPHER * cipher);
    +extern const char *SSL_CIPHER_get_version(SSL_CIPHER * cipher);
    +extern int RAND_bytes(unsigned char *buf, int num);
    +extern int RAND_load_file(const char *name, long maxbytes);
    +extern int RAND_status(void);
    +extern int RAND_write_file(const char *name);
    +extern int SSL_CIPHER_get_bits(SSL_CIPHER * cipher, int *bits);
    +extern int SSL_CTX_set_default_verify_paths(SSL_CTX * ctx);
    +extern int SSL_connect(SSL * ssl);
    +extern int SSL_library_init(void);
    +extern int SSL_read(SSL * ssl, void *buf, int len);
    +extern int SSL_set_fd(SSL * ssl, int fd);
    +extern int SSL_write(SSL * ssl, const void *buf, int len);
    +extern unsigned long ERR_get_error(void);
    +extern unsigned long SSL_CTX_set_options(SSL_CTX * ctx, unsigned long options);
    +extern void RAND_seed(const void *buf, int num);
    +extern void SSL_CTX_free(SSL_CTX * ctx);
    +extern void SSL_CTX_set_verify(SSL_CTX * ctx, int verify_mode, int (*verify_callback) (int, X509_STORE_CTX *));
    +extern void SSL_free(SSL * ssl);
    +extern void SSL_load_error_strings(void);
    +/* *INDENT-ON* */
    +
    +#endif /* TIDY_TLS_H */
    diff --git a/WWW/Library/Implementation/www_tcp.h b/WWW/Library/Implementation/www_tcp.h
    new file mode 100644
    index 0000000..ef754c8
    --- /dev/null
    +++ b/WWW/Library/Implementation/www_tcp.h
    @@ -0,0 +1,1017 @@
    +/*                System dependencies in the W3 library
    + * $LynxId: www_tcp.h,v 1.61 2018/12/26 12:30:14 tom Exp $
    + *
    +                                   SYSTEM DEPENDENCIES
    +
    +   System-system differences for TCP include files and macros.  This
    +   file includes for each system the files necessary for network and
    +   file I/O.  It should be used in conjunction with HTUtils.h to help
    +   ensure portability across as many platforms and flavors of platforms
    +   as possible.
    +
    +  AUTHORS
    +
    +  TBL                Tim Berners-Lee, W3 project, CERN, 
    +  EvA                     Eelco van Asperen 
    +  MA                      Marc Andreessen NCSA
    +  AT                      Aleksandar Totic 
    +  SCW                     Susan C. Weber 
    +
    +  HISTORY:
    +  22 Feb 91               Written (TBL) as part of the WWW library.
    +  16 Jan 92               PC code from EvA
    +  22 Apr 93               Merged diffs bits from xmosaic release
    +  29 Apr 93               Windows/NT code from SCW
    +  20 May 94		  A.Harper Add support for VMS CMU TCP/IP transport
    +   3 Oct 94		  A.Harper Add support for VMS SOCKETSHR/NETLIB
    +  15 Jul 95               S. Bjorndahl Gnu C for VMS Globaldef/ref support
    +
    +*/
    +
    +#ifndef TCP_H
    +#define TCP_H
    +
    +/*
    +
    +Default values
    +
    +   These values may be reset and altered by system-specific sections
    +   later on.  there are also a bunch of defaults at the end .
    +
    + */
    +/* Default values of those: */
    +	/* Routine to close a TCP-IP socket         */
    +#define NETCLOSE close
    +	/* Routine to read from a TCP-IP socket     */
    +#define NETREAD(s,p,n) \
    +	HTDoRead(s, p, (unsigned)(n))
    +	/* Routine to write to a TCP-IP socket      */
    +#define NETWRITE(s,p,n)		write(s, p, (size_t)(n))
    +#define SOCKET_READ(s,b,l)	read(s,b,(size_t)(l))
    +#define IOCTL(s,cmd,arg)	ioctl(s,(long)(cmd),arg)
    +#define SOCKET_ERRNO errno	/* normal socket errno */
    +
    +/* Unless stated otherwise, */
    +#define SELECT			/* Can handle >1 channel.               */
    +#define GOT_SYSTEM		/* Can call shell with string           */
    +
    +#ifdef UNIX
    +#define GOT_PIPE
    +#endif /* UNIX */
    +
    +#define INVSOC (-1)		/* Unix invalid socket */
    +		/* NB: newer libwww has something different for Windows */
    +
    +#ifndef VMS
    +
    +#include 
    +
    +#if defined(__BORLANDC__)
    +#define DECL_ERRNO
    +#endif
    +
    +#if defined(__DJGPP__) || defined(__BORLANDC__)
    +#undef HAVE_DIRENT_H
    +#define HAVE_DIRENT_H
    +#undef HAVE_SYS_FILIO_H
    +#endif /* DJGPP or __BORLANDC__ */
    +
    +#if defined(_MSC_VER)
    +#undef HAVE_DIRENT_H
    +#define HAVE_DIRENT_H
    +#undef HAVE_SYS_FILIO_H
    +#endif /* _MSC_VER */
    +
    +#ifdef HAVE_DIRENT_H
    +# include 
    +# define D_NAMLEN(dirent) strlen((dirent)->d_name)
    +# define STRUCT_DIRENT struct dirent
    +#else
    +# define D_NAMLEN(dirent) (dirent)->d_namlen
    +# define STRUCT_DIRENT struct direct
    +# ifdef HAVE_SYS_NDIR_H
    +#  include 
    +# endif
    +# ifdef HAVE_SYS_DIR_H
    +#  include 
    +# endif
    +# ifdef HAVE_NDIR_H
    +#  include 
    +# endif
    +#endif /* HAVE_DIRENT_H */
    +
    +#ifdef HAVE_STRUCT_DIRENT64
    +# undef STRUCT_DIRENT
    +# define STRUCT_DIRENT struct dirent64
    +#endif
    +
    +#if !(defined(DOSPATH) || defined(__EMX__) || defined(__CYGWIN__))
    +#define STRUCT_DIRENT__D_INO 1
    +#endif
    +
    +#endif /* !VMS */
    +
    +#ifdef TIME_WITH_SYS_TIME
    +# include 
    +# include 
    +#else
    +# ifdef HAVE_SYS_TIME_H
    +#  include 
    +# else
    +#  include 
    +# endif
    +#endif
    +
    +#if defined(_AIX) && !defined(AIX)
    +#define AIX
    +#endif /* _AIX */
    +
    +#ifndef __CYGWIN__
    +#ifdef WIN_EX
    +#define HAVE_FTIME 1
    +#define HAVE_SYS_TIMEB_H 1
    +#endif
    +#endif /* __CYGWIN__ */
    +
    +#ifdef HAVE_FCNTL_H
    +#include 
    +#else
    +#ifdef HAVE_SYS_FCNTL_H
    +#include 
    +#endif
    +#endif
    +
    +#ifdef HAVE_STRING_H
    +#include 		/* For bzero etc */
    +#endif /* HAVE_STRING_H */
    +
    +/*
    +
    +  MACROS FOR CONVERTING CHARACTERS
    +
    + */
    +#ifndef TOASCII
    +#ifdef EBCDIC			/* S/390 -- gil -- 1327 */
    +
    +extern const char un_IBM1047[];
    +extern const unsigned char IBM1047[];
    +
    +/* For debugging
    +#include 
    +#define   TOASCII(c) (assert((c)>=0 && (c)<256), un_IBM1047[c])
    +*/
    +/* for production */
    +#define   TOASCII(c) (un_IBM1047[c])
    +
    +#define FROMASCII(c) (IBM1047[c])
    +
    +#else /* EBCDIC */
    +
    +#if '0' != 48
    +error Host character set is not ASCII.
    +#endif
    +
    +#define TOASCII(c) (c)
    +#define FROMASCII(c) (c)
    +
    +#endif				/* EBCDIC */
    +#endif				/* !TOASCII */
    +
    +/* convert a char to an unsigned, needed if we have signed characters for ctype.h */
    +#define UCH(ch) ((unsigned char)(ch))
    +
    +/*
    + * These parameters were provided by Nigel Horne, using BeOS professional 5.0
    + */
    +#ifdef  __BEOS__
    +#undef NETREAD
    +#undef NETWRITE
    +#undef NETCLOSE
    +#define NETREAD(s,b,l)  recv((s),(b),(l),0)
    +#define NETWRITE(s,b,l) send((s),(b),(l),0)
    +#define NETCLOSE(s)     closesocket(s)
    +#endif
    +
    +/*
    +IBM-PC running Windows NT
    +
    +	These parameters provided by  Susan C. Weber .
    +*/
    +
    +#ifdef _WINDOWS
    +
    +#ifndef _WINDOWS_NSL
    +#define _WINDOWS_NSL
    +#endif
    +
    +#include 		/* For HTFile.c */
    +#include 		/* For HTFile.c */
    +#include 		/* For HTFile.c */
    +#undef NETREAD
    +#undef NETWRITE
    +#undef NETCLOSE
    +#undef IOCTL
    +extern int ws_netread(int fd, char *buf, int len);
    +
    +#define NETREAD(s,b,l)  ws_netread((s),(b),(l))		/* 1997/11/06 (Thu) */
    +#define NETWRITE(s,b,l) send((s),(b),(l),0)
    +#define NETCLOSE(s)     closesocket(s)
    +#define IOCTL(s,cmd,arg)	ioctlsocket(s,cmd,arg)
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef ENABLE_IPV6
    +#undef  USE_WINSOCK2_H
    +#define USE_WINSOCK2_H
    +
    +/* Avoid including  in  */
    +#ifndef WIN32_LEAN_AND_MEAN
    +#error Define "WIN32_LEAN_AND_MEAN" in your makefile
    +#endif
    +
    +#ifdef _WINSOCKAPI_
    +#error windows.h included before winsock2.h
    +#endif
    +
    +#if defined(_MSC_VER) && (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501)
    +/*
    + * Needed to pull in the real getaddrinfo() and not the inline version
    + * in  which doesn't support IPv6 (IPv4 only).  is
    + * included from  for <= 0x0500 SDKs.
    + */
    +#undef  _WIN32_WINNT
    +#define _WIN32_WINNT 0x0501
    +#endif
    +#endif /* ENABLE_IPV6 */
    +
    +#ifdef USE_WINSOCK2_H
    +#include 		/* normally included in windows.h */
    +
    +#ifdef ENABLE_IPV6
    +#include 
    +#endif
    +
    +#undef EINPROGRESS
    +#undef EALREADY
    +#undef EISCONN
    +#undef EINTR
    +#undef EAGAIN
    +#undef ENOTCONN
    +#undef ECONNRESET
    +#undef ETIMEDOUT
    +
    +#define EINPROGRESS  WSAEINPROGRESS
    +#define EALREADY     WSAEALREADY
    +#define EISCONN      WSAEISCONN
    +#define EINTR        WSAEINTR
    +/* fine EAGAIN       WSAEAGAIN */
    +#define ENOTCONN     WSAENOTCONN
    +#define ECONNRESET   WSAECONNRESET
    +#define ETIMEDOUT    WSAETIMEDOUT
    +
    +#else /* USE_WINSOCK2_H */
    +
    +#include 
    +
    +#if defined(_MSC_VER) || defined(__MINGW32__)
    +#undef EINTR
    +#undef EAGAIN
    +#endif /* _MSC_VER */
    +
    +#undef EINPROGRESS
    +#define EINPROGRESS          (WSABASEERR+36)
    +
    +#undef EALREADY
    +#define EALREADY             (WSABASEERR+37)
    +
    +#undef EISCONN
    +#define EISCONN              (WSABASEERR+56)
    +
    +#undef EINTR
    +#define EINTR                (WSABASEERR+4)
    +
    +#undef EAGAIN
    +#define EAGAIN               (WSABASEERR+1002)
    +
    +#undef ENOTCONN
    +#define ENOTCONN             (WSABASEERR+57)
    +
    +#undef ECONNRESET
    +#define ECONNRESET           (WSABASEERR+54)
    +
    +#undef ETIMEDOUT
    +#define ETIMEDOUT             WSAETIMEDOUT
    +
    +#endif /* USE_WINSOCK2_H */
    +
    +#undef  SOCKET_ERRNO
    +#define SOCKET_ERRNO          WSAGetLastError()
    +
    +#define INCLUDES_DONE
    +#define TCP_INCLUDES_DONE
    +
    +#define LYNX_FD	SOCKET		/* WINSOCK uses an unsigned value for FD_SET, etc */
    +
    +#else
    +
    +#define LYNX_FD	int		/* POSIX says FD_SET descriptor is int */
    +
    +#endif				/* WINDOWS */
    +
    +/*
    +
    +VAX/VMS
    +
    +   Under VMS, there are many versions of TCP-IP. Define one if you do
    +   not use Digital's UCX product:
    +
    +  UCX                     DEC's "Ultrix connection" (default)
    +  CMU_TCP                 Available via FTP from sacusr.mp.usbr.gov
    +  SOCKETSHR               Eckhart Meyer's interface to NETLIB
    +  WIN_TCP                 From Wollongong, now GEC software.
    +  MULTINET                From SRI, became TGV, then Cisco.
    +  DECNET                  Cern's TCP socket emulation over DECnet
    +  TCPIP_SERVICES          TCP/IP Services (newer than UCX)
    +
    +   WIN_TCP, MULTINET and DECNET do not interfere with the
    +   unix i/o library, and so they need special calls to read, write and
    +   close sockets.  In these cases the socket number is a VMS channel
    +   number, so we make the @@@ HORRIBLE @@@ assumption that a channel
    +   number will be greater than 10 but a unix file descriptor less than
    +   10.  It works.
    +
    + */
    +#ifdef VMS
    +
    +#ifdef UCX
    +#undef IOCTL
    +#define IOCTL(s,cmd,arg)	HTioctl(s,cmd,arg)
    +#endif				/* UCX */
    +
    +#ifdef WIN_TCP
    +#undef SOCKET_READ
    +#undef NETWRITE
    +#undef NETCLOSE
    +#define SOCKET_READ(s,b,l)  ((s)>10 ? netread((s),(b),(l)) : read((s),(b),(l)))
    +#define NETWRITE(s,b,l)     ((s)>10 ? netwrite((s),(b),(l)) : write((s),(b),(l)))
    +#define NETCLOSE(s)         ((s)>10 ? netclose(s) : close(s))
    +#undef IOCTL
    +#define IOCTL(a,b,c) -1		/* disables ioctl function            */
    +#define NO_IOCTL		/* flag to check if ioctl is disabled */
    +#endif				/* WIN_TCP */
    +
    +#ifdef CMU_TCP
    +#undef SOCKET_READ
    +#undef NETREAD
    +#undef NETWRITE
    +#undef NETCLOSE
    +#define SOCKET_READ(s,b,l) (cmu_get_sdc((s)) != 0 ? cmu_read((s),(b),(l)) : read((s),(b),(l)))
    +#define NETREAD(s,b,l)     (cmu_get_sdc((s)) != 0 ? HTDoRead((s),(b),(l)) : read((s),(b),(l)))
    +#define NETWRITE(s,b,l)    (cmu_get_sdc((s)) != 0 ? cmu_write((s),(b),(l)) : write((s),(b),(l)))
    +#define NETCLOSE(s)        (cmu_get_sdc((s)) != 0 ? cmu_close((s)) : close((s)))
    +#endif				/* CMU_TCP */
    +
    +#ifdef MULTINET
    +#undef NETCLOSE
    +#undef SOCKET_READ
    +#undef NETWRITE
    +#undef IOCTL
    +#undef SOCKET_ERRNO
    +/*
    + *  Delete these socket_foo() prototypes as MultiNet adds them
    + *  to it's socket library headers.  Compiler warnings due to
    + *  the absence of arguments in the generic prototypes here will
    + *  include the names of those which can be deleted. - FM
    + */
    +extern int socket_read();
    +extern int socket_write();
    +extern int socket_close();
    +extern int socket_ioctl();
    +
    +#define SOCKET_READ(s,b,l)  ((s)>10 ? socket_read((s),(b),(l)) : \
    +				read((s),(b),(l)))
    +#define NETWRITE(s,b,l)     ((s)>10 ? socket_write((s),(b),(l)) : \
    +                                write((s),(b),(l)))
    +#define NETCLOSE(s)         ((s)>10 ? socket_close(s) : close(s))
    +#define IOCTL(s,cmd,arg)	socket_ioctl(s,cmd,arg)
    +#define SOCKET_ERRNO socket_errno
    +#endif /* MULTINET */
    +
    +#ifdef SOCKETSHR_TCP
    +#undef SOCKET_READ
    +#undef NETREAD
    +#undef NETWRITE
    +#undef NETCLOSE
    +#undef IOCTL
    +#define SOCKET_READ(s,b,l)  (si_get_sdc((s)) != 0 ? si_read((s),(b),(l)) : \
    +                                read((s),(b),(l)))
    +#define NETREAD(s,b,l)      (si_get_sdc((s)) != 0 ? HTDoRead((s),(b),(l)) : \
    +                                read((s),(b),(l)))
    +#define NETWRITE(s,b,l)     (si_get_sdc((s)) != 0 ? si_write((s),(b),(l)) : \
    +                                write((s),(b),(l)))
    +#define NETCLOSE(s)         (si_get_sdc((s)) != 0 ? si_close((s)) : close((s)))
    +#define IOCTL(s,cmd,arg)	si_ioctl(s,cmd,arg)
    +#endif /* SOCKETSHR_TCP */
    +
    +#ifdef TCPIP_SERVICES
    +/*
    + * TCPIP Services has all of the entrypoints including ioctl().
    + */
    +#undef NETWRITE
    +#define NETWRITE(s,b,l) send((s),(char *)(b),(l),0)
    +
    +#define TYPE_FD_SET int
    +
    +#if 0				/* this should be declared via time.h */
    +typedef TYPE_FD_SET fd_set;
    +#endif
    +
    +#endif /* TCPIP_SERVICES */
    +
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#define INCLUDES_DONE
    +
    +#ifdef MULTINET			/* Include from standard Multinet directories */
    +/*
    + *  Delete any of these multinet_foo() and associated prototypes
    + *  as MultiNet adds them to its socket library headers.  You'll
    + *  get compiler warnings about them, due the absence of arguments
    + *  in the generic prototyping here, and the warnings will include
    + *  the names of the functions whose prototype entries can be
    + *  deleted here. - FM
    + */
    +extern int multinet_accept();
    +extern int multinet_bind();
    +extern int multinet_connect();
    +extern int multinet_gethostname();
    +extern int multinet_getsockname();
    +extern unsigned short multinet_htons(unsigned short __val);
    +extern unsigned short multinet_ntohs(unsigned short __val);
    +extern int multinet_listen();
    +extern int multinet_select();
    +extern int multinet_socket();
    +extern char *vms_errno_string();
    +
    +#ifndef __SOCKET_TYPEDEFS
    +#define __SOCKET_TYPEDEFS 1
    +#endif /* !__SOCKET_TYPEDEFS */
    +
    +#include 
    +#include 
    +/*
    + * DEC C before version 5.2 added some typedefs to  which happen
    + * to be suppressed if the version-4 compatibility define is set.  In
    + * particular, lynx uses "off_t".  VAX-C used "unsigned", DEC-C uses "int".
    + */
    +#if defined(_DECC_V4_SOURCE) && !defined(____OFF_T)
    +#undef off_t
    +#define off_t int
    +#endif
    +
    +#ifdef __TIME_T
    +#undef  __TYPES
    +#define __TYPES 1
    +#define __TYPES_LOADED 1
    +#endif /* __TIME_T */
    +
    +#ifdef __SOCKET_TYPEDEFS
    +#undef __SOCKET_TYPEDEFS
    +#endif /* __SOCKET_TYPEDEFS */
    +
    +#include "multinet_root:[multinet.include.sys]types.h"
    +
    +#ifndef __SOCKET_TYPEDEFS
    +#define __SOCKET_TYPEDEFS 1
    +#endif /* !__SOCKET_TYPEDEFS */
    +
    +#include "multinet_root:[multinet.include]errno.h"
    +
    +#ifdef __TYPES
    +#undef  __TIME_T
    +#define __TIME_T 1
    +#endif /* __TYPE */
    +
    +#ifdef __TIME_LOADED
    +#undef  __TIME
    +#define __TIME 1		/* to avoid double definitions in in.h */
    +#endif /* __TIME_LOADED */
    +
    +#include "multinet_root:[multinet.include.sys]time.h"
    +
    +#define MULTINET_NO_PROTOTYPES	/* DECC is compatible-but-different */
    +#include "multinet_root:[multinet.include.sys]socket.h"
    +#undef MULTINET_NO_PROTOTYPES
    +#include "multinet_root:[multinet.include.netinet]in.h"
    +#include "multinet_root:[multinet.include.arpa]inet.h"
    +#include "multinet_root:[multinet.include]netdb.h"
    +#include "multinet_root:[multinet.include.sys]ioctl.h"
    +#define TCP_INCLUDES_DONE
    +/*
    + *  Uncomment this if you get compiler messages
    + *  about struct timeval having no linkage. - FM
    + */
    +/*#define NO_TIMEVAL*/
    +#ifdef NO_TIMEVAL
    +struct timeval {
    +    long tv_sec;		/* seconds since Jan. 1, 1970 */
    +    long tv_usec;		/* microseconds */
    +};
    +#endif /* NO_TIMEVAL */
    +#endif /* MULTINET */
    +
    +#ifdef DECNET
    +#include 
    +#include 
    +#include 
    +#include 		/* for socket.h */
    +#include 
    +#include 
    +#include 
    +/* #include "vms.h" */
    +#define TCP_INCLUDES_DONE
    +#endif /* DECNET */
    +
    +#ifdef UCX
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#if defined(TCPWARE) && !defined(__DECC)
    +#include "tcpware_include:netdb.h"
    +#include "tcpware_include:ucx$inetdef.h"
    +#else
    +#include 
    +#ifdef MUCX
    +#include 
    +#else
    +#include 
    +#endif /* MUCX */
    +#endif /* TCPWARE */
    +#define TCP_INCLUDES_DONE
    +#endif /* UCX */
    +
    +#ifdef CMU_TCP
    +#include 
    +#include 
    +#include "cmuip_root:[syslib]time.h"
    +#include "cmuip_root:[syslib]socket.h"
    +#include 
    +#include 
    +#include 
    +#include "cmuip_root:[syslib]ioctl.h"
    +#define TCP_INCLUDES_DONE
    +#endif /* CMU_TCP */
    +
    +#ifdef SOCKETSHR_TCP
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include "socketshr_library:socketshr.h"
    +#include "socketshr_library:ioctl.h"
    +#define TCP_INCLUDES_DONE
    +#endif /* SOCKETSHR_TCP */
    +
    +#ifdef TCPIP_SERVICES
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#define TCP_INCLUDES_DONE
    +#endif /* TCPIP_SERVICES */
    +
    +#ifdef WIN_TCP
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#ifndef NO_IOCTL
    +#include 
    +#endif /* !NO_IOCTL */
    +#define TCP_INCLUDES_DONE
    +#endif /* WIN_TCP */
    +
    +#ifndef TCP_INCLUDES_DONE
    +#include 
    +#include 
    +#include 
    +#ifdef VMS_SOCKET_HEADERS
    +/*
    + *  Not all versions of VMS have the full set of headers
    + *  for socket library functions, because the TCP/IP
    + *  packages were layered products.  If we want these
    + *  specifically, instead of those for the above packages,
    + *  the module should be compiled with VMS_SOCKET_HEADERS
    + *  defined instead of layered product definitions, above.
    + *  If the module is not using socket library functions,
    + *  none of the definitions need be used, and we include
    + *  only the above three headers. - FM
    + */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#endif /* VMS_SOCKET_HEADERS */
    +#define TCP_INCLUDES_DONE
    +#endif /* !TCP_INCLUDES_DONE */
    +
    +/*
    + * On VMS machines, the linker needs to be told to put global data sections
    + * into a data segment using these storage classes.  (MarkDonszelmann)
    + */
    +#if defined(VAXC) && !defined(__DECC)
    +#define GLOBALDEF globaldef
    +#define GLOBALREF globalref
    +#else
    +#ifdef __GNUC__			/* this added by Sterling Bjorndahl */
    +#define GLOBALREF_IS_MACRO 1
    +#define GLOBALDEF_IS_MACRO 1
    +#include 		/* defines GLOBALREF and GLOBALDEF for GNUC on VMS */
    +#endif /* __GNUC__ */
    +#endif /* VAXC && !DECC */
    +
    +#include 
    +#ifndef errno
    +extern int errno;
    +#endif /* !errno */
    +
    +#endif /* VMS */
    +
    +/*
    + * On non-VMS machines and for DECC on VMS, the GLOBALDEF and GLOBALREF
    + * storage types default to normal C storage types.
    + */
    +#ifndef GLOBALREF
    +#define GLOBALDEF
    +#define GLOBALREF extern
    +#endif /* !GLOBALREF */
    +
    +#ifdef __DJGPP__
    +#undef SELECT
    +#define TCP_INCLUDES_DONE
    +#undef  IOCTL
    +#define IOCTL(s,cmd,arg)	ioctlsocket(s,cmd,(char*)(arg))
    +#define DECL_ERRNO
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#ifdef word
    +#undef word
    +#endif /* word */
    +#ifdef set_timeout
    +#undef set_timeout
    +#endif /* set_timeout */
    +#define select select_s
    +
    +#undef NETWRITE
    +#define NETWRITE write_s
    +#undef NETREAD
    +#define NETREAD read_s
    +#undef NETCLOSE
    +#define NETCLOSE close_s
    +#ifdef UNIX
    +#undef UNIX
    +#endif /* UNIX */
    +#ifdef HAVE_GETTEXT
    +#define gettext gettext__
    +#endif
    +#if !defined(NCURSES) && !defined(USE_SLANG)
    +#define HAVE_CBREAK 1
    +#endif /* !NCURSES && !USE_SLANG */
    +#if defined(USE_SLANG) && !defined(NO_DJ_KEYHANDLER) && defined(HAVE_CONFIG_H)
    +#define DJGPP_KEYHANDLER
    +#endif /* USE_SLANG && !NO_DJ_KEYHANDLER  && HAVE_CONFIG_H */
    +#endif /* DJGPP */
    +
    +#ifdef HAVE_UNISTD_H
    +#include 
    +#endif /* HAVE_UNISTD_H */
    +
    +#ifdef HAVE_SYS_FILIO_H
    +#include 
    +#endif /* HAVE_SYS_FILIO_H */
    +
    +#if !defined(HAVE_LSTAT) && !defined(lstat)
    +#define lstat(path,block) stat(path,block)
    +#endif
    +
    +#if defined(DECL_ERRNO) && !defined(errno)
    +extern int errno;
    +#endif /* DECL_ERRNO */
    +
    +/*
    +Regular BSD unix versions
    +=========================
    +   These are a default unix where not already defined specifically.
    + */
    +#ifndef INCLUDES_DONE
    +#include 
    +#ifdef HAVE_STRING_H
    +#include 
    +#endif /* HAVE_STRING_H */
    +#include 		/* independent */
    +#ifdef __MVS__			/* S/390 -- gil -- 1361 */
    +#include 
    +#endif /* __MVS__ */
    +#ifdef SCO
    +#include 
    +#include 
    +#endif /* SCO */
    +#if defined(AIX) || defined(SVR4)
    +#include 
    +#endif /* AIX || SVR4 */
    +#include 		/* independent */
    +#include 
    +#ifndef __MVS__			/* S/390 -- gil -- 1373 */
    +#include 
    +#endif /* __MVS__ */
    +#include 		/* For open() etc */
    +
    +#if defined(NeXT) || defined(sony_news)
    +#ifndef mode_t
    +typedef unsigned short mode_t;
    +#endif /* !mode_t */
    +
    +#endif /* NeXT || sony_news */
    +
    +#define INCLUDES_DONE
    +#endif /* Normal includes */
    +
    +/* FIXME: this should be autoconf'd */
    +/* Interactive UNIX for i386 and i486 -- Thanks to jeffrey@itm.itm.org */
    +#ifdef ISC
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#ifndef MERGE
    +#define MERGE
    +#include 
    +#undef MERGE
    +#else
    +#include 
    +#endif /* !MERGE */
    +#ifndef USE_DIRENT
    +#define USE_DIRENT		/* sys V style directory open */
    +#endif /* USE_DIRENT */
    +#include 
    +#endif /* ISC */
    +
    +/*	Directory reading stuff - BSD or SYS V
    +*/
    +#ifdef HAVE_CONFIG_H
    +
    +# ifdef HAVE_LIMITS_H
    +#  include 
    +# endif	/* HAVE_LIMITS_H */
    +# if !defined(MAXINT) && defined(INT_MAX)
    +#  define MAXINT INT_MAX
    +# endif	/* !MAXINT && INT_MAX */
    +
    +#else
    +
    +#if !(defined(VM) || defined(VMS) || defined(THINK_C) || defined(PCNFS) || defined(_WINDOWS))
    +#define DECL_SYS_ERRLIST 1
    +#endif
    +
    +#if defined(VMS)
    +#define socklen_t unsigned
    +#else
    +#define socklen_t int		/* used for default LY_SOCKLEN definition */
    +#endif
    +
    +#endif /* !HAVE_CONFIG_H */
    +
    +#ifdef HAVE_LIBINTL_H
    +#include 
    +#endif
    +
    +#ifdef HAVE_LIBGETTEXT_H
    +#include 
    +#endif
    +
    +#define N_(s) s
    +
    +#ifndef HAVE_GETTEXT
    +#define gettext(s) s
    +#endif
    +
    +#ifndef NLS_TEXTDOMAIN
    +#define NLS_TEXTDOMAIN "lynx"
    +#endif
    +
    +/*
    +Defaults
    +========
    +  INCLUDE FILES FOR TCP
    + */
    +#ifndef TCP_INCLUDES_DONE
    +#ifndef NO_IOCTL
    +#include 		/* EJB */
    +#endif /* !NO_IOCTL */
    +#include 
    +#include 
    +#ifdef HAVE_ARPA_INET_H
    +#include 		/* Must be after netinet/in.h */
    +#endif
    +#include 
    +#endif /* TCP includes */
    +
    +typedef unsigned short PortNumber;
    +
    +#ifndef S_ISLNK
    +#define S_ISLNK(m)	(((m) & S_IFMT) == S_IFLNK)
    +#endif /* S_ISLNK */
    +
    +#ifndef S_ISDIR
    +#define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
    +#endif /* S_ISDIR */
    +
    +#ifndef S_ISREG
    +#define S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)
    +#endif /* S_ISREG */
    +
    +#ifndef S_ISUID
    +#define S_ISUID  0004000
    +#endif
    +#ifndef S_ISGID
    +#define S_ISGID  0002000
    +#endif
    +#ifndef S_ISVTX
    +#define S_ISVTX  0001000
    +#endif
    +
    +#ifndef S_IRWXU
    +#define S_IRWXU 00700
    +#endif
    +
    +#ifndef S_IRUSR
    +#define S_IRUSR 00400
    +#endif
    +#ifndef S_IWUSR
    +#define S_IWUSR 00200
    +#endif
    +#ifndef S_IXUSR
    +#define S_IXUSR 00100
    +#endif
    +
    +#ifndef S_IRWXG
    +#define S_IRWXG 00070
    +#endif
    +
    +#ifndef S_IRGRP
    +#define S_IRGRP 00040
    +#endif
    +#ifndef S_IWGRP
    +#define S_IWGRP 00020
    +#endif
    +#ifndef S_IXGRP
    +#define S_IXGRP 00010
    +#endif
    +
    +#ifndef S_IRWXO
    +#define S_IRWXO 00007
    +#endif
    +
    +#ifndef S_IROTH
    +#define S_IROTH 00004
    +#endif
    +#ifndef S_IWOTH
    +#define S_IWOTH 00002
    +#endif
    +#ifndef S_IXOTH
    +#define S_IXOTH 00001
    +#endif
    +
    +/*
    +
    +ROUGH ESTIMATE OF MAX PATH LENGTH
    +
    +*/
    +#ifndef HT_MAX_PATH
    +#ifdef MAXPATHLEN
    +#define HT_MAX_PATH MAXPATHLEN
    +#else
    +#ifdef PATH_MAX
    +#define HT_MAX_PATH PATH_MAX
    +#else
    +#define HT_MAX_PATH 1024	/* Any better ideas? */
    +#endif
    +#endif
    +#endif /* HT_MAX_PATH */
    +
    +#if HT_MAX_PATH < 256
    +#undef HT_MAX_PATH
    +#define HT_MAX_PATH 256
    +#endif
    +
    +/*
    +  MACROS FOR MANIPULATING MASKS FOR SELECT()
    + */
    +#ifdef SELECT
    +#ifndef FD_SET
    +#ifndef TYPE_FD_SET
    +#define TYPE_FD_SET unsigned
    +typedef TYPE_FD_SET fd_set;
    +#endif /* !TYPE_FD_SET */
    +
    +#define FD_SET(fd,pmask)   (*(pmask)) |=  (1 << (fd))
    +#define FD_CLR(fd,pmask)   (*(pmask)) &= ~(1 << (fd))
    +#define FD_ZERO(pmask)     (*(pmask)) = 0
    +#define FD_ISSET(fd,pmask) (*(pmask) & (1 << (fd)))
    +#endif /* !FD_SET */
    +#endif /* SELECT */
    +
    +/*
    + * Macro for setting errno - only define this if you really can do it.
    + */
    +#if defined(CAN_SET_ERRNO) || (!defined(errno) && (!defined(VMS) || defined(UCX)))
    +#define set_errno(value) errno = value
    +#else
    +#define set_errno(value)	/* we do not know how */
    +#endif
    +
    +/*
    + * IPv6 support
    + */
    +#if defined(HAVE_GETADDRINFO) && defined(ENABLE_IPV6)
    +#if defined(HAVE_GAI_STRERROR)
    +#define INET6
    +#elif defined(_WINDOWS)
    +#define INET6
    +#ifndef WIN_EX
    +#error Define "WIN_EX" in your makefile.
    +#endif
    +#ifndef _MSC_VER		/* MSVC has this inlined in  */
    +#undef  gai_strerror
    +#define gai_strerror(err) w32_strerror (err)
    +#endif
    +#endif
    +#endif /* HAVE_GETADDRINFO && ENABLE_IPV6 */
    +
    +typedef union {
    +    struct sockaddr_in soc_in;
    +    struct sockaddr soc_address;
    +#ifdef INET6
    +    struct sockaddr_storage soc_storage;
    +#endif
    +} LY_SOCKADDR;
    +
    +#ifdef INET6
    +typedef struct sockaddr_storage SockA;
    +
    +#define SOCKADDR_OF(param) (&((param).soc_address))
    +
    +#ifndef SA_LEN
    +#define SA_LEN(x) (((x)->sa_family == AF_INET6) \
    +		   ? sizeof(struct sockaddr_in6) \
    +		   : (((x)->sa_family == AF_INET) \
    +		      ? sizeof(struct sockaddr_in) \
    +		      : sizeof(struct sockaddr)))	/* AF_UNSPEC? */
    +#endif
    +
    +#ifdef SIN6_LEN
    +#define SOCKADDR_LEN(param) (SOCKADDR_OF(param)->sa_len)
    +#else
    +#define SOCKADDR_LEN(param) (socklen_t) SA_LEN(SOCKADDR_OF(param))
    +#endif /* SIN6_LEN */
    +#else
    +typedef struct sockaddr_in SockA;
    +
    +#define SOCKADDR_OF(param) ((struct sockaddr *)&(param))
    +#define SOCKADDR_LEN(param) sizeof(param)
    +#endif /* INET6 */
    +
    +#ifndef MAXHOSTNAMELEN
    +#define MAXHOSTNAMELEN 128	/* Max label is 63. Should handle 2 of those */
    +#endif /* MAXHOSTNAMELEN */
    +
    +#endif /* TCP_H */
    diff --git a/WWW/Library/Implementation/www_wait.h b/WWW/Library/Implementation/www_wait.h
    new file mode 100644
    index 0000000..3e40e88
    --- /dev/null
    +++ b/WWW/Library/Implementation/www_wait.h
    @@ -0,0 +1,34 @@
    +#ifndef WWW_WAIT_H
    +#define WWW_WAIT_H 1
    +
    +#include 
    +
    +#ifdef HAVE_SYS_WAIT_H
    +#include 
    +#endif
    +
    +#ifndef WEXITSTATUS
    +# ifdef HAVE_TYPE_UNIONWAIT
    +#  define	WEXITSTATUS(status)	(status.w_retcode)
    +# else
    +#  define	WEXITSTATUS(status)	(((status) & 0xff00) >> 8)
    +# endif
    +#endif
    +
    +#ifndef WTERMSIG
    +# ifdef HAVE_TYPE_UNIONWAIT
    +#  define	WTERMSIG(status)	(status.w_termsig)
    +# else
    +#  define	WTERMSIG(status)	((status) & 0x7f)
    +# endif
    +#endif
    +
    +#ifndef WSTOPSIG
    +# ifdef HAVE_TYPE_UNIONWAIT
    +#  define	WSTOPSIG(status)	(status.w_stopsig)
    +# else
    +#  define	WSTOPSIG(status)	WEXITSTATUS(status)
    +# endif
    +#endif
    +
    +#endif /* WWW_WAIT_H */
    diff --git a/WWW/Library/djgpp/CommonMakefile b/WWW/Library/djgpp/CommonMakefile
    new file mode 100644
    index 0000000..d8d8d20
    --- /dev/null
    +++ b/WWW/Library/djgpp/CommonMakefile
    @@ -0,0 +1,334 @@
    +#	Common Makefile for W3 Library Code
    +#	-----------------------------------
    +#
    +#	(c) CERN 1990, 1991 -- see Copyright.html for conditions
    +#
    +# This file should be invariant between systems.
    +#	DEPENDENCIES NOT COMPLETE @@
    +#
    +#	make		Compile and link the software (private version)
    +#	make install	Copy it into the system (implies make)
    +#	make update	Copy installed version into installed version
    +#	make uninstall	Unlink installed version from the system
    +#	make clean	Remove intermediate files
    +#	make cleanall	Remove intremediate files and products
    +#
    +# Macros required to be defined already for make:
    +#
    +# CC		The C compiler
    +# CFLAGS	Flags for $(CC) -- except the -I which are below
    +# LFLAGS	Flags for ld
    +# LYFLAGS	Flags for Lynx
    +#
    +# WWW		The WWW source tree directory
    +#
    +# Macros needed for make install:
    +#
    +# LIBDIR	Directory for installed library
    +#______________________________________________________________________
    +
    +#  If this env var is set to something else Some makes will use that instead
    +SHELL = /bin/sh
    +
    +#	.h files are distributed but originally are made from the
    +#	self-documenting hypertext files.
    +
    +.SUFFIXES: .h .html .htm
    +.html.h:
    +#	- chmod +w $*.h
    +#	www -w90 -na -to text/x-c $*.html > $*.h
    +#	chmod -w $*.h
    +
    +# If this is actually run in a subdirectory,
    +#
    +WWW = ../..
    +# WWW = ../..	For [cernlib] build in this directory
    +
    +WC = $(WWW)/Library
    +CMN = $(WWW)/Library/Implementation/
    +VMS = $(CMN)vms
    +# Where shall we put the objects and built library?
    +
    +LOB = $(WTMP)/Library/$(WWW_MACH)
    +
    +# Only needed if HTWAIS.c is to be compiled. Put into your Makefile.include
    +#  uncomment these and fill in WAISINC for adding direct wais access
    +#  to Lynx.
    +#HTWAIS = $(LOB)/HTWAIS.o
    +#WAIS = YES
    +#WAISINC = -I../../../../freeWAIS-0.202/ir
    +#WAISCFLAGS = -DDIRECT_WAIS
    +#
    +
    +# This path, if relative, is taken relative to the directory
    +# in which this makefile is, not the pwd.  This screws up the
    +# recursive invocation
    +# include $(CMN)Version.make
    +include $(ABS)$(WWW)/Library/Implementation/Version.make
    +
    +# XMOsAIC hack is only for server to cope with xmosaic kludge for mmedia
    +#
    +# add -DNEW_GATEWAY here for the new gateway config stuff
    +CFLAGS2 = $(CFLAGS) $(LYFLAGS) $(WAISCFLAGS) -I$(CMN) -DACCESS_AUTH
    +
    +CERNLIBBIN = $(WWW)/bin
    +
    +COMMON = $(LOB)/HTParse.o $(LOB)/HTAccess.o $(LOB)/HTTP.o \
    +	$(LOB)/HTFile.o	$(LOB)/HTBTree.o $(LOB)/HTFTP.o $(LOB)/HTTCP.o \
    +	$(LOB)/SGML.o $(LOB)/HTMLDTD.o $(LOB)/HTChunk.o \
    +	$(LOB)/HTPlain.o \
    +	$(LOB)/HTMLGen.o \
    +	$(LOB)/HTAtom.o $(LOB)/HTAnchor.o $(LOB)/HTStyle.o \
    +	$(LOB)/HTList.o $(LOB)/HTString.o $(LOB)/HTDOS.o \
    +	$(LOB)/HTRules.o $(LOB)/HTFormat.o  $(LOB)/HTMIME.o \
    +	$(LOB)/HTNews.o $(LOB)/HTGopher.o \
    +	$(LOB)/HTTelnet.o $(LOB)/HTFinger.o $(LOB)/HTWSRC.o $(HTWAIS) \
    +	$(LOB)/HTAAUtil.o $(LOB)/HTAABrow.o \
    +	$(LOB)/HTGroup.o  \
    +	$(LOB)/HTAAProt.o \
    +	$(LOB)/HTAssoc.o  $(LOB)/HTLex.o    $(LOB)/HTUU.o
    +
    +CFILES = $(CMN)HTParse.c $(CMN)HTAccess.c $(CMN)HTTP.c $(CMN)HTFile.c \
    +	$(CMN)HTBTree.c \
    +	$(CMN)HTFTP.c	 $(CMN)HTTCP.c	   $(CMN)SGML.c \
    +	$(CMN)HTMLDTD.c \
    +	$(CMN)HTPlain.c	 \
    +	$(CMN)HTDOS.c	 $(CMN)HTMLGen.c \
    +	$(CMN)HTChunk.c	 $(CMN)HTAtom.c	  $(CMN)HTAnchor.c $(CMN)HTStyle.c \
    +	$(CMN)HTList.c	 $(CMN)HTString.c $(CMN)HTRules.c \
    +	$(CMN)HTFormat.c $(CMN)HTMIME.c \
    +	$(CMN)HTNews.c	 $(CMN)HTGopher.c $(CMN)HTTelnet.c \
    +	$(CMN)HTFinger.c $(CMN)HTWAIS.c	  $(CMN)HTWSRC.c \
    +	$(CMN)HTAAUtil.c $(CMN)HTAABrow.c \
    +	$(CMN)HTGroup.c  \
    +	$(CMN)HTAAProt.c \
    +	$(CMN)HTAssoc.c  $(CMN)HTLex.c    $(CMN)HTUU.c
    +
    +HFILES = $(CMN)HTParse.h $(CMN)HTAccess.h $(CMN)HTTP.h $(CMN)HTFile.h \
    +	$(CMN)HTBTree.h $(CMN)HTFTP.h $(CMN)HTTCP.h \
    +	$(CMN)SGML.h $(CMN)HTML.h $(CMN)HTMLDTD.h $(CMN)HTChunk.h \
    +	$(CMN)HTPlain.h	\
    +	$(CMN)HTFWriter.h	$(CMN)HTMLGen.h $(CMN)HTDOS.h \
    +	$(CMN)HTStream.h \
    +	$(CMN)HTAtom.h $(CMN)HTAnchor.h $(CMN)HTStyle.h \
    +	$(CMN)HTList.h \
    +	$(CMN)HTString.h $(CMN)HTRules.h \
    +	$(CMN)HTFormat.h $(CMN)HTInit.h \
    +	$(CMN)HTMIME.h $(CMN)HTNews.h \
    +	$(CMN)HTGopher.h \
    +	$(CMN)HTUtils.h $(CMN)www_tcp.h $(CMN)HText.h \
    +	$(CMN)HTTelnet.h $(CMN)HTFinger.h \
    +	$(CMN)HTWAIS.h  $(CMN)HTWSRC.h \
    +	$(CMN)HTAAUtil.h $(CMN)HTAABrow.h \
    +	$(CMN)HTGroup.h  \
    +	$(CMN)HTAAProt.h \
    +	$(CMN)HTAssoc.h  $(CMN)HTLex.h    $(CMN)HTUU.h
    +
    +SOURCES = $(CFILES) $(HFILES) $(CMN)Version.make \
    +	$(CMN)CommonMakefile $(CMN)Makefile \
    +	$(WWW)/README.txt $(WWW)/Copyright.txt $(WWW)/BUILD $(WWW)/Makefile
    +SPECIFIC = $(WWW)/All/*/Makefile.include $(WWW)/All/Implementation/Makefile* \
    +	$(VMS)/descrip.mms $(VMS)/build_multinet.com \
    +	$(VMS)/COPYING.LIB $(VMS)/setup.com $(VMS)/multinet.opt \
    +	$(VMS)/HTVMSUtils.h $(VMS)/HTVMSUtils.c
    +
    +
    +#	Library
    +#
    +#  On SGI, ranlib is unnecessary and does not exist so we ignore errors
    +# for that step
    +$(LOB)/libwww.a : $(COMMON)
    +	ar r $(LOB)/libwww.a $(COMMON)
    +	-ranlib $(LOB)/libwww.a
    +
    +#	Clean up everything generatable except final products
    +clean :
    +	rm -f $(LOB)/*.o
    +
    +#	Clean up everything generatable including final products
    +
    +cleanall : clean
    +	rm -f $(LOB)/libwww.a
    +
    +#	Install W3 library into system space (not normally necessary)
    +
    +install : libwww.a
    +	if [ ! -r $(LIBDIR) ] mkdir $(LIBDIR)
    +	cp libwww.a $(LIBDIR)/libwww.a
    +
    +uninstall :
    +	rm $(LIBDIR)/libwww.a
    +
    +#		Distribution use only:
    +#		----------------------
    +
    +#	Needs www version 2.4 or later to do this
    +inc : $(HFILES)
    +	echo Include files generated from hypertext.
    +
    +binary : /pub/www/bin/$(WWW_MACH)/libwww_$(VC).a
    +	echo FTP archive binary Library $(VC) for $(WWW_MACH) up to date.
    +
    +
    +/pub/www/bin/$(WWW_MACH)/libwww_$(VC).a : libwww.a
    +	-mkdir /pub/www/bin/$(WWW_MACH)
    +	cp libwww.a /pub/www/bin/$(WWW_MACH)/libwww_$(VC).a
    +
    +#	Source Distribution:
    +
    +distribute :  /pub/www/README.txt /pub/www/Copyright.txt
    +	(cd $(WWW)/..; WWW=WWW ABS=`pwd`/ make $(MFLAGS) \
    +		-f WWW/Library/Implementation/CommonMakefile \
    +		/pub/www/src/WWWLibrary_$(VC).tar.Z)
    +	(cd ../Implementation; cvs tag \
    +	    `sed -e 's/VC = /v/' Version.make | sed -e 's?\.?/?'` )
    +	echo Distribution of Library version $(VC) up to date.
    +
    +/pub/www/src/WWWLibrary_$(VC).tar.Z : $(SOURCES)
    +	tar cf /pub/www/src/WWWLibrary_$(VC).tar \
    +	    $(SOURCES)  $(SPECIFIC) $(WC)/*/Makefile
    +	compress /pub/www/src/WWWLibrary_$(VC).tar
    +
    +
    +#	Hypertext supplied in text format
    +#	---------------------------------
    +
    +$(WWW)/README.txt : $(WWW)/../README.html
    +	www -n -p66 http://www.w3.org/hypertext/README.html \
    +		> $(WWW)/README.txt
    +/pub/www/README.txt : $(WWW)/README.txt
    +	cp $(WWW)/README.txt /pub/www/README.txt
    +
    +$(WWW)/Copyright.txt : $(WWW)/../Copyright.html
    +	www -n -p66 http://www.w3.org/hypertext/Copyright.html \
    +		> $(WWW)/Copyright.txt
    +/pub/www/Copyright.txt : $(WWW)/Copyright.txt
    +	cp $(WWW)/Copyright.txt /pub/www/Copyright.txt
    +
    +#			Common code
    +#			-----------
    +
    +#	Directory for object files
    +
    +$(LOB)/HTList.o : $(OE) $(CMN)HTList.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTList.c
    +
    +$(LOB)/HTAnchor.o : $(OE) $(CMN)HTAnchor.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAnchor.c
    +
    +$(LOB)/HTFormat.o : $(OE) $(CMN)HTFormat.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTFormat.c
    +
    +$(LOB)/HTMIME.o : $(OE) $(CMN)HTMIME.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTMIME.c
    +
    +$(LOB)/HTDOS.o : $(OE) $(CMN)HTDOS.c $(CMN)HTUtils.h $(CMN)../../../userdefs.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTDOS.c
    +
    +$(LOB)/HTNews.o : $(OE) $(CMN)HTNews.c $(CMN)HTUtils.h $(CMN)HTList.h\
    +	 $(CMN)HTMLDTD.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTNews.c
    +
    +$(LOB)/HTGopher.o : $(OE) $(CMN)HTGopher.c $(CMN)HTUtils.h $(CMN)HTList.h \
    +	 $(CMN)HTMLDTD.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTGopher.c
    +
    +$(LOB)/HTTelnet.o : $(OE) $(CMN)HTTelnet.c $(CMN)HTUtils.h $(CMN)HTTelnet.h $(CMN)../../../userdefs.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTTelnet.c
    +
    +$(LOB)/HTFinger.o : $(OE) $(CMN)HTFinger.c $(CMN)HTUtils.h $(CMN)HTList.h \
    +	$(CMN)HTMLDTD.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTFinger.c
    +
    +$(LOB)/HTStyle.o : $(OE) $(CMN)HTStyle.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTStyle.c
    +
    +$(LOB)/HTAtom.o : $(OE) $(CMN)HTAtom.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAtom.c
    +
    +$(LOB)/HTChunk.o : $(OE) $(CMN)HTChunk.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTChunk.c
    +
    +$(LOB)/HTString.o : $(OE) $(CMN)HTString.c $(CMN)HTUtils.h $(CMN)Version.make
    +	$(CC) -c  $(CFLAGS2) -DVC=\"$(VC)\" $(CMN)HTString.c
    +
    +$(LOB)/HTRules.o : $(OE) $(CMN)HTRules.c $(CMN)HTUtils.h $(CMN)Version.make \
    +	 $(CMN)HTAAProt.h
    +	$(CC) -c  $(CFLAGS2) -DVC=\"$(VC)\" $(CMN)HTRules.c
    +
    +$(LOB)/SGML.o : $(OE) $(CMN)SGML.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)SGML.c
    +
    +$(LOB)/HTMLGen.o : $(OE) $(CMN)HTMLGen.c $(CMN)HTUtils.h $(CMN)HTMLDTD.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTMLGen.c
    +
    +$(LOB)/HTMLDTD.o : $(OE) $(CMN)HTMLDTD.c $(CMN)SGML.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTMLDTD.c
    +
    +$(LOB)/HTPlain.o : $(OE) $(CMN)HTPlain.c $(CMN)HTPlain.h $(CMN)HTStream.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTPlain.c
    +
    +$(LOB)/HTWAIS.o : $(OE) $(CMN)HTWAIS.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(WAISINC) $(CMN)HTWAIS.c
    +
    +$(LOB)/HTWSRC.o : $(OE) $(CMN)HTWSRC.c $(CMN)HTUtils.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTWSRC.c
    +
    +#	Access Authorization
    +
    +$(LOB)/HTAAUtil.o : $(OE) $(CMN)HTAAUtil.c $(CMN)HTAAUtil.h \
    +	 $(CMN)HTUtils.h $(CMN)HTString.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAAUtil.c
    +
    +$(LOB)/HTGroup.o : $(OE) $(CMN)HTGroup.c $(CMN)HTGroup.h \
    +	 $(CMN)HTAAUtil.h \
    +	 $(CMN)HTAssoc.h $(CMN)HTLex.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTGroup.c
    +
    +$(LOB)/HTAABrow.o : $(OE) $(CMN)HTAABrow.c $(CMN)HTAABrow.h \
    +	 $(CMN)HTAAUtil.h $(CMN)HTUU.h \
    +	 $(CMN)HTUtils.h $(CMN)HTString.h \
    +	 $(CMN)HTParse.h $(CMN)HTList.h \
    +	 $(CMN)HTAssoc.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAABrow.c
    +
    +$(LOB)/HTAAProt.o : $(OE) $(CMN)HTAAProt.c $(CMN)HTAAProt.h \
    +	 $(CMN)HTUtils.h $(CMN)HTAAUtil.h \
    +	 $(CMN)HTAssoc.h $(CMN)HTLex.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAAProt.c
    +
    +$(LOB)/HTAssoc.o : $(OE) $(CMN)HTAssoc.c $(CMN)HTAssoc.h \
    +	$(CMN)HTUtils.h $(CMN)HTString.h $(CMN)HTList.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAssoc.c
    +
    +$(LOB)/HTLex.o : $(OE) $(CMN)HTLex.c $(CMN)HTLex.h $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTLex.c
    +
    +$(LOB)/HTUU.o : $(OE) $(CMN)HTUU.c $(CMN)HTUU.h $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTUU.c
    +
    +
    +#	Communications & Files
    +
    +$(LOB)/HTTP.o : $(OE) $(CMN)HTTP.c $(CMN)HTUtils.h $(CMN)HTAABrow.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTTP.c
    +
    +$(LOB)/HTTCP.o : $(OE) $(CMN)HTTCP.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTTCP.c
    +
    +$(LOB)/HTFile.o : $(OE) $(CMN)HTFile.c $(CMN)HTUtils.h \
    +	 $(CMN)HTMLDTD.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTFile.c
    +
    +$(LOB)/HTBTree.o : $(OE) $(CMN)HTBTree.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTBTree.c
    +
    +$(LOB)/HTFTP.o : $(OE) $(CMN)HTFTP.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTFTP.c
    +
    +$(LOB)/HTAccess.o : $(OE)  $(CMN)HTAccess.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTAccess.c
    +
    +$(LOB)/HTParse.o : $(OE) $(CMN)HTParse.c $(CMN)HTUtils.h
    +	$(CC) -c  $(CFLAGS2) $(CMN)HTParse.c
    +
    diff --git a/WWW/Library/djgpp/makefile b/WWW/Library/djgpp/makefile
    new file mode 100644
    index 0000000..f46ad1f
    --- /dev/null
    +++ b/WWW/Library/djgpp/makefile
    @@ -0,0 +1,59 @@
    +#  Make WWW under unix for a.n.other unix system (bsd)
    +#   Use this as a template
    +
    +# For W3 distribution, machine type for subdirectories
    +WWW_MACH = djgpp
    +
    +# The ASIS repository's name for the machine we are on
    +#ASIS_MACH = hardware/os
    +
    +# Use this option to enable optional and *experimental* color style.
    +#ENABLE_COLOR_STYLE = -DUSE_COLOR_STYLE
    +
    +# comment this line to suppress DIRED support
    +DIRED_DEFS = -DDIRED_SUPPORT
    +
    +CFLAGS= -O2 $(MCFLAGS) $(INTLFLAGS) $(SSLFLAGS) $(SSLINC)
    +
    +MCFLAGS = \
    + $(DIRED_DEFS) \
    + $(ENABLE_COLOR_STYLE) \
    + -DDISP_PARTIAL \
    + -DDOSPATH \
    + -DUSE_FILE_UPLOAD \
    + -DNOUSERS \
    + -DUSE_SOURCE_CACHE \
    + -DUSE_PRETTYSRC \
    + -DUSE_ZLIB \
    + -I../Implementation \
    + -I../../../src \
    + -I../../.. \
    + -I/djgpp/pdcur26 \
    + -I/djgpp/watt32/inc
    +
    +LFLAGS =
    +CC = gcc
    +
    +# Uncomment the following to enable Internationalization.
    +#INTLFLAGS = -DHAVE_GETTEXT -DHAVE_LIBINTL_H
    +
    +# Uncomment the following to enable SSL.
    +#SSLFLAGS = -DUSE_SSL
    +#SSLINC = -I/dev/env/DJDIR/include/openssl
    +
    +# Directory for installed binary:
    +!BINDIR = /usr/local/bin
    +
    +# Where is the W3 object library to be installed (not normally done)?
    +LIBDIR = $(WWW)/Library/Implementation/$(WWW_MACH)
    +
    +#_________________ OK if normal W3 distribution
    +# Where is the WWW source root?
    +WWW = ../..
    +
    +#  Where should temporary (object) files go?
    +WTMP = ../..
    +
    +include $(WWW)/Library/Implementation/Version.make
    +#include $(WWW)/Library/Implementation/CommonMakefile
    +include ./CommonMakefile
    diff --git a/WWW/Library/djgpp/makefile.sla b/WWW/Library/djgpp/makefile.sla
    new file mode 100644
    index 0000000..79e87b8
    --- /dev/null
    +++ b/WWW/Library/djgpp/makefile.sla
    @@ -0,0 +1,40 @@
    +#  Make WWW under unix for a.n.other unix system (bsd)
    +#   Use this as a template
    +
    +# For W3 distribution, machine type for subdirectories
    +WWW_MACH = djgpp
    +
    +# The ASIS repository's name for the machine we are on
    +#ASIS_MACH = hardware/os
    +
    +CFLAGS = -O2 -DUSE_SLANG -DUSE_ZLIB -DDOSPATH -DNOUSERS -DDISP_PARTIAL \
    +-DDIRED_SUPPORT -DUSE_SOURCE_CACHE -DUSE_PRETTYSRC \
    +-I../Implementation \
    +-I/dev/env/DJDIR/watt32/inc \
    +-I../../../src \
    +-I../../.. $(SLANGINC) $(INTLFLAGS) $(SSLFLAGS) $(SSLINC)
    +
    +LFLAGS =
    +CC = gcc
    +#INTLFLAGS = -DHAVE_GETTEXT -DHAVE_LIBINTL_H
    +
    +# Uncomment the following to enable SSL.
    +#SSLFLAGS = -DUSE_SSL
    +#SSLINC = -I/dev/env/DJDIR/include/openssl
    +
    +# Directory for installed binary:
    +!BINDIR = /usr/local/bin
    +
    +# Where is the W3 object library to be installed (not normally done)?
    +LIBDIR = $(WWW)/Library/Implementation/$(WWW_MACH)
    +
    +#_________________ OK if normal W3 distribution
    +# Where is the WWW source root?
    +WWW = ../..
    +
    +#  Where should temporary (object) files go?
    +WTMP = ../..
    +
    +include $(WWW)/Library/Implementation/Version.make
    +#include $(WWW)/Library/Implementation/CommonMakefile
    +include ./CommonMakefile
    diff --git a/WWW/Library/vms/COPYING.LIB b/WWW/Library/vms/COPYING.LIB
    new file mode 100644
    index 0000000..5bc8fb2
    --- /dev/null
    +++ b/WWW/Library/vms/COPYING.LIB
    @@ -0,0 +1,481 @@
    +                  GNU LIBRARY GENERAL PUBLIC LICENSE
    +                       Version 2, June 1991
    +
    + Copyright (C) 1991 Free Software Foundation, Inc.
    + 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    +[This is the first released version of the library GPL.  It is
    + numbered 2 because it goes with version 2 of the ordinary GPL.]
    +
    +                            Preamble
    +
    +  The licenses for most software are designed to take away your
    +freedom to share and change it.  By contrast, the GNU General Public
    +Licenses are intended to guarantee your freedom to share and change
    +free software--to make sure the software is free for all its users.
    +
    +  This license, the Library General Public License, applies to some
    +specially designated Free Software Foundation software, and to any
    +other libraries whose authors decide to use it.  You can use it for
    +your libraries, too.
    +
    +  When we speak of free software, we are referring to freedom, not
    +price.  Our General Public Licenses are designed to make sure that you
    +have the freedom to distribute copies of free software (and charge for
    +this service if you wish), that you receive source code or can get it
    +if you want it, that you can change the software or use pieces of it
    +in new free programs; and that you know you can do these things.
    +
    +  To protect your rights, we need to make restrictions that forbid
    +anyone to deny you these rights or to ask you to surrender the rights.
    +These restrictions translate to certain responsibilities for you if
    +you distribute copies of the library, or if you modify it.
    +
    +  For example, if you distribute copies of the library, whether gratis
    +or for a fee, you must give the recipients all the rights that we gave
    +you.  You must make sure that they, too, receive or can get the source
    +code.  If you link a program with the library, you must provide
    +complete object files to the recipients so that they can relink them
    +with the library, after making changes to the library and recompiling
    +it.  And you must show them these terms so they know their rights.
    +
    +  Our method of protecting your rights has two steps: (1) copyright
    +the library, and (2) offer you this license which gives you legal
    +permission to copy, distribute and/or modify the library.
    +
    +  Also, for each distributor's protection, we want to make certain
    +that everyone understands that there is no warranty for this free
    +library.  If the library is modified by someone else and passed on, we
    +want its recipients to know that what they have is not the original
    +version, so that any problems introduced by others will not reflect on
    +the original authors' reputations.
    +
    +  Finally, any free program is threatened constantly by software
    +patents.  We wish to avoid the danger that companies distributing free
    +software will individually obtain patent licenses, thus in effect
    +transforming the program into proprietary software.  To prevent this,
    +we have made it clear that any patent must be licensed for everyone's
    +free use or not licensed at all.
    +
    +  Most GNU software, including some libraries, is covered by the ordinary
    +GNU General Public License, which was designed for utility programs.  This
    +license, the GNU Library General Public License, applies to certain
    +designated libraries.  This license is quite different from the ordinary
    +one; be sure to read it in full, and don't assume that anything in it is
    +the same as in the ordinary license.
    +
    +  The reason we have a separate public license for some libraries is that
    +they blur the distinction we usually make between modifying or adding to a
    +program and simply using it.  Linking a program with a library, without
    +changing the library, is in some sense simply using the library, and is
    +analogous to running a utility program or application program.  However, in
    +a textual and legal sense, the linked executable is a combined work, a
    +derivative of the original library, and the ordinary General Public License
    +treats it as such.
    +
    +  Because of this blurred distinction, using the ordinary General
    +Public License for libraries did not effectively promote software
    +sharing, because most developers did not use the libraries.  We
    +concluded that weaker conditions might promote sharing better.
    +
    +  However, unrestricted linking of non-free programs would deprive the
    +users of those programs of all benefit from the free status of the
    +libraries themselves.  This Library General Public License is intended to
    +permit developers of non-free programs to use free libraries, while
    +preserving your freedom as a user of such programs to change the free
    +libraries that are incorporated in them.  (We have not seen how to achieve
    +this as regards changes in header files, but we have achieved it as regards
    +changes in the actual functions of the Library.)  The hope is that this
    +will lead to faster development of free libraries.
    +
    +  The precise terms and conditions for copying, distribution and
    +modification follow.  Pay close attention to the difference between a
    +"work based on the library" and a "work that uses the library".  The
    +former contains code derived from the library, while the latter only
    +works together with the library.
    +
    +  Note that it is possible for a library to be covered by the ordinary
    +General Public License rather than by this special one.
    +
    +                  GNU LIBRARY GENERAL PUBLIC LICENSE
    +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    +
    +  0. This License Agreement applies to any software library which
    +contains a notice placed by the copyright holder or other authorized
    +party saying it may be distributed under the terms of this Library
    +General Public License (also called "this License").  Each licensee is
    +addressed as "you".
    +
    +  A "library" means a collection of software functions and/or data
    +prepared so as to be conveniently linked with application programs
    +(which use some of those functions and data) to form executables.
    +
    +  The "Library", below, refers to any such software library or work
    +which has been distributed under these terms.  A "work based on the
    +Library" means either the Library or any derivative work under
    +copyright law: that is to say, a work containing the Library or a
    +portion of it, either verbatim or with modifications and/or translated
    +straightforwardly into another language.  (Hereinafter, translation is
    +included without limitation in the term "modification".)
    +
    +  "Source code" for a work means the preferred form of the work for
    +making modifications to it.  For a library, complete source code means
    +all the source code for all modules it contains, plus any associated
    +interface definition files, plus the scripts used to control compilation
    +and installation of the library.
    +
    +  Activities other than copying, distribution and modification are not
    +covered by this License; they are outside its scope.  The act of
    +running a program using the Library is not restricted, and output from
    +such a program is covered only if its contents constitute a work based
    +on the Library (independent of the use of the Library in a tool for
    +writing it).  Whether that is true depends on what the Library does
    +and what the program that uses the Library does.
    +  
    +  1. You may copy and distribute verbatim copies of the Library's
    +complete source code as you receive it, in any medium, provided that
    +you conspicuously and appropriately publish on each copy an
    +appropriate copyright notice and disclaimer of warranty; keep intact
    +all the notices that refer to this License and to the absence of any
    +warranty; and distribute a copy of this License along with the
    +Library.
    +
    +  You may charge a fee for the physical act of transferring a copy,
    +and you may at your option offer warranty protection in exchange for a
    +fee.
    +
    +  2. You may modify your copy or copies of the Library or any portion
    +of it, thus forming a work based on the Library, and copy and
    +distribute such modifications or work under the terms of Section 1
    +above, provided that you also meet all of these conditions:
    +
    +    a) The modified work must itself be a software library.
    +
    +    b) You must cause the files modified to carry prominent notices
    +    stating that you changed the files and the date of any change.
    +
    +    c) You must cause the whole of the work to be licensed at no
    +    charge to all third parties under the terms of this License.
    +
    +    d) If a facility in the modified Library refers to a function or a
    +    table of data to be supplied by an application program that uses
    +    the facility, other than as an argument passed when the facility
    +    is invoked, then you must make a good faith effort to ensure that,
    +    in the event an application does not supply such function or
    +    table, the facility still operates, and performs whatever part of
    +    its purpose remains meaningful.
    +
    +    (For example, a function in a library to compute square roots has
    +    a purpose that is entirely well-defined independent of the
    +    application.  Therefore, Subsection 2d requires that any
    +    application-supplied function or table used by this function must
    +    be optional: if the application does not supply it, the square
    +    root function must still compute square roots.)
    +
    +These requirements apply to the modified work as a whole.  If
    +identifiable sections of that work are not derived from the Library,
    +and can be reasonably considered independent and separate works in
    +themselves, then this License, and its terms, do not apply to those
    +sections when you distribute them as separate works.  But when you
    +distribute the same sections as part of a whole which is a work based
    +on the Library, the distribution of the whole must be on the terms of
    +this License, whose permissions for other licensees extend to the
    +entire whole, and thus to each and every part regardless of who wrote
    +it.
    +
    +Thus, it is not the intent of this section to claim rights or contest
    +your rights to work written entirely by you; rather, the intent is to
    +exercise the right to control the distribution of derivative or
    +collective works based on the Library.
    +
    +In addition, mere aggregation of another work not based on the Library
    +with the Library (or with a work based on the Library) on a volume of
    +a storage or distribution medium does not bring the other work under
    +the scope of this License.
    +
    +  3. You may opt to apply the terms of the ordinary GNU General Public
    +License instead of this License to a given copy of the Library.  To do
    +this, you must alter all the notices that refer to this License, so
    +that they refer to the ordinary GNU General Public License, version 2,
    +instead of to this License.  (If a newer version than version 2 of the
    +ordinary GNU General Public License has appeared, then you can specify
    +that version instead if you wish.)  Do not make any other change in
    +these notices.
    +
    +  Once this change is made in a given copy, it is irreversible for
    +that copy, so the ordinary GNU General Public License applies to all
    +subsequent copies and derivative works made from that copy.
    +
    +  This option is useful when you wish to copy part of the code of
    +the Library into a program that is not a library.
    +
    +  4. You may copy and distribute the Library (or a portion or
    +derivative of it, under Section 2) in object code or executable form
    +under the terms of Sections 1 and 2 above provided that you accompany
    +it with the complete corresponding machine-readable source code, which
    +must be distributed under the terms of Sections 1 and 2 above on a
    +medium customarily used for software interchange.
    +
    +  If distribution of object code is made by offering access to copy
    +from a designated place, then offering equivalent access to copy the
    +source code from the same place satisfies the requirement to
    +distribute the source code, even though third parties are not
    +compelled to copy the source along with the object code.
    +
    +  5. A program that contains no derivative of any portion of the
    +Library, but is designed to work with the Library by being compiled or
    +linked with it, is called a "work that uses the Library".  Such a
    +work, in isolation, is not a derivative work of the Library, and
    +therefore falls outside the scope of this License.
    +
    +  However, linking a "work that uses the Library" with the Library
    +creates an executable that is a derivative of the Library (because it
    +contains portions of the Library), rather than a "work that uses the
    +library".  The executable is therefore covered by this License.
    +Section 6 states terms for distribution of such executables.
    +
    +  When a "work that uses the Library" uses material from a header file
    +that is part of the Library, the object code for the work may be a
    +derivative work of the Library even though the source code is not.
    +Whether this is true is especially significant if the work can be
    +linked without the Library, or if the work is itself a library.  The
    +threshold for this to be true is not precisely defined by law.
    +
    +  If such an object file uses only numerical parameters, data
    +structure layouts and accessors, and small macros and small inline
    +functions (ten lines or less in length), then the use of the object
    +file is unrestricted, regardless of whether it is legally a derivative
    +work.  (Executables containing this object code plus portions of the
    +Library will still fall under Section 6.)
    +
    +  Otherwise, if the work is a derivative of the Library, you may
    +distribute the object code for the work under the terms of Section 6.
    +Any executables containing that work also fall under Section 6,
    +whether or not they are linked directly with the Library itself.
    +
    +  6. As an exception to the Sections above, you may also compile or
    +link a "work that uses the Library" with the Library to produce a
    +work containing portions of the Library, and distribute that work
    +under terms of your choice, provided that the terms permit
    +modification of the work for the customer's own use and reverse
    +engineering for debugging such modifications.
    +
    +  You must give prominent notice with each copy of the work that the
    +Library is used in it and that the Library and its use are covered by
    +this License.  You must supply a copy of this License.  If the work
    +during execution displays copyright notices, you must include the
    +copyright notice for the Library among them, as well as a reference
    +directing the user to the copy of this License.  Also, you must do one
    +of these things:
    +
    +    a) Accompany the work with the complete corresponding
    +    machine-readable source code for the Library including whatever
    +    changes were used in the work (which must be distributed under
    +    Sections 1 and 2 above); and, if the work is an executable linked
    +    with the Library, with the complete machine-readable "work that
    +    uses the Library", as object code and/or source code, so that the
    +    user can modify the Library and then relink to produce a modified
    +    executable containing the modified Library.  (It is understood
    +    that the user who changes the contents of definitions files in the
    +    Library will not necessarily be able to recompile the application
    +    to use the modified definitions.)
    +
    +    b) Accompany the work with a written offer, valid for at
    +    least three years, to give the same user the materials
    +    specified in Subsection 6a, above, for a charge no more
    +    than the cost of performing this distribution.
    +
    +    c) If distribution of the work is made by offering access to copy
    +    from a designated place, offer equivalent access to copy the above
    +    specified materials from the same place.
    +
    +    d) Verify that the user has already received a copy of these
    +    materials or that you have already sent this user a copy.
    +
    +  For an executable, the required form of the "work that uses the
    +Library" must include any data and utility programs needed for
    +reproducing the executable from it.  However, as a special exception,
    +the source code distributed need not include anything that is normally
    +distributed (in either source or binary form) with the major
    +components (compiler, kernel, and so on) of the operating system on
    +which the executable runs, unless that component itself accompanies
    +the executable.
    +
    +  It may happen that this requirement contradicts the license
    +restrictions of other proprietary libraries that do not normally
    +accompany the operating system.  Such a contradiction means you cannot
    +use both them and the Library together in an executable that you
    +distribute.
    +
    +  7. You may place library facilities that are a work based on the
    +Library side-by-side in a single library together with other library
    +facilities not covered by this License, and distribute such a combined
    +library, provided that the separate distribution of the work based on
    +the Library and of the other library facilities is otherwise
    +permitted, and provided that you do these two things:
    +
    +    a) Accompany the combined library with a copy of the same work
    +    based on the Library, uncombined with any other library
    +    facilities.  This must be distributed under the terms of the
    +    Sections above.
    +
    +    b) Give prominent notice with the combined library of the fact
    +    that part of it is a work based on the Library, and explaining
    +    where to find the accompanying uncombined form of the same work.
    +
    +  8. You may not copy, modify, sublicense, link with, or distribute
    +the Library except as expressly provided under this License.  Any
    +attempt otherwise to copy, modify, sublicense, link with, or
    +distribute the Library is void, and will automatically terminate your
    +rights under this License.  However, parties who have received copies,
    +or rights, from you under this License will not have their licenses
    +terminated so long as such parties remain in full compliance.
    +
    +  9. You are not required to accept this License, since you have not
    +signed it.  However, nothing else grants you permission to modify or
    +distribute the Library or its derivative works.  These actions are
    +prohibited by law if you do not accept this License.  Therefore, by
    +modifying or distributing the Library (or any work based on the
    +Library), you indicate your acceptance of this License to do so, and
    +all its terms and conditions for copying, distributing or modifying
    +the Library or works based on it.
    +
    +  10. Each time you redistribute the Library (or any work based on the
    +Library), the recipient automatically receives a license from the
    +original licensor to copy, distribute, link with or modify the Library
    +subject to these terms and conditions.  You may not impose any further
    +restrictions on the recipients' exercise of the rights granted herein.
    +You are not responsible for enforcing compliance by third parties to
    +this License.
    +
    +  11. If, as a consequence of a court judgment or allegation of patent
    +infringement or for any other reason (not limited to patent issues),
    +conditions are imposed on you (whether by court order, agreement or
    +otherwise) that contradict the conditions of this License, they do not
    +excuse you from the conditions of this License.  If you cannot
    +distribute so as to satisfy simultaneously your obligations under this
    +License and any other pertinent obligations, then as a consequence you
    +may not distribute the Library at all.  For example, if a patent
    +license would not permit royalty-free redistribution of the Library by
    +all those who receive copies directly or indirectly through you, then
    +the only way you could satisfy both it and this License would be to
    +refrain entirely from distribution of the Library.
    +
    +If any portion of this section is held invalid or unenforceable under any
    +particular circumstance, the balance of the section is intended to apply,
    +and the section as a whole is intended to apply in other circumstances.
    +
    +It is not the purpose of this section to induce you to infringe any
    +patents or other property right claims or to contest validity of any
    +such claims; this section has the sole purpose of protecting the
    +integrity of the free software distribution system which is
    +implemented by public license practices.  Many people have made
    +generous contributions to the wide range of software distributed
    +through that system in reliance on consistent application of that
    +system; it is up to the author/donor to decide if he or she is willing
    +to distribute software through any other system and a licensee cannot
    +impose that choice.
    +
    +This section is intended to make thoroughly clear what is believed to
    +be a consequence of the rest of this License.
    +
    +  12. If the distribution and/or use of the Library is restricted in
    +certain countries either by patents or by copyrighted interfaces, the
    +original copyright holder who places the Library under this License may add
    +an explicit geographical distribution limitation excluding those countries,
    +so that distribution is permitted only in or among countries not thus
    +excluded.  In such case, this License incorporates the limitation as if
    +written in the body of this License.
    +
    +  13. The Free Software Foundation may publish revised and/or new
    +versions of the Library General Public License from time to time.
    +Such new versions will be similar in spirit to the present version,
    +but may differ in detail to address new problems or concerns.
    +
    +Each version is given a distinguishing version number.  If the Library
    +specifies a version number of this License which applies to it and
    +"any later version", you have the option of following the terms and
    +conditions either of that version or of any later version published by
    +the Free Software Foundation.  If the Library does not specify a
    +license version number, you may choose any version ever published by
    +the Free Software Foundation.
    +
    +  14. If you wish to incorporate parts of the Library into other free
    +programs whose distribution conditions are incompatible with these,
    +write to the author to ask for permission.  For software which is
    +copyrighted by the Free Software Foundation, write to the Free
    +Software Foundation; we sometimes make exceptions for this.  Our
    +decision will be guided by the two goals of preserving the free status
    +of all derivatives of our free software and of promoting the sharing
    +and reuse of software generally.
    +
    +                            NO WARRANTY
    +
    +  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
    +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
    +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
    +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
    +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    +PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
    +LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
    +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    +
    +  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
    +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
    +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
    +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
    +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
    +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
    +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
    +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
    +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
    +DAMAGES.
    +
    +                     END OF TERMS AND CONDITIONS
    +
    +           How to Apply These Terms to Your New Libraries
    +
    +  If you develop a new library, and you want it to be of the greatest
    +possible use to the public, we recommend making it free software that
    +everyone can redistribute and change.  You can do so by permitting
    +redistribution under these terms (or, alternatively, under the terms of the
    +ordinary General Public License).
    +
    +  To apply these terms, attach the following notices to the library.  It is
    +safest to attach them to the start of each source file to most effectively
    +convey the exclusion of warranty; and each file should have at least the
    +"copyright" line and a pointer to where the full notice is found.
    +
    +    
    +    Copyright (C)   
    +
    +    This library is free software; you can redistribute it and/or
    +    modify it under the terms of the GNU Library General Public
    +    License as published by the Free Software Foundation; either
    +    version 2 of the License, or (at your option) any later version.
    +
    +    This library is distributed in the hope that it will be useful,
    +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    +    Library General Public License for more details.
    +
    +    You should have received a copy of the GNU Library General Public
    +    License along with this library; if not, write to the Free Software
    +    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
    +
    +Also add information on how to contact you by electronic and paper mail.
    +
    +You should also get your employer (if you work as a programmer) or your
    +school, if any, to sign a "copyright disclaimer" for the library, if
    +necessary.  Here is a sample; alter the names:
    +
    +  Yoyodyne, Inc., hereby disclaims all copyright interest in the
    +  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
    +
    +  , 1 April 1990
    +  Ty Coon, President of Vice
    +
    +That's all there is to it!
    diff --git a/WWW/Library/vms/descrip.mms b/WWW/Library/vms/descrip.mms
    new file mode 100644
    index 0000000..fe275bb
    --- /dev/null
    +++ b/WWW/Library/vms/descrip.mms
    @@ -0,0 +1,206 @@
    +!	Make WorldWideWeb LIBRARY under VMS
    +!       =======================================================
    +!
    +! History:
    +!  14 Aug 91 (TBL)	Reconstituted
    +!  25 Jun 92 (JFG)	Added TCP socket emulation over DECnet
    +!  07 Sep 93 (MD)	Remade for version 2.09a
    +!  10 Dec 93 (FM)	Upgrade for version 2.14 with Lynx v2.1
    +!  13 Dec 93 (FM)	Added conditional compilations for VAXC vs. DECC
    +!			(MMS can't handle a MODULE list as large as the
    +!			 WWWLibrary has become, so this just illustrates
    +!			 how you'd set it up if it could 8-).
    +!  26 Oct 94 (RLD)	Updated to work with VAX/VMS v5.5-1 and AXP/VMS v6.1
    +!  31 Oct 94 (RLD)      Updated for Lynx v2.3.4, supporting OpenCMU and
    +!                       TCPWare
    +!  18 Nov 94 (FM)	Updated for SOCKETSHR/NETLIB
    +!  07 Dec 94 (FM)	Updated for DECC/VAX, VAXC/VAX and DECC/AXP
    +!  03 May 95 (FM)	Include /NoMember for DECC (not the default on AXP,
    +!			and the code assumes byte alignment).
    +!  07 Jul 95 (FM)	Added GNUC support.
    +!  15 Sep 06 (TD)	Cleanup...
    +!
    +! Bugs:
    +!	The dependencies are anything but complete - they were
    +!	just enough to allow the files to be compiled.
    +!
    +! Instructions:
    +! 	Copy [WWW.LIBRARY.VMS]DESCRIP.MMS into [WWW.LIBRARY.IMPLEMENTATION]
    +!	Use the correct command line for your TCP/IP implementation,
    +!	inside the IMPLEMENTATION directory:
    +!
    +!	$ MMS/MACRO=(MULTINET=1)		for VAXC - MultiNet
    +!	$ MMS/MACRO=(WIN_TCP=1)			for VAXC - Wollongong TCP/IP
    +!	$ MMS/MACRO=(UCX=1)			for VAXC - UCX
    +!	$ MMS/MACRO=(CMU_TCP=1)			for VAXC - OpenCMU TCP/IP
    +!	$ MMS/MACRO=(SOCKETSHR_TCP=1)		for VAXC - SOCKETSHR/NETLIB
    +!	$ MMS/MACRO=(TCPWARE=1)			for VAXC - TCPWare TCP/IP
    +!	$ MMS/MACRO=(DECNET=1)		for VAXC - socket emulation over DECnet
    +!
    +!	$ MMS/MACRO=(MULTINET=1,DEC_C=1)	for DECC - MultiNet
    +!	$ MMS/MACRO=(WIN_TCP=1,DEC_C=1)		for DECC - Wollongong TCP/IP
    +!	$ MMS/MACRO=(UCX=1,DEC_C=1)		for DECC - UCX
    +!	$ MMS/MACRO=(CMU_TCP=1,DEC_C=1)		for DECC - OpenCMU TCP/IP
    +!	$ MMS/MACRO=(SOCKETSHR_TCP=1,DEC_C=1)	for DECC - SOCKETSHR/NETLIB
    +!	$ MMS/MACRO=(TCPWARE=1,DEC_C=1)		for DECC - TCPWare TCP/IP
    +!	$ MMS/MACRO=(DECNET=1,DEC_C=1)	for DECC - socket emulation over DECnet
    +!
    +!	$ MMS/MACRO=(MULTINET=1,GNU_C=1)	for GNUC - MultiNet
    +!	$ MMS/MACRO=(WIN_TCP=1,GNU_C=1)		for GNUC - Wollongong TCP/IP
    +!	$ MMS/MACRO=(UCX=1,GNU_C=1)		for GNUC - UCX
    +!	$ MMS/MACRO=(CMU_TCP=1,GNU_C=1)		for GNUC - OpenCMU TCP/IP
    +!	$ MMS/MACRO=(SOCKETSHR_TCP=1,GNU_C=1)	for GNUC - SOCKETSHR/NETLIB
    +!	$ MMS/MACRO=(TCPWARE=1,GNU_C=1)		for GNUC - TCPWare TCP/IP
    +!	$ MMS/MACRO=(DECNET=1,GNU_C=1)	for GNUC - socket emulation over DECnet
    +!
    +! To compile with debug mode:
    +!
    +!	$ MMS/MACRO=(MULTINET=1, DEBUG=1)	for Multinet
    +!
    +!
    +! If you are on HEP net and want to build using the really latest sources on
    +! PRIAM:: then define an extra macro U=PRIAM::, e.g.
    +!
    +!	$ MMS/MACRO=(MULTINET=1, U=PRIAM::)	for Multinet
    +!
    +! This will copy the sources from PRIAM as necessary. You can also try
    +!
    +!	$ MMS/MACRO=(U=PRIAM::) descrip.mms
    +!
    +! to update this file.
    +
    +
    +.include Version.make
    +
    +! debug flags
    +.ifdef DEBUG
    +DEBUGFLAGS = /Debug /NoOptimize
    +.endif
    +
    +INCLUDES = /Include=([-.Implementation],[---.src],[---])
    +
    +! defines valid for all compilations
    +EXTRADEFINES = ACCESS_AUTH, VC="""$(VC)"""
    +
    +.ifdef WIN_TCP
    +NETWORK_DEF = WIN_TCP
    +.else
    +.ifdef CMU_TCP
    +NETWORK_DEF = CMU_TCP
    +.else
    +.ifdef SOCKETSHR_TCP
    +NETWORK_DEF = SOCKETSHR_TCP
    +.else
    +.ifdef UCX
    +NETWORK_DEF = UCX
    +.else
    +.ifdef TCPWARE
    +NETWORK_DEF = TCPWARE,UCX
    +.else
    +.ifdef DECnet
    +NETWORK_DEF = DECNET
    +.else !  Default to MultiNet
    +NETWORK_DEF = MULTINET
    +.endif !  DECnet
    +.endif !  TCPWARE
    +.endif !  UCX
    +.endif !  SOCKETSHR_TCP
    +.endif !  CMU_TCP
    +.endif !  WIN_TCP
    +
    +! DECC flags for all compilations
    +.ifdef DEC_C
    +MY_CFLAGS = /decc/Prefix=All /NoMember /Warning=(disable=implicitfunc) $(INCLUDES)
    +.else
    +MY_CFLAGS = $(INCLUDES)
    +.endif
    +
    +CFLAGS = $(DEBUGFLAGS) $(MY_CFLAGS) /Define=($(EXTRADEFINES), $(NETWORK_DEF))
    +
    +.ifdef GNU_C
    +CC = gcc
    +.endif
    +
    +!HEADERS = HTUtils.h, HTStream.h, www_tcp.h, HText.h -
    +!        HTParse.h, HTAccess.h, HTTP.h, HTFile.h, -
    +!	HTBTree.h, HTTCP.h, SGML.h, -
    +!	HTML.h, HTMLDTD.h, HTChunk.h, HTPlain.h, -
    +!	HTFwriter.h, HTMLGen.h, -
    +!	HTAtom.h, HTAnchor.h, HTStyle.h, -
    +!	HTList.h, HTString.h, HTAlert.h, -
    +!	HTRules.h, HTFormat.h, HTInit.h, -
    +!	HTMIME.h, HTTelnet.h, -
    +!	HTFinger.h, HTAABrow.h, -
    +!	HTAAProt.h, HTAAUtil.h, -
    +!	HTAssoc.h, HTUU.h, -
    +!	HTVMSUtils.h,
    +
    +MODULES = HTParse, HTAccess, HTTP, HTFile, HTBTree, HTFTP, HTTCP, HTString, -
    +	SGML, HTMLDTD, HTChunk, HTPlain, HTMLGen, -
    +	HTAtom, HTAnchor, HTStyle, HTList, HTRules, HTFormat, -
    +	HTMIME, HTNews, HTGopher, HTTelnet, HTFinger, -
    +	HTWSRC, HTAAUtil, HTAABrow, HTGroup, -
    +	HTAAProt, HTAssoc, HTLex, HTUU, HTVMSUtils, -
    +	HTWAIS, HTVMS_WaisUI, HTVMS_WaisProt
    +
    +!.ifdef DECNET  ! Strip FTP, Gopher, News, WAIS
    +!HEADERS = $(COMMON_HEADERS)
    +!MODULES = $(COMMON_MODULES)
    +!.else
    +!HEADERS = $(COMMON_HEADERS), $(EXTRA_HEADERS), $(WAIS_HEADER)
    +!MODULES = $(COMMON_MODULES), $(EXTRA_MODULES), $(WAIS_MODULE)
    +!.endif
    +
    +!___________________________________________________________________
    +! WWW Library
    +
    +!library : $(HEADERS)  wwwlib($(MODULES))
    +library : wwwlib($(MODULES))
    + 	@ Continue
    +
    +build_www.com : descrip.mms
    +	$(MMS) /NoAction /From_Sources /Output = build_www.com /Macro = ($(NETWORK_DEF)=1)
    +
    +clean :
    +	- Set Protection = (Owner:RWED) *.*;-1
    +	- Purge /NoLog /NoConfirm
    +	- Delete /NoLog /NoConfirm *.obj;,*.olb;
    +
    +!___________________________________________________________________
    +! Simple Dependencies
    +
    +
    +!HTString.obj :	HTString.c HTString.h www_tcp.h Version.make HTUtils.h
    +!HTAtom.obj :	HTAtom.c HTAtom.h HTUtils.h HTString.h
    +!HTChunk.obj :	HTChunk.c HTChunk.h HTUtils.h
    +!HTList.obj :	HTList.c HTList.h HTUtils.h
    +!HTBTree.obj :	HTBTree.c HTBTree.h HTUtils.h
    +!HTMLDTD.obj :	HTMLDTD.c HTMLDTD.h SGML.h
    +!HTPlain.obj :	HTPlain.c HTPlain.h HTStream.h
    +!HTMLGen.obj :	HTMLGen.c HTMLGen.h HTUtils.h HTMLDTD.h
    +!HTRules.obj :	HTRules.c HTRules.h HTUtils.h Version.make
    +!HTMIME.obj :	HTMIME.c HTMIME.h HTUtils.h HTList.h
    +!HTTelnet.obj :	HTTelnet.c HTTelnet.h HTUtils.h
    +!HTWAIS.obj :	HTWAIS.c HTWAIS.h HTUtils.h HTList.h
    +!HTWSRC.obj :	HTWSRC.c HTWSRC.h HTUtils.h HTList.h
    +!HTAccess.obj :	HTAccess.c HTAccess.h HTUtils.h
    +!HTAnchor.obj :	HTAnchor.c HTAnchor.h HTUtils.h HTList.h
    +!HTFile.obj :	HTFile.c HTFile.h HTUtils.h HTVMSUtils.h
    +!HTFormat.obj :	HTFormat.c HTFormat.h HTUtils.h HTML.h SGML.h HTPlain.h HTMLGen.h HTList.h
    +!HTFTP.obj :	HTFTP.c HTFTP.h HTUtils.h
    +!HTGopher.obj :	HTGopher.c HTGopher.h HTUtils.h HTList.h
    +!HTFinger.obj :	HTFinger.c HTFinger.h HTUtils.h HTList.h
    +!HTNews.obj :	HTNews.c HTNews.h HTUtils.h HTList.h
    +!HTParse.obj :	HTParse.c HTParse.h HTUtils.h
    +!HTStyle.obj :	HTStyle.c HTStyle.h HTUtils.h
    +!HTTCP.obj :	HTTCP.c HTTCP.h HTUtils.h www_tcp.h
    +!HTTP.obj :	HTTP.c HTTP.h HTUtils.h
    +!SGML.obj :	SGML.c SGML.h HTUtils.h
    +!HTAABrow.obj :	HTAABrow.c HTUtils.h
    +!HTAAProt.obj :	HTAAProt.c HTUtils.h
    +!HTAAUtil.obj :	HTAAUtil.c HTUtils.h
    +!HTGroup.obj :	HTGroup.c HTUtils.h
    +!HTLex.obj :	HTLex.c HTUtils.h
    +!HTAssoc.obj :	HTAssoc.c HTAssoc.h HTAAUtil.h HTString.h
    +!HTUU.obj :	HTUU.c HTUU.h HTUtils.h
    +!HTVMSUtils.obj :	HTVMSUtils.c HTVMSUtils.h HTUtils.h
    diff --git a/WWW/Library/vms/libmake.com b/WWW/Library/vms/libmake.com
    new file mode 100644
    index 0000000..6ef7c07
    --- /dev/null
    +++ b/WWW/Library/vms/libmake.com
    @@ -0,0 +1,233 @@
    +$ v0 = 0
    +$ v = f$verify(v0)
    +$! $LynxId: libmake.com,v 1.15 2011/05/23 23:58:48 tom Exp $
    +$!			LIBMAKE.COM
    +$!
    +$!   Command file to build the WWWLibrary on VMS systems.
    +$!
    +$!   11-Jul-2010	Ch. Gartmann
    +$!	add support for "MULTINETUCX"
    +$!   01-Jul-2007	T.Dickey
    +$!	add support for "TCPIP" (TCPIP Services)
    +$!   23-Oct-2004	T.Dickey
    +$!	cleanup, remove duplication, etc.
    +$!   08-Oct-1997	F.Macrides		macrides@sci.wfeb.edu
    +$!	Added comments and minor tweaks for convenient addition of
    +$!	compiler definitions and compiler and linker options.
    +$!   26-Jul-1995	F.Macrides		macrides@sci.wfeb.edu
    +$!	Adding support for GNUC.
    +$!   03-May-1995	F.Macrides		macrides@sci.wfeb.edu
    +$!	Include /nomember for compilations with DECC.  It's not the
    +$!	default on AXP and the code assumes byte alignment.
    +$!   07-Dec-1994	F.Macrides		macrides@sci.wfeb.edu
    +$!	Updated for DECC/VAX, VAXC/VAX and DECC/AXP
    +$!   03-NOV-1994	A.Harper		A.Harper@kcl.ac.uk
    +$!	Mods to support SOCKETSHR/NETLIB and add a /DEBUG/NOOPT option
    +$!   02-Jun-1994	F.Macrides		macrides@sci.wfeb.edu
    +$!	Mods to support TCPWare (To use non-blocking connects, you need
    +$!	the DRIVERS_V405B.INC patch from FTP.PROCESS.COM for TCPware for
    +$!	OpenVMS Version 4.0-5, or a higher version of TCPWare, which will
    +$!	have that bug in the TCPDRIVER fixed.  Otherwise, add NO_IOCTL to
    +$!	the /define=(...) list.)
    +$!   20-May-1994	Andy Harper		A.Harper@bay.cc.kcl.ac.uk
    +$!	Added support for the CMU TCP/IP transport
    +$!   13-Dec-1993	F.Macrides		macrides@sci.wfeb.edu
    +$!	Mods for conditional compilations with VAXC versus DECC
    +$!   10-Dec-1993	F.Macrides		macrides@sci.wfeb.edu
    +$!	Initial version, for WWWLibrary v2.14 with Lynx v2.1
    +$!
    +$ ON CONTROL_Y THEN GOTO CLEANUP
    +$ ON ERROR THEN GOTO CLEANUP
    +$!
    +$!	Compiler definitions can be added here as a comma separated
    +$!	list with a lead comma, e.g., ",HAVE_FOO_H,DO_BLAH".  They
    +$!	will apply only to the libwww-FM modules. - FM
    +$!
    +$ extra_defs = ",ACCESS_AUTH"
    +$!
    +$!	Include-paths can be added here as a comma separated
    +$!	list with a lead comma, e.g., ",foo".
    +$!
    +$ extra_incs = ""
    +$!
    +$ extra_libs = ""
    +$!
    +$!	If no TCP/IP agent is specified (as the first argument),
    +$!	prompt for a number from the list.   Note that the agent
    +$!	must be the first argument if the debugger mode is to be
    +$!	set via a second argument (see below). - FM
    +$!
    +$ agent = 0
    +$ IF P1 .EQS. ""
    +$ THEN
    +$ 	write sys$output "Acceptable TCP/IP agents are"
    +$ 	write sys$output " [1] MultiNet (default)"
    +$ 	write sys$output " [2] UCX"
    +$ 	write sys$output " [3] WIN_TCP"
    +$	write sys$output " [4] CMU_TCP"
    +$	write sys$output " [5] SOCKETSHR_TCP"
    +$	write sys$output " [6] TCPWARE"
    +$ 	write sys$output " [7] DECNET"
    +$ 	write sys$output " [8] TCPIP"
    +$ 	write sys$output " [9] Multinet UCX emulation"
    +$ 	read sys$command/prompt="Agent [1,2,3,4,5,6,7,8,9] (RETURN = [1]) " agent
    +$ ENDIF
    +$ if agent .eq. 1 .or. agent .eqs. "" .or. p1 .eqs. "MULTINET" then -
    +    transport = "MULTINET"
    +$ if agent .eq. 2 .or. p1 .eqs. "UCX"           then transport = "UCX"
    +$ if agent .eq. 3 .or. p1 .eqs. "WIN_TCP"       then transport = "WIN_TCP"
    +$ if agent .eq. 4 .or. p1 .eqs. "CMU_TCP"       then transport = "CMU_TCP"
    +$ if agent .eq. 5 .or. p1 .eqs. "SOCKETSHR_TCP" then transport = "SOCKETSHR_TCP"
    +$ if agent .eq. 6 .or. p1 .eqs. "TCPWARE"       then transport = "TCPWARE"
    +$ if agent .eq. 7 .or. p1 .eqs. "DECNET"        then transport = "DECNET"
    +$ if agent .eq. 8 .or. p1 .eqs. "TCPIP"         then transport = "TCPIP"
    +$ IF agent .EQ. 9 .OR. P1 .EQS. "MULTINETUCX"
    +$    THEN
    +$    transport = "UCX"
    +$    extra_defs = extra_defs + ",MUCX"
    +$ ENDIF
    +$!
    +$ if transport .eqs. "SOCKETSHR_TCP" then extra_defs = extra_defs + ",_DECC_V4_SOURCE"
    +$ if transport .eqs. "TCPIP"         then extra_defs = extra_defs + ",TCPIP_SERVICES"
    +$ if transport .eqs. "TCPWARE"       then extra_defs = extra_defs + ",UCX"
    +$!
    +$  if option .eqs. "TCPIP"
    +$  then
    +$     if f$trnlnm("TCPIP$IPC_SHR") .eqs. "" then define TCPIP$IPC_SHR SYS$LIBRARY:TCPIP$IPC_SHR
    +$  endif
    +$!
    +$ if P2 .nes. ""
    +$ then
    +$   count_parm = 0
    +$ parse_p2:
    +$   value_parm = f$element('count_parm, ",", "''p2'")
    +$   if value_parm .nes. ","
    +$   then
    +$      if value_parm .eqs. "BZLIB"
    +$      then
    +$         write sys$output "** adding BZlib to build."
    +$         extra_defs = extra_defs + ",USE_BZLIB"
    +$         extra_incs = extra_incs + "," + BZLIB_INC
    +$         extra_libs = extra_libs + "," + BZLIB_LIB + "libbz2/LIB"
    +$      endif
    +$      if value_parm .eqs. "SLANG"
    +$      then
    +$         write sys$output "** adding SLang to build."
    +$         extra_defs = extra_defs + ",USE_SLANG"
    +$         extra_incs = extra_incs + "," + SLANG_INC
    +$         extra_libs = extra_libs + "," + SLANG_LIB + "slang.olb/lib"
    +$      endif
    +$      if value_parm .eqs. "SSL"
    +$      then
    +$         write sys$output "** adding SSL to build."
    +$         IF F$TYPE( ssl_lib ) .EQS. "" THEN ssl_lib = F$TRNLNM("SSLLIB")
    +$         IF F$TYPE( ssl_inc ) .EQS. "" THEN ssl_inc = F$TRNLNM("SSLINCLUDE")
    +$         extra_defs = extra_defs + ",USE_SSL,USE_OPENSSL_INCL"
    +$         extra_libs = extra_libs + "," + SSL_LIB + "libssl/LIB," + SSL_LIB + "libcrypto/LIB"
    +$!
    +$!	The "#include " requires a logical variable "openssl".
    +$!
    +$         define/nolog openssl 'SSL_INC
    +$      endif
    +$      if value_parm .eqs. "ZLIB"
    +$      then
    +$         write sys$output "** adding Zlib to build."
    +$         extra_defs = extra_defs + ",USE_ZLIB"
    +$         extra_incs = extra_incs + "," + ZLIB_INC
    +$         extra_libs = extra_libs + "," + ZLIB_LIB + "libz/LIB"
    +$      endif
    +$      count_parm = count_parm + 1
    +$      goto parse_p2
    +$   endif
    +$ endif
    +$!
    +$!	Compiler options can be specified here.  If there was
    +$!	a second argument (with any value), then debugger mode
    +$!	with no optimization will be specified as well. - FM
    +$!
    +$ cc_opts = ""
    +$ if p3 .nes. "" then cc_opts = cc_opts + "/DEBUG/NOOPT"
    +$!
    +$ all_defs = transport + extra_defs
    +$ all_incs = "[-.Implementation],[---.src],[---.src.chrtrans],[---]" + extra_incs
    +$!
    +$ IF f$getsyi("ARCH_NAME") .eqs. "Alpha" .or. -
    +     f$getsyi("ARCH_NAME") .eqs. "IA64" .or. -
    +     f$trnlnm("VAXCMSG") .eqs. "DECC$MSG" .or. -
    +     f$trnlnm("DECC$CC_DEFAULT") .eqs. "/DECC" .or. -
    +     f$trnlnm("DECC$CC_DEFAULT") .eqs. "/VAXC"
    +$ THEN
    +$! DECC:
    +$  If transport .eqs. "UCX" .or. transport .eqs. "TCPWARE"
    +$  Then
    +$    cc := cc/decc/prefix=all /nomember 'cc_opts'-
    +	  /warning=(disable=implicitfunc)-
    +	  /DEFINE=('all_defs) -
    +	  /INCLUDE=('all_incs)
    +$  Else
    +$    if transport .eqs. "MULTINET" then -
    +	extra_defs = extra_defs + ",_DECC_V4_SOURCE,__SOCKET_TYPEDEFS"
    +$    cc := cc/decc/prefix=all /nomember 'cc_opts'-
    +	  /warning=(disable=implicitfunc)-
    +	  /DEFINE=('all_defs) -
    +	  /INCLUDE=('all_incs)
    +$  EndIf
    +$ ELSE
    +$  IF f$search("gnu_cc:[000000]gcclib.olb") .nes. ""
    +$  THEN
    +$! GNUC:
    +$    cc := gcc/DEFINE=('all_defs) 'cc_opts' /INCLUDE=('all_incs)
    +$  ELSE
    +$! VAXC:
    +$    cc := cc/DEFINE=('all_defs) 'cc_opts' /INCLUDE=('all_incs)
    +$  ENDIF
    +$ ENDIF
    +$ v1 = f$verify(1)
    +$ show sym cc
    +$ cc [-.Implementation]HTString.c
    +$ cc [-.Implementation]HTParse.c
    +$ cc [-.Implementation]HTAccess.c
    +$ cc [-.Implementation]HTTP.c
    +$ cc [-.Implementation]HTFile.c
    +$ cc [-.Implementation]HTBTree.c
    +$ cc [-.Implementation]HTFTP.c
    +$ cc [-.Implementation]HTTCP.c
    +$ cc [-.Implementation]SGML.c
    +$ cc [-.Implementation]HTMLDTD.c
    +$ cc [-.Implementation]HTChunk.c
    +$ cc [-.Implementation]HTPlain.c
    +$ cc [-.Implementation]HTMLGen.c
    +$ cc [-.Implementation]HTAtom.c
    +$ cc [-.Implementation]HTAnchor.c
    +$ cc [-.Implementation]HTStyle.c
    +$ cc [-.Implementation]HTList.c
    +$ cc [-.Implementation]HTRules.c
    +$ cc [-.Implementation]HTFormat.c
    +$ cc [-.Implementation]HTMIME.c
    +$ cc [-.Implementation]HTNews.c
    +$ cc [-.Implementation]HTGopher.c
    +$ cc [-.Implementation]HTTelnet.c
    +$ cc [-.Implementation]HTFinger.c
    +$ cc [-.Implementation]HTWSRC.c
    +$ cc [-.Implementation]HTAAUtil.c
    +$ cc [-.Implementation]HTAABrow.c
    +$ cc [-.Implementation]HTGroup.c
    +$ cc [-.Implementation]HTAAProt.c
    +$ cc [-.Implementation]HTAssoc.c
    +$ cc [-.Implementation]HTLex.c
    +$ cc [-.Implementation]HTUU.c
    +$ cc [-.Implementation]HTVMSUtils.c
    +$ cc [-.Implementation]HTWAIS.c
    +$ cc [-.Implementation]HTVMS_WaisUI.c
    +$ cc [-.Implementation]HTVMS_WaisProt.c
    +$!
    +$ result = "[-.Implementation]WWWLib_''transport'.olb"
    +$ If f$search("''result'") .eqs. "" Then -
    +    LIBRARY/Create 'result
    +$ LIBRARY/Replace 'result *.obj
    +$ Delete/nolog/noconf *.obj;*
    +$!
    +$ v1 = f$verify(v0)
    +$ CLEANUP:
    +$    v1 = f$verify(v)
    +$exit
    -- 
    cgit v1.2.3