langhtml.c

Go to the documentation of this file.
00001 
00002 /*  $Id: langhtml.c 1259 2006-04-13 04:56:46Z sethdill $    */
00003 
00004 /******************************************************************************
00005 
00006     UserLand Frontier(tm) -- High performance Web content management,
00007     object database, system-level and Internet scripting environment,
00008     including source code editing and debugging.
00009 
00010     Copyright (C) 1992-2004 UserLand Software, Inc.
00011 
00012     This program is free software; you can redistribute it and/or modify
00013     it under the terms of the GNU General Public License as published by
00014     the Free Software Foundation; either version 2 of the License, or
00015     (at your option) any later version.
00016 
00017     This program is distributed in the hope that it will be useful,
00018     but WITHOUT ANY WARRANTY; without even the implied warranty of
00019     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020     GNU General Public License for more details.
00021 
00022     You should have received a copy of the GNU General Public License
00023     along with this program; if not, write to the Free Software
00024     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00025 
00026 ******************************************************************************/
00027 
00028 /*dmb 6/17/96 4.0.2b1: integrated macroprocessor ucmd code*/
00029 
00030 #include "frontier.h"
00031 #include "standard.h"
00032 
00033 #ifdef MACVERSION
00034 #include <iac.h>
00035 #endif
00036 
00037 #include "error.h"
00038 #include "file.h"
00039 #include "memory.h"
00040 #include "ops.h"
00041 #include "resources.h"
00042 #include "strings.h"
00043 #include "lang.h"
00044 #include "langipc.h"
00045 #include "langinternal.h"
00046 #include "langexternal.h"
00047 #include "langsystem7.h"
00048 #include "langhtml.h"
00049 #include "langwinipc.h"
00050 #include "process.h"
00051 #include "tableinternal.h"
00052 #include "tablestructure.h"
00053 #include "tableverbs.h"
00054 #include "op.h"
00055 #include "opinternal.h"
00056 #include "oplist.h"
00057 #include "opverbs.h"
00058 #include "kernelverbs.h"
00059 #include "kernelverbdefs.h"
00060 #include "shell.rsrc.h"
00061 #include "timedate.h"
00062 #include "WinSockNetEvents.h"
00063 #ifdef flcomponent
00064 #include "osacomponent.h"
00065 #endif
00066 
00067 #include "tableverbs.h"  //6.1b8 AR: we need gettablevalue
00068 #include "byteorder.h"  /* 2006-04-08 aradke: endianness conversion macros */
00069 
00070 #include "iso8859.c"
00071 
00072 extern boolean frontierversion (tyvaluerecord *v); //implemted in shellsysverbs.c
00073 extern boolean sysos (tyvaluerecord *v); //implemted in shellsysverbs.c
00074 
00075 #define fldebugwebsite false
00076 
00077 #define str_separatorline BIGSTRING ("\x17<hr size=2 width=100% />\r")/* 2005-12-18 creedon - end tag with space slash for compatibility with post HTML 4.01 standards */
00078 #define str_macroerror BIGSTRING ("\x20<b>[</b>Macro error: ^0<b>]</b>\r")
00079 #define str_mailto  BIGSTRING ("\x1A<a href=\"mailto:^0\">^0</a>")
00080 #define str_hotlink BIGSTRING ("\x13<a href=\"^0\">^1</a>")
00081 #define str_pagebreak BIGSTRING ("\x05<p />") /* 2005-10-29 creedon - changed from <p> to <p /> for compatibility with post HTML 4.01 standards */
00082 #define str_startbold BIGSTRING ("\x03<b>")
00083 #define str_endbold BIGSTRING ("\x04</b>")
00084 #define str_default BIGSTRING ("\x07" "default")
00085 #define str_index BIGSTRING ("\x05" "index")
00086 
00087 #define str_closetag BIGSTRING ("\x05" "</^0>")
00088 
00089 #define flcurlybracemacros true
00090 #define startmacrochar '{'
00091 #define endmacrochar '}'
00092 #define flvariablemacrocharacters true /*PBS 7.1b1: variable macro characters*/
00093 
00094 #define maxglossarynamelength 127 /*DW 5/3/96: upped from 31*/
00095 
00096 #define str_no  BIGSTRING ("\x02" "no")
00097 #define str_yes BIGSTRING ("\x03" "yes")
00098 
00099 #define str_adrpagetable        BIGSTRING ("\x16" "html.data.adrpagetable")
00100 #define str_websitesdata        BIGSTRING ("\x0e" "websites.#data")
00101 #define str_userhtmlprefs       BIGSTRING ("\x0f" "user.html.prefs")
00102 #define str_usermacros          BIGSTRING ("\x10" "user.html.macros")
00103 #define str_standardmacros      BIGSTRING ("\x18" "html.data.standardMacros")
00104 #define str_tools               BIGSTRING ("\x05" "tools")
00105 #define str_glossary            BIGSTRING ("\x08" "glossary")
00106 #define str_images              BIGSTRING ("\x06" "images")
00107 #define str_glosspatch          BIGSTRING ("\x0e" "[[#glossPatch ")
00108 #define str_useglosspatcher     BIGSTRING ("\x0f" "useGlossPatcher")
00109 #define str_renderedtext        BIGSTRING ("\x0c" "renderedtext")
00110 #ifdef MACVERSION
00111     #define str_iso8859map      BIGSTRING ("\x15" "html.data.iso8859.mac")
00112 #else
00113     #define str_iso8859map      BIGSTRING ("\x15" "html.data.iso8859.win")
00114 #endif
00115 
00116 #define str_template            BIGSTRING ("\x08" "template")
00117 #define str_indirecttemplate    BIGSTRING ("\x10" "indirectTemplate")
00118 #define str_adrobject           BIGSTRING ("\x09" "adrobject")
00119 #define str_ftpsite             BIGSTRING ("\x07" "ftpsite")
00120 #define str_fileextension       BIGSTRING ("\x0d" "fileextension")
00121 #define str_maxfilenamelength   BIGSTRING ("\x11" "maxfilenamelength")
00122 #define str_defaulttemplate     BIGSTRING ("\x0f" "defaulttemplate")
00123 #define str_defaultfilename     BIGSTRING ("\x0f" "defaultfilename")
00124 #define str_directivesonlyatbeginning   BIGSTRING ("\x19" "directivesOnlyAtBeginning")
00125 
00126 #define STR_P_ERRORPAGETEMPLATE         BIGSTRING ("\x4C" "<HTML><HEAD><TITLE>^0</TITLE></HEAD><BODY><H1>^0</H1><P>^1</P></BODY></HTML>")
00127 #define STR_P_MISSING_HOST_HEADER       BIGSTRING ("\x40" "Every HTTP/1.1 request must include a Host header")
00128 #define STR_P_UNSUPPORTED_VERSION       BIGSTRING ("\x24" "This server does not support HTTP/^0")
00129 #define STR_P_INVALID_URI               BIGSTRING ("\x23" "All URIs must begin with / or http:")
00130 #define STR_P_BODY_NOT_READ             BIGSTRING ("\x22" "The request body couldn't be read.")
00131 #define STR_P_METHOD_NOT_ALLOWED        BIGSTRING ("\x20" "^0 isn't allowed on this object.")
00132 #define STR_P_INVALID_REQUEST_LINE      BIGSTRING ("\x1C" "The request line is invalid.")
00133 #define STR_P_USERWEBSERVERPOSTFILTERS  BIGSTRING ("\x1A" "user.webserver.postfilters")
00134 #define STR_P_USERWEBSERVERPREFILTERS   BIGSTRING ("\x19" "user.webserver.prefilters")
00135 #define STR_P_USERWEBSERVERRESPONDERS   BIGSTRING ("\x19" "user.webserver.responders")
00136 #define STR_P_PROCESSING_STARTED        BIGSTRING ("\x18" "requestProcessingStarted")
00137 #define STR_P_WEBSERVERDATARESPONSES    BIGSTRING ("\x18" "webserver.data.responses")
00138 
00139 #define STR_P_macrostartchars           BIGSTRING ("\x14" "macrostartcharacters") /*PBS 7.1b1*/
00140 #define STR_P_macroendchars             BIGSTRING ("\x12" "macroendcharacters")
00141 
00142 #define STR_P_flprocessmacrosintags     BIGSTRING ("\x17" "processmacrosinhtmltags")
00143 
00144 #ifdef PIKE /*7.0 PBS: server string is Radio UserLand*/
00145 
00146 #ifndef OPMLEDITOR
00147 
00148     #if TARGET_API_MAC_CARBON == 1
00149         #define STR_P_SERVERSTRING              BIGSTRING ("\x15" "Radio UserLand/^0-^1X")
00150     #else
00151         #define STR_P_SERVERSTRING              BIGSTRING ("\x14" "Radio UserLand/^0-^1")
00152     #endif
00153 
00154 #else  // OPMLEDITOR 2005-04-06 dluebbert
00155     #if TARGET_API_MAC_CARBON == 1
00156         #define STR_P_SERVERSTRING              BIGSTRING ("\x0b" "OPML/^0-^1X")
00157     #else
00158         #define STR_P_SERVERSTRING              BIGSTRING ("\x0a" "OPML/^0-^1")
00159     #endif
00160 #endif // OPMLEDITOR
00161 
00162 #else // !PIKE
00163 
00164     #if TARGET_API_MAC_CARBON == 1
00165         #define STR_P_SERVERSTRING          BIGSTRING ("\x0f" "Frontier/^0-^1X") /* 2005-01-04 creedon - removed UserLand for open source release */
00166     #else
00167         #define STR_P_SERVERSTRING          BIGSTRING ("\x0e" "Frontier/^0-^1") /* 2005-01-04 creedon - removed UserLand for open source release */
00168     #endif
00169 
00170 #endif  
00171 
00172 #define STR_P_RESPONDERERROR            BIGSTRING ("\x16" "Responder method error")
00173 #define STR_P_LOGADD                    BIGSTRING ("\x16" "log.addToGuestDatabase")
00174 #define STR_P_USERWEBSERVERCONFIG       BIGSTRING ("\x15" "user.webserver.config")
00175 #define STR_P_USERWEBSERVERPREFS        BIGSTRING ("\x14" "user.webserver.prefs")
00176 #define STR_P_USERWEBSERVERSTATS        BIGSTRING ("\x14" "user.webserver.stats")
00177 #define STR_P_INETDCONFIGTABLEADR       BIGSTRING ("\x13" "inetdConfigTableAdr")
00178 #define STR_P_DEFAULTTIMEOUTSECS        BIGSTRING ("\x12" "defaultTimeoutSecs")
00179 #define STR_P_USERINETDLISTENS          BIGSTRING ("\x12" "user.inetd.listens")
00180 #define STR_P_WEBSERVERDISPATCH         BIGSTRING ("\x12" "webserver.dispatch")
00181 #define STR_P_RESPONDERTABLEADR         BIGSTRING ("\x11" "responderTableAdr")
00182 #define STR_P_POSTFILTERERROR           BIGSTRING ("\x11" "Post Filter error")
00183 #define STR_P_PREFILTERERROR            BIGSTRING ("\x10" "Pre filter error")
00184 #define STR_P_DEFAULTRESPONDER          BIGSTRING ("\x10" "defaultResponder")
00185 #define STR_P_USERINETDPREFS            BIGSTRING ("\x10" "user.inetd.prefs")
00186 #define STR_P_RESPONSEHEADERS           BIGSTRING ("\x0F" "responseHeaders")
00187 #define STR_P_WHATWEREWEDOING           BIGSTRING ("\x0F" "whatWereWeDoing")
00188 #define STR_P_RETURNCHUNKSIZE           BIGSTRING ("\x0F" "returnChunkSize")
00189 #define STR_P_ADRHEADERTABLE            BIGSTRING ("\x0E" "adrHeaderTable")
00190 #define STR_P_MAXCONNECTIONS            BIGSTRING ("\x0E" "maxConnections")
00191 #define STR_P_REQUESTHEADERS            BIGSTRING ("\x0E" "requestHeaders")
00192 #define STR_P_CONTENT_LENGTH            BIGSTRING ("\x0E" "Content-Length")
00193 #define STR_P_WHATWENTWRONG             BIGSTRING ("\x0D" "whatWentWrong")
00194 #define STR_P_100CONTINUE               BIGSTRING ("\x0C" "100-continue")
00195 #define STR_P_RESPONSEBODY              BIGSTRING ("\x0C" "responseBody")
00196 #define STR_P_REQUESTBODY               BIGSTRING ("\x0B" "requestBody")
00197 #define STR_P_MAXMEMAVAIL               BIGSTRING ("\x0B" "maxMemAvail")
00198 #define STR_P_MINMEMAVAIL               BIGSTRING ("\x0B" "minMemAvail")
00199 #define STR_P_CONNECTION                BIGSTRING ("\x0A" "Connection")
00200 #define STR_P_PARAMTABLE                BIGSTRING ("\x0A" "paramTable")
00201 #define STR_P_SEARCHARGS                BIGSTRING ("\x0A" "searchArgs")
00202 #define STR_P_FIRSTLINE                 BIGSTRING ("\x09" "firstLine")
00203 #define STR_P_CHUNKSIZE                 BIGSTRING ("\x09" "chunksize")
00204 #define STR_P_HTTP11                    BIGSTRING ("\x09" "HTTP/1.1 ")
00205 #define STR_P_CONDITION                 BIGSTRING ("\x09" "condition")
00206 #define STR_P_RESPONDER                 BIGSTRING ("\x09" "responder")
00207 #define STR_P_PATHARGS                  BIGSTRING ("\x08" "pathArgs")
00208 #define STR_P_ADRTABLE                  BIGSTRING ("\x08" "adrTable")
00209 #define STR_P_FLPARAMS                  BIGSTRING ("\x08" "flParams")
00210 #define STR_P_ENABLED                   BIGSTRING ("\x07" "enabled")
00211 #define STR_P_REQUEST                   BIGSTRING ("\x07" "request")
00212 #define STR_P_TIMEOUT                   BIGSTRING ("\x07" "timeout")
00213 #define STR_P_COOKIES                   BIGSTRING ("\x07" "cookies")
00214 #define STR_P_METHODS                   BIGSTRING ("\x07" "methods")
00215 #define STR_P_FLLEGAL                   BIGSTRING ("\x07" "flLegal")
00216 #define STR_P_FLCLOSE                   BIGSTRING ("\x07" "flClose")
00217 #define STR_P_NOWAIT                    BIGSTRING ("\x06" "noWait")
00218 #define STR_P_METHOD                    BIGSTRING ("\x06" "method")
00219 #define STR_P_EXPECT                    BIGSTRING ("\x06" "Expect")
00220 #define STR_P_STREAM                    BIGSTRING ("\x06" "stream")
00221 #define STR_P_REFCON                    BIGSTRING ("\x06" "refcon")
00222 #define STR_P_CLIENT                    BIGSTRING ("\x06" "client")
00223 #define STR_P_COOKIE                    BIGSTRING ("\x06" "Cookie")
00224 #define STR_P_SERVER                    BIGSTRING ("\x06" "Server")
00225 #define STR_P_UNKNOWN                   BIGSTRING ("\x07" "UNKNOWN")
00226 #define STR_P_THREAD                    BIGSTRING ("\x06" "thread")
00227 #define STR_P_DAEMON                    BIGSTRING ("\x06" "daemon")
00228 #define STR_P_ALLOW                     BIGSTRING ("\x05" "ALLOW")
00229 #define STR_P_CLOSE                     BIGSTRING ("\x05" "close")
00230 #define STR_P_READY                     BIGSTRING ("\x05" "ready")
00231 #define STR_P_STATS                     BIGSTRING ("\x05" "stats")
00232 #define STR_P_COUNT                     BIGSTRING ("\x05" "count")
00233 #define STR_P_CODE                      BIGSTRING ("\x04" "code")
00234 #define STR_P_HITS                      BIGSTRING ("\x04" "hits")
00235 #define STR_P_HOST                      BIGSTRING ("\x04" "host")
00236 #define STR_P_PORT                      BIGSTRING ("\x04" "port")
00237 #define STR_P_PATH                      BIGSTRING ("\x04" "path")
00238 #define STR_P_DATE                      BIGSTRING ("\x04" "Date")
00239 #define STR_P_CRLFCRLF                  BIGSTRING ("\x04" "\r\n\r\n")
00240 #define STR_P_ANY                       BIGSTRING ("\x03" "any")
00241 #define STR_P_URI                       BIGSTRING ("\x03" "URI")
00242 #define STR_P_DOLLAR_ENCODED            BIGSTRING ("\x03" "%24")
00243 #define STR_P_CRLF                      BIGSTRING ("\x02" "\r\n")
00244 #define STR_P_COLON                     BIGSTRING ("\x02" ": ")
00245 #define STR_P_DOLLAR                    BIGSTRING ("\x01" "$")
00246 #define STR_P_SPACE                     BIGSTRING ("\x01" " ")
00247 #define STR_P_EMPTY                     BIGSTRING ("\x00")
00248 
00249 #define STR_STATUSCONTINUE              "HTTP/1.1 100 CONTINUE\r\n\r\n"
00250 #define sizestatuscontinue              25
00251 
00252 typedef enum tyhtmlverbtoken { /*verbs that are processed by langhtml.c*/
00253     
00254     processmacrosfunc,
00255     
00256     urldecodefunc,
00257     
00258     urlencodefunc,
00259     
00260     parseargsfunc,
00261     
00262     iso8859encodefunc,
00263 
00264     getgifheightwidthfunc,
00265 
00266     getjpegheightwidthfunc,
00267     
00268     buildpagetablefunc,
00269     
00270     refglossaryfunc,
00271     
00272     getpreffunc,
00273     
00274     getonedirectivefunc,
00275     
00276     rundirectivefunc,
00277     
00278     rundirectivesfunc,
00279     
00280     runoutlinedirectivesfunc,
00281     
00282     cleanforexportfunc,
00283     
00284     normalizenamefunc,
00285     
00286     glossarypatcherfunc,
00287     
00288     expandurlsfunc,
00289     
00290     traversalskipfunc,
00291     
00292     getpagetableaddressfunc,
00293 
00294     htmlneutermacrosfunc,
00295 
00296     htmlneutertagsfunc,
00297     
00298     htmlcalendardrawfunc,
00299 
00300     /* searchengine */
00301 
00302     stripmarkupfunc,
00303     
00304     deindexpagefunc,
00305     
00306     indexpagefunc,
00307     
00308     cleanindexfunc,
00309     
00310     unionmatchesfunc,
00311     
00312     /* mainResponder.calendar */
00313     
00314     mrcalendargetaddressdayfunc,
00315     
00316     mrcalendargetdayaddressfunc,
00317     
00318     mrcalendargetfirstaddressfunc,
00319     
00320     mrcalendargetfirstdayfunc,
00321     
00322     mrcalendargetlastaddressfunc,
00323     
00324     mrcalendargetlastdayfunc,
00325     
00326     mrcalendargetmostrecentaddressfunc,
00327     
00328     mrcalendargetmostrecentdayfunc,
00329     
00330     mrcalendargetnextaddressfunc,
00331     
00332     mrcalendargetnextdayfunc,
00333     
00334     mrcalendarnavigatefunc,
00335 
00336     /* webserver */
00337 
00338     webserverserverfunc,
00339 
00340     webserverdispatchfunc,
00341 
00342     webserverparseheadersfunc,
00343 
00344     webserverparsecookiesfunc,
00345 
00346     webserverbuildresponsefunc,
00347 
00348     webserverbuilderrorpagefunc,
00349 
00350     webservergetserverstringfunc,
00351 
00352     /* inetd */
00353 
00354     inetdsupervisorfunc,
00355 
00356     cthtmlverbs
00357     } tyhtmlverbtoken;
00358 
00359 static bigstring bsdebug;
00360 
00361 static boolean flpagemillfile = false;
00362 
00363 #if version42orgreater
00364 
00365 //static ptraddress callbackscript = nil;
00366 
00367 typedef struct typrocessmacrosinfo {
00368 
00369     hdlhashtable hpagetable;
00370     hdlhashtable hstandardmacros;
00371     hdlhashtable huserprefs;
00372     hdlhashtable husermacros;
00373     hdlhashtable htools;
00374     hdlhashtable hmacrocontext;
00375     
00376     boolean flprocessmacros;
00377     boolean flexpandglossaryitems;
00378     boolean flautoparagraphs;
00379     boolean flactiveurls;
00380     boolean flclaycompatibility;
00381     boolean flisofilter;
00382     } typrocessmacrosinfo, *ptrprocessmacrosinfo;
00383 
00384 #else
00385 
00386 static tyvaluerecord osaval = { binaryvaluetype };
00387 
00388 #endif
00389 
00390 
00391 #ifdef MACVERSION
00392 #pragma mark === processhtmlmacros ===
00393 #endif
00394 
00395 static boolean htmlcallbackerror (bigstring bsmsg, ptrvoid perrorstring) {
00396     
00397     /*
00398     4.0.2b1 dmb: this error trapping isn't bullet proof, but it should 
00399     be fine since once an error occurs, script execution quickly unwinds, 
00400     with no thread yielding.
00401     */
00402     
00403     copystring (bsmsg, (ptrstring) perrorstring);
00404     
00405     return (true);
00406     } /*htmlcallbackerror*/
00407 
00408 #if 0
00409 #if version42orgreater
00410 
00411 static boolean frontTextScriptCall (OSType idroutine, Handle stringparam, Handle *hresult, bigstring errorstring) {
00412     
00413     /*
00414     an interface for a script call that takes one parameter, a string,
00415     and returns a text value. a common situation, and it makes it possible for
00416     me to include some sample code in the toolkit.
00417     
00418     dmb 4.1b11: take Handle parameter, not bigstring, which we consume
00419     
00420     dmb 3/18/97: non-component version
00421     
00422     dmb 5.0d14: save/restore perrorstring; don't disable yield
00423     */
00424     
00425     bigstring bsfunction;
00426     hdlhashnode hnode;
00427     hdltreenode hcode, hparam;
00428     boolean fl = false;
00429     langerrormessagecallback savecallback;
00430     ptrvoid saveerrorstring;
00431     tyvaluerecord vparam;
00432     tyvaluerecord vresult;
00433     
00434     *hresult = nil;
00435     
00436     ostypetostring (idroutine, bsfunction);
00437     
00438     if (!setheapvalue (stringparam, stringvaluetype, &vparam)) // consumes stringparam
00439         return (false);
00440     
00441     exemptfromtmpstack (&vparam); // 5.0.2b10 dmb
00442     
00443     if (!newconstnode (vparam, &hparam))    // consumes vparam
00444         return (false);
00445     
00446 //  if (!getaddressvalue (callbackval, &htable, bs))
00447 //      goto exit;
00448     
00449     if (!hashtablelookupnode ((*callbackscript).ht, (*callbackscript).bs, &hnode))
00450         goto exit;
00451     
00452     if (!langgetnodecode ((*callbackscript).ht, bsfunction, hnode, &hcode)) {
00453     
00454         if (!fllangerror)
00455             langparamerror (notfunctionerror, (*callbackscript).bs);
00456         
00457         goto exit;
00458         }
00459     
00460     savecallback = langcallbacks.errormessagecallback;
00461     
00462     saveerrorstring = langcallbacks.errormessagerefcon;
00463     
00464     langcallbacks.errormessagerefcon = errorstring;
00465     
00466     langcallbacks.errormessagecallback = &htmlcallbackerror;
00467     
00468     //  ++fldisableyield;
00469     
00470     fl = langfunctioncall (nil, (*callbackscript).ht, hnode, bsfunction, hcode, hparam, &vresult);
00471     
00472     //  --fldisableyield;
00473     
00474     if (fl)
00475         fl = coercetostring (&vresult);
00476     else
00477         fllangerror = false;    /*we don't want to abort anything*/
00478 
00479     if (fl) {
00480         
00481         exemptfromtmpstack (&vresult);
00482         
00483         *hresult = vresult.data.stringvalue;
00484         }
00485     
00486     langcallbacks.errormessagerefcon = saveerrorstring;
00487     
00488     langcallbacks.errormessagecallback = savecallback;
00489     
00490     exit:
00491     
00492     langdisposetree (hparam);
00493     
00494     return (fl);
00495     } /*frontTextScriptCall*/
00496 
00497 #else
00498 
00499 static boolean frontTextScriptCall (OSType idroutine, Handle stringparam, Handle *hresult, bigstring errorstring) {
00500     
00501     /*
00502     an interface for a script call that takes one parameter, a string,
00503     and returns a text value. a common situation, and it makes it possible for
00504     me to include some sample code in the toolkit.
00505     
00506     dmb 4.1b11: take Handle parameter, not bigstring
00507     */
00508     
00509     AppleEvent event, reply = {typeNull, nil};
00510     AEDesc script, result;
00511     boolean fl;
00512     langerrormessagecallback savecallback;
00513     ptrvoid saveerrorstring;
00514     OSErr ec;
00515     
00516     *hresult = nil;
00517     
00518     if (!newselfaddressedevent (idroutine, &event))
00519         return (false);
00520     
00521     #if TARGET_API_MAC_CARBON == 1 /*PBS 03/14/02: AE OS X fix.*/   
00522     
00523         typeAEList (&script, typeChar, stringparam);
00524     
00525     #else
00526     
00527         script.descriptorType = typeChar;
00528 
00529         script.dataHandle = stringparam;
00530     
00531     #endif
00532     
00533     ec = AEPutKeyDesc (&event, 'prm1', &script);
00534     
00535     disposehandle (stringparam);
00536     
00537     if (ec != noErr)
00538         goto error;
00539     
00540     savecallback = langcallbacks.errormessagecallback;
00541     
00542     saveerrorstring = langcallbacks.errormessagerefcon;
00543     
00544     langcallbacks.errormessagerefcon = errorstring;
00545     
00546     langcallbacks.errormessagecallback = &htmlcallbackerror;
00547     
00548     fl = evaluateosascriptevent (&osaval, &event, &reply);
00549     
00550     langcallbacks.errormessagerefcon = saveerrorstring;
00551     
00552     langcallbacks.errormessagecallback = savecallback;
00553 
00554     if (!fl)
00555         goto error;
00556     
00557     AEDisposeDesc (&event); 
00558     
00559     ec = AEGetParamDesc (&reply, keyDirectObject, typeChar, &result);
00560     
00561     AEDisposeDesc (&reply); 
00562     
00563     if (ec != noErr)
00564         goto error;
00565     
00566     #if TARGET_API_MAC_CARBON == 1 /*PBS 03/14/02: AE OS X fix.*/
00567     
00568         copydatahandle (&result, hresult);
00569         
00570     #else
00571     
00572         *hresult = result.dataHandle;
00573     
00574     #endif
00575     
00576     return (true);
00577     
00578     error:
00579     
00580     AEDisposeDesc (&event); 
00581     
00582     AEDisposeDesc (&reply); 
00583     
00584     return (false);
00585     } /*frontTextScriptCall*/
00586 
00587 #endif
00588 #endif
00589 
00590 
00591 static boolean strongcoercetostring (tyvaluerecord *val) {
00592 
00593     boolean fl;
00594     
00595     flcoerceexternaltostring = true;
00596     
00597     fl = coercetostring (val);
00598     
00599     flcoerceexternaltostring = false;
00600     
00601     return (fl);
00602     } /*strongcoercetostring*/
00603 
00604 
00605 static boolean langpushwithtable (hdlhashtable ht, hdlhashtable hwith) {
00606 
00607     /*
00608     5.0.2b14 dmb: sucked out of evaluatewith, comes in at a different level
00609     */
00610     
00611     short n = (**ht).ctwithvalues;
00612     bigstring bs;
00613     tyvaluerecord valwith;
00614     
00615     if (n == 7) { /*maximum value of ctwithvalues*/
00616         
00617         langlongparamerror (toomanywithtableserror, n);
00618         
00619         return (false);
00620         }
00621     
00622     if (!setaddressvalue (hwith, zerostring, &valwith))
00623         return (false);
00624     
00625     langgetwithvaluename (++n, bs);
00626     
00627     (**ht).ctwithvalues = n; /*optimization for langfindsymbol*/
00628     
00629     if (!hashtableassign (ht, bs, valwith)) {
00630         
00631         disposevaluerecord (valwith, false);
00632         
00633         return (false);
00634         }
00635 
00636     exemptfromtmpstack (&valwith); /*its in the local table now*/
00637     
00638     return (true);
00639     } /*langpushwithtable*/
00640 
00641 
00642 static boolean htmlgetdefaultpagetable (hdlhashtable *hpagetable) {
00643     
00644     /*
00645     5.0.2 dmb: super-fast lookup of "websites.[#data]"
00646     */
00647     
00648     return (langfastaddresstotable (roottable, str_websitesdata, hpagetable));
00649     } /*htmlgetdefaultpagetable*/
00650 
00651 
00652 static boolean getoptionalpagetablevalue (hdltreenode hp1, short n, hdlhashtable *hpagetable) {
00653 
00654     if (langgetparamcount (hp1) >= n) {
00655         
00656         flnextparamislast = true;
00657         
00658         if (!gettablevalue (hp1, n, hpagetable))
00659             return (false);
00660         }
00661     else {
00662         if (!htmlgetdefaultpagetable (hpagetable))
00663             return (false);
00664         }
00665     
00666     return (true);
00667     } /*getoptionalpagetablevalue*/
00668 
00669 
00670 #if 0
00671 
00672 static boolean htmlgetpagetable (hdlhashtable *hpagetable) {
00673     
00674     /*
00675     5.0.2 dmb: super-fast lookup of html.data.adrpagetable. we skip the 
00676     normal error reporting, because we're responsible for setting this 
00677     value up properly
00678     */
00679     
00680     hdlhashtable ht;
00681     bigstring bs;
00682     tyvaluerecord val;
00683     hdlhashnode hnode;
00684     
00685     if (!langexpandtodotparams (str_adrpagetable, &ht, bs))
00686         return (false);
00687     
00688     if (!hashtablelookup (ht, bs, &val, &hnode) || (val.valuetype != addressvaluetype))
00689         return (false);
00690     
00691     if (!getaddressvalue (val, &ht, bs))
00692         return (false);
00693     
00694     if (!hashtablelookup (ht, bs, &val, &hnode))
00695         return (false);
00696     
00697     return (tablevaltotable (val, hpagetable, hnode));
00698     } /*htmlgetpagetable*/
00699 
00700 #endif
00701 
00702         
00703 static boolean htmlgetprefstable (hdlhashtable *huserprefs) {
00704     
00705     /*
00706     5.0.2 dmb: super-fast lookup of "user.html.prefs"
00707     */
00708     
00709     return (langfastaddresstotable (roottable, str_userhtmlprefs, huserprefs));
00710     } /*htmlgetprefstable*/
00711 
00712 
00713 static boolean htmlgetpref (typrocessmacrosinfo *pmi, bigstring pref, tyvaluerecord *val) {
00714 
00715     /*
00716     5.0.2 dmb: pulled into kernel. we return val on the tmp stack
00717     
00718     on getPref (prefName, adrpagedata=@websites.["#data"]) { «new in 4.1
00719         «Look for a preference directive, a global pref, or return a default
00720     */
00721     
00722     //local (val);
00723     bigstring bs;
00724     hdlhashnode hnode;
00725     
00726     //  try { «try to get it from pagedata
00727     //      val = adrpagedata^.[prefName];
00728     
00729     if (hashtablelookup ((*pmi).hpagetable, pref, val, &hnode)) {
00730         
00731         //  case val {
00732         //      "yes";
00733         //      "true" {
00734         //          return (true)};
00735         //      "no";
00736         //      "false" {
00737         //          return (false)}}
00738         //  else {
00739         //      return (val)}};
00740         
00741         if ((*val).valuetype == stringvaluetype) {
00742             
00743             pullstringvalue (val, bs);
00744             
00745             if (equalstrings (bs, str_yes) || equalstrings (bs, bstrue))
00746                 return (setbooleanvalue (true, val));
00747             
00748             if (equalstrings (bs, str_no) || equalstrings (bs, bsfalse))
00749                 return (setbooleanvalue (false, val));
00750             }
00751         
00752         return (copyvaluerecord (*val, val));
00753         }
00754     
00755     //  try { «try to get it from user.html.prefs
00756     //      return (user.html.prefs.[prefName])};
00757     
00758     if (hashtablelookup ((*pmi).huserprefs, pref, val, &hnode))
00759         return (copyvaluerecord (*val, val));
00760     
00761     //  case string.lower (prefName) { «return a default value
00762     //      "fileextension" {
00763     //          return (".html")};
00764     //      "maxfilenamelength" {
00765     //          return (31)};
00766     //      "defaulttemplate" {
00767     //          return ("normal")};
00768     //      "defaultFileName" {
00769     //          "default"}}
00770     
00771     copystring (pref, bs);
00772     alllower (bs);
00773     
00774     if (equalstrings (bs, str_fileextension))
00775         return (setstringvalue (BIGSTRING ("\x05" ".html"), val));
00776     
00777     if (equalstrings (bs, str_maxfilenamelength))
00778         return (setlongvalue (31, val));
00779     
00780     if (equalstrings (bs, str_defaulttemplate))
00781         return (setstringvalue (BIGSTRING ("\x06" "normal"), val));
00782     
00783     if (equalstrings (bs, str_defaultfilename))
00784         return (setstringvalue (BIGSTRING ("\x07" "default"), val));
00785 
00786     //  else { «unknown or default prefs are true
00787     //      return (true)}};
00788     
00789     return (setbooleanvalue (true, val));
00790     } /*htmlgetpref*/
00791 
00792 
00793 static boolean htmlgetbooleanpref (typrocessmacrosinfo *pmi, bigstring pref, boolean *flpref) {
00794     
00795     tyvaluerecord val;
00796     
00797     if (!htmlgetpref (pmi, pref, &val))
00798         return (false);
00799     
00800     if (!coercetoboolean (&val))
00801         return (false);
00802     
00803     *flpref = val.data.flvalue;
00804     
00805     return (true);
00806     } /*htmlgetbooleanpref*/
00807 
00808 
00809 static boolean htmlgetstringpref (typrocessmacrosinfo *pmi, bigstring pref, bigstring bspref) {
00810     
00811     tyvaluerecord val;
00812     
00813     if (!htmlgetpref (pmi, pref, &val))
00814         return (false);
00815     
00816     if (!coercetostring (&val))
00817         return (false);
00818     
00819     pullstringvalue (&val, bspref);
00820     
00821     return (true);
00822     } /*htmlgetstringpref*/
00823 
00824 
00825 static boolean htmlrefglossary (typrocessmacrosinfo *pmi, Handle hreference, bigstring perrorstring, Handle *hresult) {
00826 #pragma unused (pmi)
00827 
00828     /*
00829     expand the glossary item, consuming it. return the result in hresult, which 
00830     our caller must dispose. on error, return the error text in perrorstring.
00831     
00832     5.0.2b14 dmb: we used to frontTextScriptCall to call html.data.processmacroscallback, 
00833     which called html.refGlossary. now we call html.refGlossary directly. I looked at 
00834     kernelizing the whole thing here, but it would be a lot of work. plus, these type of 
00835     lookups aren't that slow in UserTalk.
00836     */
00837     
00838     tyvaluerecord vparams, vresult;
00839     langerrormessagecallback savecallback = langcallbacks.errormessagecallback;
00840     ptrvoid saveerrorstring = langcallbacks.errormessagerefcon;
00841     boolean fl;
00842     
00843     setheapvalue (hreference, stringvaluetype, &vparams);
00844     
00845     if (!coercetolist (&vparams, listvaluetype))
00846         return (false);
00847     
00848     langcallbacks.errormessagecallback = &htmlcallbackerror;
00849     
00850     langcallbacks.errormessagerefcon = perrorstring;
00851     
00852     fl = langrunscript (BIGSTRING ("\x10" "html.refglossary"), &vparams, nil, &vresult) && strongcoercetostring (&vresult);
00853     
00854     langcallbacks.errormessagerefcon = saveerrorstring;
00855     
00856     langcallbacks.errormessagecallback = savecallback;
00857     
00858     fllangerror = false;
00859     
00860     if (fl) {
00861         
00862         exemptfromtmpstack (&vresult);
00863         
00864         *hresult = vresult.data.stringvalue;
00865         }
00866     
00867     return (fl);
00868     
00869     /*
00870     //  local (adrpagetable = html.data.adrPageTable);
00871     //  local (adrobject = adrpagetable^.adrobject);
00872     //  local (adrparent = parentOf (adrobject^));
00873     
00874     hdlhashtable hpagetable = (*pmi).hpagetable;
00875     tyaddress adrobject;
00876     tyaddress adrparent;
00877     tyaddress adrelement;
00878     tyvaluetype objecttype;
00879     tyvaluerecord val;
00880     
00881     if (!hashtablelookup (hpagetable, str_adrobject, &val) || !getaddressvalue (val, &adrobject.ht, adrobject.bs)) {
00882         
00883         langparamerror (unknownidentifiererror, str_adrobject);
00884         
00885         return (false);
00886         }
00887     
00888     findinparenttable (adrobject.ht, &adrparent.ht, adrparent.bs);
00889     
00890     
00891     if defined (adrpagetable^.glossary) { «look in the default glossary
00892         local (adrelement = @adrpagetable^.glossary^ [name]);
00893         if defined (adrelement^) {
00894             return (foundit (adrelement))}};
00895     try { «check the hierarchy for glossary tables
00896         local (nomad = adrparent, newnomad, found = false);
00897         on glossLookup (glossName) {
00898             if (defined (nomad^.[glossName])) { «the table has a glossary
00899                 local (adrelement = @nomad^.[glossName].[name]);
00900                 if defined (adrelement^) { «it defines the term
00901                     found = true;
00902                     return (foundit (adrelement))}}};
00903         loop {
00904             s = glossLookup ("glossary");
00905             if found {
00906                 return (s)};
00907             s = glossLookup ("#glossary");
00908             if found {
00909                 return (s)};
00910             newnomad = parentOf (nomad^);
00911             if newnomad == "" {
00912                 break};
00913             nomad = newnomad}};
00914     try { «check the user table glossary
00915         return (string (user.html.glossary [name]))};
00916     if not (name beginsWith "#") {
00917         try { «look for objects at the same level with the name
00918             local (normalizedName = html.normalizeName (name, adrpagetable));
00919             local (ext = html.getPref ("fileExtension", adrpagetable));
00920             if defined (adrparent^.[normalizedName]) {
00921                 return ("<a href=\"" + normalizedName + ext + "\">" + name + "</a>")};
00922             if defined (adrparent^.[name]) {
00923                 return ("<a href=\"" + normalizedName + ext + "\">" + name + "</a>")}}};
00924     scriptError ("There is no glossary entry named \"" + name + "\"")}
00925     
00926     foundit:
00927     //  on foundit (adr) { «new in 5.0
00928     //      case typeOf (adr^) {
00929     //          scriptType { //new in 5.0, glossary items can be scripts
00930     //              return (adr^ ())};
00931     //          tableType { //new in 5.0, convert a table (prettier to look at) to a glossPatch
00932     //              local (s = "[[#glossPatch ");
00933     //              s = s + adr^.linetext + "|";
00934     //              s = s + adr^.path + "|]]";
00935     //              return (s)}}
00936     //      else {
00937     //          local (s = string (adr^));
00938     //          if (s beginsWith "{") and (s endsWith "}") {
00939     //              return (html.processMacros (s))}
00940     //          else {
00941     //              return (s)}}};
00942     
00943         objecttype = langgetextendedvaluetype (&glossval);
00944         
00945         switch (objecttype) {
00946             
00947             case tablevaluetype:
00948                     if (langexternalvaltotable (glossval, &ht)) { // should never fail
00949                         if (!newtexthandle (str_glosspatch, hresult))
00950                             return (false);
00951                         
00952                         if (!langlookupstringvalue (ht, "\x08" "linetext", &x)
00953                             return (false);
00954                         
00955                         if (!pushhandle (x, *hresult))
00956                             return (false);
00957                         
00958                         disposehandle (x);
00959                         
00960                         if (!pushtexthandle ("\x01" "|", *hresult))
00961                             return (false);
00962                         
00963                         
00964             
00965             case scriptvaluetype:
00966                 break;
00967             
00968             default:
00969                 break;
00970             }
00971         
00972     //  return (frontTextScriptCall ('refg', term, hresult, errorstring));
00973     */
00974     
00975     } /*htmlrefglossary*/
00976     
00977 
00978 static boolean htmlcleanforexport (Handle x) {
00979 
00980     /*
00981     on cleanForExport (text) { «prepare text to leave Mac environment
00982         if sys.os () == "MacOS" {
00983             on replace (searchfor, replacewith) {
00984                 if string.patternMatch (searchfor, text) != 0 { «optimization
00985                     text = string.replaceAll (text, searchfor, replacewith)}};
00986             
00987             replace ("’", "'");
00988             replace ("‘", "'");
00989             replace ("“", “"”);
00990             replace ("”", “"”);
00991             replace ("•", "o");
00992             replace ("...", "...");
00993             replace (" ", " "); «a funny space that Word produces
00994             replace ("–", "--");
00995             replace ("«", "&lt;&lt;");
00996             replace ("»", "&gt;&gt;")};
00997         return (text)}
00998     */
00999     
01000     #ifdef MACVERSION
01001         handlestream s;
01002     
01003         openhandlestream (x, &s);
01004         
01005         for (s.pos = 0; s.pos < s.eof; ++s.pos) {
01006             
01007             switch ((*x) [s.pos]) { // set chreplace or bsreplace
01008             
01009                 case (char)0xd4:    /* '‘' open single quote */
01010                 case (char)0xd5:    /* '’' close single quote */
01011                     (*x) [s.pos] = '\'';
01012                     break;
01013                 
01014                 case (char)chopencurlyquote:
01015                 case (char)chclosecurlyquote:
01016                     (*x) [s.pos] = '\"';
01017                     break;
01018                 
01019                 case (char)0xa5:    /* '•' disk */
01020                     (*x) [s.pos] = 'o';
01021                     break;
01022                     
01023                 case (char)0xca:    /* ' ' non-breaking space */
01024                     (*x) [s.pos] = ' ';
01025                     break;
01026                 
01027                 case (char)0xc9:    /* '…' ellipsis */
01028                     mergehandlestreamstring (&s, 1, BIGSTRING ("\x03" "..."));
01029                     --s.pos;
01030                     break;
01031                 
01032                 case (char)0xd0:    /* '–' n-dash */
01033                 case (char)0xd1:    /* '—' m-dash */
01034                     mergehandlestreamstring (&s, 1, BIGSTRING ("\x02" "--"));
01035                     --s.pos;
01036                     break;
01037                 
01038                 case (char)chcomment:
01039                     mergehandlestreamstring (&s, 1, BIGSTRING ("\x08" "&lt;&lt;"));
01040                     --s.pos;
01041                     break;
01042                 
01043                 case (char)chendcomment:
01044                     mergehandlestreamstring (&s, 1, BIGSTRING ("\x08" "&gt;&gt;"));
01045                     --s.pos;
01046                     break;
01047                 }
01048             }
01049         
01050         closehandlestream (&s);
01051     #endif
01052     
01053     return (true);
01054     } /*htmlcleanforexport*/
01055 
01056 
01057 static void htmldisposemacrocontext (typrocessmacrosinfo *pmi) {
01058     
01059     disposehashtable ((*pmi).hmacrocontext, false);
01060     
01061     (*pmi).hmacrocontext = nil;
01062     } /*htmldisposemacrocontext*/
01063 
01064     
01065 static boolean htmlbuildmacrocontext (typrocessmacrosinfo *pmi) {
01066     
01067     /*
01068     5.0.2b15 dmb: build the with statement for evaluating macro expressions.
01069     also include a local "adrPageTable" value.
01070     
01071     5.0.2b17 dmb: restore the page table to the outermost with scope
01072     */
01073     
01074     hdlhashtable hstandardmacros, husermacros, htools;
01075     hdlhashtable ht, hcontext;
01076     tyvaluerecord val;
01077     bigstring bs, bstools;
01078     hdlhashnode hnode;
01079     
01080     if ((*pmi).hmacrocontext != nil)
01081         return (true);
01082     
01083     // find macros tables
01084     if (!langfastaddresstotable (builtinstable, str_standardmacros, &hstandardmacros))
01085         return (false);
01086     
01087     if (!langfastaddresstotable (roottable, str_usermacros, &husermacros))
01088         return (false);
01089     
01090     if (!newhashtable (&hcontext)) /*new table for the function when it runs*/
01091         return (false);
01092     
01093     (**hcontext).fllocaltable = true;
01094 
01095     (**hcontext).lexicalrefcon = 0L; /*'with' gets global scope*/
01096     
01097     // local (adrpagetable = html.data.adrPageTable)
01098     if (!findinparenttable ((*pmi).hpagetable, &ht, bs))
01099         goto error;
01100     
01101     if (!setaddressvalue (ht, bs, &val))
01102         goto error;
01103     
01104     if (!hashtableassign (hcontext, BIGSTRING ("\x0c" "adrPageTable"), val))
01105         goto error;
01106     
01107     exemptfromtmpstack (&val);
01108     
01109     //  with html.data.adrPageTable^, html.data.standardMacros, user.html.macros, toolTableAdr^ {
01110     if (!langpushwithtable (hcontext, (*pmi).hpagetable))
01111         goto error;
01112     
01113     if (!langpushwithtable (hcontext, hstandardmacros))
01114         goto error;
01115     
01116     if (!langpushwithtable (hcontext, husermacros))
01117         goto error;
01118     
01119     if (hashtablelookup ((*pmi).hpagetable, str_tools, &val, &hnode) && 
01120         (val.valuetype == addressvaluetype) &&
01121         getaddressvalue (val, &htools, bstools) &&
01122         hashtablelookup (htools, bstools, &val, &hnode) &&
01123         tablevaltotable (val, &htools, hnode))
01124         
01125         if (!langpushwithtable (hcontext, htools))
01126             goto error;
01127     
01128     (*pmi).hmacrocontext = hcontext;
01129     
01130     return (true);
01131     
01132     error:
01133         disposehashtable (hcontext, false);
01134         
01135         return (false);
01136     } /*htmlbuildmacrocontext*/
01137 
01138 
01139 static boolean htmlrunmacro (typrocessmacrosinfo *pmi, Handle macro, bigstring perrorstring, Handle *hresult) {
01140     
01141     /*
01142     run the macro, consuming it. return the result in hresult, which our caller 
01143     must dispose. on error, return the error text in perrorstring.
01144     
01145         local (toolTableAdr, nilTable);
01146         if defined (adrpagetable^.tools) {
01147             toolTableAdr = adrpagetable^.tools}
01148         else { «'with' wants a table
01149             new (tableType, @nilTable);
01150             toolTableAdr = @nilTable};
01151     
01152         try {
01153             local (macroResult);
01154             with html.data.adrPageTable^, html.data.standardMacros, user.html.macros, toolTableAdr^ {
01155                 macroResult = string (evaluate (s))};
01156     
01157     5.0.2b14 dmb: we used to frontTextScriptCall to call html.data.processmacroscallback, 
01158     now we do all the work right here.
01159     
01160     5.0.2b15 dmb: we also need to create a local "adrPageTable" value
01161 
01162     5.1b21 dmb: plugged leak on error
01163     
01164     6.2b6 AR: If langruntraperror returns false but didn't set perrorstring, our thread has probably been killed.
01165     */
01166     
01167     tyvaluerecord val;
01168     boolean fl = false;
01169     
01170     if (!htmlbuildmacrocontext (pmi))
01171         return (false);
01172         
01173     setemptystring (perrorstring); //6.2b6 AR
01174     
01175     chainhashtable ((*pmi).hmacrocontext);
01176     
01177     fl = langruntraperror (macro, &val, perrorstring) && strongcoercetostring (&val) && exemptfromtmpstack (&val);
01178     
01179     unchainhashtable ();
01180     
01181     if (fl) {
01182         
01183         Handle h = val.data.stringvalue;
01184         
01185         if ((*pmi).flexpandglossaryitems) {
01186         
01187             //  if (macroResult beginsWith '"') and (macroResult endsWith '"') {
01188             //      if html.getPref ("expandGlossaryItems", adrpagetable) {
01189             //          macroResult = string.mid (macroResult, 2, sizeOf (macroResult) - 2);
01190             //          macroResult = html.refGlossary (macroResult)}};
01191             
01192             if ((*h) [0] == '"' && (*h) [gethandlesize (h) - 1] == '"') {
01193                 
01194                 pullfromhandle (h, 0, 1, nil);
01195                 
01196                 popfromhandle (h, 1, nil);
01197                 
01198                 return (htmlrefglossary (pmi, h, perrorstring, hresult));
01199                 }
01200             }
01201         
01202         *hresult = h;
01203         }
01204     else
01205         if (stringlength (perrorstring) > 0 && ingoodthread ()) { //6.2b6 AR
01206             
01207             Handle herror;
01208 
01209             if (newtexthandle (perrorstring, &herror)) {
01210                 
01211                 htmlcleanforexport (herror);
01212                 
01213                 texthandletostring (herror, perrorstring);
01214 
01215                 disposehandle (herror);
01216                 }
01217             }
01218         else
01219             *hresult = nil; 
01220     
01221     return (fl);
01222     /*
01223         else {
01224             local (errorString = html.cleanForExport (tryError));
01225             if html.getPref ("logMacroErrors", adrpagetable) {
01226                 local (adroutline = @user.html.macroErrors);
01227                 if not defined (adroutline^) {
01228                     new (outlineType, adroutline)};
01229                 local (oldtarget = target.get ());
01230                 edit (adroutline);
01231                 target.set (adroutline);
01232                 op.firstSummit ();
01233                 local (adr = adrpagetable^.adrObject);
01234                 local (linetext = op.getLineText ());
01235                 local (message = "Error rendering \"" + adr + "\" at " + string.timeString ());
01236                 if linetext == "" {
01237                     op.setLineText (message)}
01238                 else {
01239                     op.insert (message, up)};
01240                 op.insert (errorString, right);
01241                 op.insert ("Macro string: {" + s + "}", down); // PBS: add the macro string
01242                 try {target.set (oldtarget)}};
01243             scriptError (errorString)}}
01244     */
01245     //return (frontTextScriptCall ('dosc', macro, hresult, errorstring));
01246     } /*htmlrunmacro*/
01247 
01248 
01249 static boolean htmlreportmacroerror (typrocessmacrosinfo *pmi, Handle macro, bigstring perrorstring) {
01250 #pragma unused (pmi)
01251 
01252     /*
01253     call back to the odb to report the error. consume the macro handle
01254     */
01255     
01256     tyvaluerecord val, vparams, vresult;
01257     
01258     setheapvalue (macro, stringvaluetype, &vparams);
01259     
01260     if (!coercetolist (&vparams, listvaluetype))
01261         return (false);
01262     
01263     if (!setstringvalue (perrorstring, &val))
01264         return (false);
01265         
01266     if (!langpushlistval (vparams.data.listvalue, nil, &val))
01267         return (false);
01268     
01269     return (langrunscript (BIGSTRING ("\x17" "html.data.logMacroError"), &vparams, nil, &vresult));
01270     } /*htmlreportmacroerror*/
01271 
01272 
01273 #if 0
01274 
01275 static boolean isPunctuationChar (char ch) {
01276     
01277     /*return (ispunct (ch));*/
01278     
01279     if ((ch >= '!') && (ch <= '/')) 
01280         return (true);
01281         
01282     if ((ch >= ':') && (ch <= '?'))
01283         return (true);
01284     
01285     return (false);
01286     } /*isPunctuationChar*/
01287 
01288 #endif
01289     
01290 
01291 static boolean isLegalURLPunctuationChar (char ch) {
01292     
01293     /*
01294     5.0a9 dmb: made this a function, stead of a huge or test in two places.
01295                also added '?', '=', '$' and ',' to the list
01296     
01297     5.0.2b16 dmb: added '&'
01298     
01299     6.1d1 ar: added '@'
01300 
01301     6.1b15 AR: Added '+'
01302     */
01303     
01304     switch (ch) {
01305         case '.':
01306         case ',':
01307         case '/':
01308         case '~':
01309         case '#':
01310         case '-':
01311         case ':':
01312         case '%':
01313         case '?':
01314         case '=':
01315         case '$':
01316         case '&':
01317         case '@':
01318         case '+':
01319             return (true);
01320         
01321         default:
01322             return (false);
01323         }
01324     } /*isLegalURLPunctuationChar*/
01325     
01326 
01327 static boolean isAlphaChar (char ch) {
01328     
01329     return (isalnum (ch) || (ch == '_'));
01330     } /*isAlphaChar*/
01331     
01332 
01333 static boolean processmacrosintags (typrocessmacrosinfo *pmi) {
01334 
01335     /*
01336     7.1b3 PBS: return true if we should process macros in HTML tags.
01337     If the pref isn't specified, the default is false,
01338     preserving current behavior.
01339     */
01340 
01341     hdlhashnode hnode;
01342     tyvaluerecord val;
01343     bigstring bsval;
01344     
01345     if (hashtablelookup ((*pmi).hpagetable, STR_P_flprocessmacrosintags, &val, &hnode)) { /*page table*/
01346 
01347         if (val.valuetype == booleanvaluetype)
01348 
01349             return (val.data.flvalue);
01350 
01351         else {
01352 
01353             coercetostring (&val);
01354 
01355             pullstringvalue (&val, bsval);
01356 
01357             if (equalidentifiers (bsval, bstrue))
01358 
01359                 return (true);
01360 
01361             else
01362 
01363                 return (false);
01364             } /*else*/
01365         } /*if*/
01366                 
01367     if (hashtablelookup ((*pmi).huserprefs, STR_P_flprocessmacrosintags, &val, &hnode)) { /*user.html.prefs*/
01368 
01369         if (val.valuetype == booleanvaluetype)
01370 
01371             return (val.data.flvalue);
01372 
01373         else {
01374 
01375             coercetostring (&val);
01376 
01377             pullstringvalue (&val, bsval);
01378 
01379             if (equalidentifiers (bsval, bstrue))
01380 
01381                 return (true);
01382 
01383             else
01384 
01385                 return (false);
01386             } /*else*/
01387         } /*if*/
01388 
01389     return (false);
01390     } /*processmacrosintags*/
01391 
01392 
01393 static boolean getmacrocharacters (typrocessmacrosinfo *pmi, bigstring bsstart, bigstring bsend) {
01394     
01395     /*
01396     7.1b1 PBS: get the user-specified macro characters. Return false if there are none, in which
01397     case default behavior will apply.
01398     */
01399     
01400     boolean flfoundone = false;
01401 
01402     if (!flvariablemacrocharacters)
01403         
01404         return (false);
01405 
01406     htmlgetstringpref (pmi, STR_P_macrostartchars, bsstart); /*get the start characters*/
01407 
01408     if (equalidentifiers (bsstart, bstrue)) /*not found*/
01409                 
01410             copystring (BIGSTRING ("\x01{"), bsstart);
01411 
01412     else
01413 
01414         flfoundone = true;
01415 
01416     htmlgetstringpref (pmi, STR_P_macroendchars, bsend); /*get the end characters*/
01417 
01418     if (equalidentifiers (bsend, bstrue)) /*not found*/
01419         
01420         copystring (BIGSTRING ("\x01}"), bsend);
01421         
01422     else
01423         
01424         flfoundone = true;
01425 
01426     return (flfoundone);    
01427     } /*getmacrocharacters*/
01428 
01429 
01430 static boolean processhtmltext (handlestream *s, typrocessmacrosinfo *pmi) {
01431 
01432     //boolean flactiveurls, boolean claycompatibity) {
01433     
01434     /*
01435     4.1b4 dmb: handled nested startmacrochars in macro text.
01436     
01437     4.1b5 dmb: don't translate «, », ©, ® or non-breaking space. They'll all get 
01438     iso8859 encoded when appropriate.
01439     
01440     4.1b5 dmb: for glassary references, we were adding the full length of the 
01441     item to 'i', but we need to so one less since the loop is about to increment it
01442     
01443     4.1b11 dmb: added flactiveurls, claycompatibity params. 
01444     also, allow '#', '-', ':' and '%' characters in urls
01445     also, runmacro and glossaryreference take handles, not bigstrings
01446 
01447     5.0.1 dmb: fixed bug handling garbage text around a @ character
01448     
01449     5.0.2b14 dmb: use typrocessmacrosinfo, suck down much more logic into kernel.
01450     
01451     5.1.6 dmb: chprev for macro expansion security fix
01452     
01453     6.1d1 ar: fixed bug handling URLs and email addresses > 255 chars, use handles
01454     instead of bigstrings.
01455 
01456     7.1b1 PBS: allow variable macro characters. Get them from the page table.
01457     If not present, use the default curly braces.
01458 
01459     7.1b3 PBS: allow, optionally, for macros to be processed inside HTML tags.
01460     Default is false, preserving current behavior.
01461 
01462     7.1b16 PBS: don't do glossary lookups or active URLs inside HTML tags.
01463     */
01464     
01465     #define ct ((*s).eof)
01466     #define i ((*s).pos)
01467     Handle h = (*s).data;
01468 //  unsigned long ct, i;
01469     char ch = chnul;
01470     char chprev;
01471     long remainingchars;
01472     boolean flcustommacrochars = false; /*true if allowed and in the page table*/
01473     bigstring bsmacrostartchars, bsmacroendchars; /*start and end characters for macros*/
01474     unsigned char firststartmacrochar = startmacrochar;
01475     unsigned char firstendmacrochar = endmacrochar;
01476     long lenstartmacro = 1, lenendmacro = 1;
01477     boolean flprocessmacrosinhtmltags = false;
01478     boolean flinhtmltag = false;
01479 
01480     flcustommacrochars = getmacrocharacters (pmi, bsmacrostartchars, bsmacroendchars); /*PBS 7.1b1: get custom chars*/
01481 
01482     if (flcustommacrochars) {
01483 
01484         firststartmacrochar = bsmacrostartchars [1];
01485 
01486         firstendmacrochar = bsmacroendchars [1];
01487 
01488         lenstartmacro = bsmacrostartchars [0];
01489 
01490         lenendmacro =  bsmacroendchars [0];
01491         } /*if*/
01492 
01493     flprocessmacrosinhtmltags = processmacrosintags (pmi); /*PBS 7.1b3: true if we should process macros in HTML tags.*/
01494     
01495     if (fldebugwebsite) {
01496         
01497         setstringlength (bsdebug, 25); 
01498         
01499         memset(&bsdebug [1], ' ', 25);
01500         }
01501     
01502     for (i = 0; i < ct; i++) {
01503         
01504         chprev = ch;
01505         
01506         ch = (*h) [i];
01507 
01508         if (ch == '<')
01509             flinhtmltag = true;
01510 
01511         if (ch == '>')
01512             flinhtmltag = false;
01513 
01514         if (!flprocessmacrosinhtmltags)
01515             flinhtmltag = false;
01516         
01517         if (fldebugwebsite) { /*makes it easy to see where the process failed after the fact*/
01518             
01519             moveleft (&bsdebug [2], &bsdebug [1], 24);
01520             
01521             bsdebug [25] = ch;
01522             }
01523         
01524         remainingchars = ct - i - 1;
01525 
01526         if (ch == firststartmacrochar) /*7.1b1: variable macros support*/
01527             ch = startmacrochar;
01528         
01529         switch (ch) {
01530             
01531             case '\\': { /*delete the backslash, skip over the char after it*/
01532 
01533                 if (flinhtmltag)
01534                     break;
01535                 
01536                 pullfromhandlestream (s, 1, nil);
01537                 
01538                 i++; /*skip over next character*/
01539                 
01540                 ch = (*h) [i]; //note it for secure macro expansion
01541                 
01542                 break;
01543                 }
01544             
01545             case '-': { /*support AutoWeb convention, three dashes is a separator line*/
01546 
01547                 if (flinhtmltag)
01548                     break;
01549                 
01550                 if ((*pmi).flclaycompatibility && remainingchars > 3) {  /*4.1b11 dmb*/
01551                     
01552                     if (((*h) [i + 1] == '-') && ((*h) [i + 2] == '-') && ((*h) [i + 3] == '\r')) {
01553                         
01554                         if (!mergehandlestreamstring (s, 3, str_separatorline))
01555                             return (false);
01556                         }
01557                     }
01558                 
01559                 break;
01560                 }
01561             
01562             case '*': { /*support AutoWeb convention, three asterisks emboldens the rest of the line*/
01563 
01564                 if (flinhtmltag)
01565                     break;
01566                 
01567                 if ((*pmi).flclaycompatibility && remainingchars > 3) {  /*4.1b11 dmb - claycompatibity*/
01568                     
01569                     if (((*h) [i + 1] == '*') && ((*h) [i + 2] == '*')) {
01570                         
01571                         long j;
01572                         
01573                         mergehandlestreamstring (s, 3, str_startbold);
01574                         
01575                         for (j = i; j < ct; j++) {
01576                             
01577                             if ((*h) [j] == '\r') {
01578                                 
01579                                 long savepos = i;
01580                                 
01581                                 i = j;
01582                                 
01583                                 if (!mergehandlestreamstring (s, 0, str_endbold))
01584                                     return (false);
01585                                 
01586                                 i = savepos;
01587                                 
01588                                 break;
01589                                 }
01590                             } /*for*/
01591 
01592                         --i;
01593                         }
01594                     }
01595                     
01596                 break;
01597                 }
01598                 
01599         case '<': 
01600         case startmacrochar: { /*process an HTML tag or inline macro*/
01601 
01602                 boolean flismacro = false;
01603                 unsigned long startmacro, endmacro;
01604                 long j, k;
01605                 unsigned short lenmacrooverhead;
01606                 boolean flprocessed = false;
01607                 boolean flfoundvariablemacro = false;
01608                 
01609                 if ((!flcurlybracemacros) && (ch == startmacrochar)) /*DW 11/4/95*/
01610                     continue;
01611                 
01612                 if (chprev == '\\') //5.1.6 security fix
01613                     continue;
01614 
01615                 #if flvariablemacrocharacters
01616 
01617                     if (((*h) [i] == firststartmacrochar) && (flcustommacrochars)) {
01618 
01619                         if (remainingchars > lenstartmacro) {
01620 
01621                             flismacro = true;
01622 
01623                             for (j = 1; j < lenstartmacro; j++) {
01624 
01625                                 if ((*h) [i + j] != bsmacrostartchars [j + 1]) {
01626 
01627                                     flismacro = false;
01628 
01629                                     break;
01630                                     } /*if*/
01631                                 } /*for*/
01632 
01633                             if (flismacro) { /*find end of macro*/
01634 
01635                                 flismacro = false; /*will be set true if we find the end of the macro*/
01636 
01637                                 startmacro = i + lenstartmacro;
01638 
01639                                 for (j = 1; j < (i + remainingchars); j++) {
01640                 
01641                                     if ((*h) [j + startmacro] == firstendmacrochar) {
01642 
01643                                         flfoundvariablemacro = true;
01644 
01645                                         for (k = 1; k < lenendmacro; k++) {
01646 
01647                                             if ((*h) [j + startmacro + k] != bsmacroendchars [k + 1]) {
01648 
01649                                                 flfoundvariablemacro = false;
01650 
01651                                                 break;
01652                                                 } /*if*/
01653                                             } /*for*/
01654 
01655                                         if (flfoundvariablemacro) {
01656 
01657                                             flismacro = true;
01658 
01659                                             endmacro = j + startmacro - 1;
01660 
01661                                             lenmacrooverhead = lenstartmacro + lenendmacro;
01662 
01663                                             goto processmacro;
01664                                             }
01665                                         } /*if*/
01666                                     } /*for*/
01667                                 } /*if*/
01668                             } /*if*/
01669                         } /*if*/
01670 
01671                 #endif
01672                 
01673                 #ifdef acceptnoncurlymacros /*DW 11/17/95*/
01674                 
01675                     if (ch == '<') {
01676                         
01677                         if (remainingchars > 3) {
01678                         
01679                             if (((*h) [i + 1] == '!') && ((*h) [i + 2] == '-') && ((*h) [i + 3] == '-') && ((*h) [i + 4] == '#')) {
01680                             
01681                                 for (j = i; j < (ct - 2); j++) {
01682                                 
01683                                     if (((*h) [j] == '-') && ((*h) [j + 1] == '-') && ((*h) [j + 2] == '>')) {
01684                                         
01685                                         flismacro = true;
01686                                         
01687                                         startmacro = i + 5;
01688                                         
01689                                         endmacro = j - 1;
01690                                         
01691                                         lenmacrooverhead = 5 + 3;
01692                                         
01693                                         goto processmacro;
01694                                         }
01695                                     }
01696                                 }
01697                             }
01698                         }
01699                 #endif
01700                 
01701                 if ((ch == startmacrochar) && (!flcustommacrochars)) {
01702                     
01703                     if (remainingchars > 0) {
01704                         
01705                         short ctendsneeded = 1;
01706                         
01707                         for (j = i + 1; j < ct; j++) {
01708                             
01709                             char lch = (*h) [j];
01710                             
01711                             if (lch == endmacrochar) {
01712                             
01713                                 if (--ctendsneeded == 0) {
01714                             
01715                                     flismacro = true;
01716                                     
01717                                     startmacro = i + 1;
01718                                     
01719                                     endmacro = j - 1;
01720                                     
01721                                     lenmacrooverhead = 1 + 1;
01722                                     
01723                                     goto processmacro;
01724                                     }
01725                                 }
01726                             else {
01727                             
01728                                 if (lch == startmacrochar)
01729                                     ++ctendsneeded;
01730                                 }
01731                             }
01732                         }
01733                     }
01734             
01735                 goto notmacro;
01736                 
01737                 processmacro: {
01738                     long lenmacro;
01739                     
01740                     lenmacro = endmacro - startmacro + 1;
01741                     
01742                     if (!(*pmi).flprocessmacros) {
01743 
01744                         i += lenmacro + lenmacrooverhead;
01745                         }
01746                     else {
01747                                 
01748                         bigstring lerrorstring;
01749                         Handle macro;
01750                         Handle hmacroresult;
01751 
01752                         if (!newhandle (lenmacro, &macro))
01753                             return (false);
01754                         
01755                         moveleft (&(*h) [startmacro], *macro, lenmacro);
01756     
01757                         if (flpagemillfile) {
01758                             
01759                             long ix;
01760                             
01761                             for (ix = 1; ix <= lenmacro; ix++) {
01762                                 
01763                                 if ((*macro) [ix] == '\r')
01764                                     (*macro) [ix] = ' ';
01765                                 } /*for*/
01766                             }
01767 
01768                         if (!htmlrunmacro (pmi, macro, lerrorstring, &hmacroresult)) {
01769                             
01770                             bigstring bs;
01771                             boolean fllogerrors;
01772                             
01773                             if (hmacroresult == nil) /*6.2b6 AR: Assume that our thread has been killed and unwind recursion*/
01774                                 return (false);
01775                             
01776                             if (htmlgetbooleanpref (pmi, BIGSTRING ("\x0e" "logMacroErrors"), &fllogerrors) && fllogerrors) {
01777                             
01778                                 if (!newhandle (lenmacro, &macro))
01779                                     return (false);
01780                                 
01781                                 moveleft (&(*h) [startmacro], *macro, lenmacro);
01782                                 
01783                                 htmlreportmacroerror (pmi, macro, lerrorstring);
01784                                 }
01785                             
01786                             parsedialogstring (str_macroerror, lerrorstring, nil, nil, nil, bs);
01787                             
01788                             if (!newtexthandle (bs, &hmacroresult))
01789                                 return (false);
01790                             }
01791                         
01792                         if (!mergehandlestreamhandle (s, lenmacro + lenmacrooverhead, hmacroresult))
01793                             return (false);
01794                         }
01795                     
01796                     i--; /*DW 11/4/95*/
01797                     
01798                     flprocessed = true;
01799                     }
01800                     
01801                 notmacro:
01802                 
01803                 if (flprocessmacrosinhtmltags) /*7.1b3 PBS: allow macros in HTML tags to be processed. Don't skip ahead.*/
01804 
01805                     break;
01806 
01807                 if (!flprocessed) {
01808                 
01809                     for (j = i; j < ct; j++) {
01810                         
01811                         if ((*h) [j] == '>') {
01812                             
01813                             i = j;
01814                                 
01815                             break;
01816                             }
01817                         } /*for*/
01818                     }
01819                 
01820                 break;
01821                 }
01822                 
01823             case '@': { /*process email addresses*/
01824                 
01825                 long startaddress, endaddress;
01826                 long j; /*must be signed! -- loops need to go negative to terminate*/
01827                 
01828                 if (flinhtmltag)
01829                     break;
01830 
01831                 if (!(*pmi).flactiveurls)  /*4.1b11 dmb*/
01832                     break;
01833                 
01834                 startaddress = 0; /*we might be at the beginning of the text handle*/
01835                 
01836                 for (j = i - 1; j >= 0; j--) { /*scan to the left, looking for the start of the mail address*/
01837                     
01838                     char lch = (*h) [j];
01839                     
01840                     if (!isAlphaChar (lch) && !isLegalURLPunctuationChar (lch)) {
01841 
01842                         startaddress = j + 1;
01843                         
01844                         break;
01845                         }
01846                     } /*for*/
01847                 
01848                 if (startaddress == i) /*4.1b11 dmb*/
01849                     break;
01850                 
01851                 endaddress = ct; /*we might be at the end of the text handle*/
01852                 
01853                 for (j = i + 1; j < ct; j++) { /*scan to the right, looking for the end of the mail address*/
01854                 
01855                     char lch = (*h) [j];
01856                     
01857                     if (!isAlphaChar (lch) && !isLegalURLPunctuationChar (lch)) {
01858 
01859                         endaddress = j;
01860                         
01861                         break;
01862                         }
01863                     } /*for*/
01864                 
01865                 for (j = endaddress - 1; j > i; j--) { /*move endaddress to the left, past any non-alpha chars, like a comma after the mail address -- common!*/
01866                     
01867                     if (isAlphaChar ((*h) [j])) {
01868                         
01869                         endaddress = j;
01870                         
01871                         break;
01872                         }
01873                     } /*for*/
01874                 
01875                 if (endaddress - startaddress < 3) /*4.1b11 dmb*/
01876                     break;
01877                 
01878                 /*6.1d1 AR: replace the text with a hot email address*/ {
01879 
01880                     Handle hlink = nil;
01881                     Handle hmail = nil;
01882                     long len = endaddress - startaddress + 1;
01883                     long ixload = startaddress;
01884                     boolean fl;
01885                     
01886                     fl = newtexthandle (str_mailto, &hlink);
01887 
01888                     fl = fl && loadfromhandletohandle (h, &ixload, len, true, &hmail);
01889                     
01890                     fl = fl && parsedialoghandle (hlink, hmail, nil, nil, nil);
01891                     
01892                     disposehandle (hmail);
01893 
01894                     if (!fl) {
01895 
01896                         disposehandle (hlink);
01897 
01898                         return (false);
01899                         }
01900 
01901                     i = startaddress;
01902                     
01903                     if (!mergehandlestreamhandle (s, len, hlink)) /*consumes hlink*/
01904                         return (false);
01905                     
01906                     i--;
01907                     }
01908                     
01909                 break;
01910                 }
01911                 
01912             case '/': { /*process URL references, as in http://www.userland.com*/
01913             
01914                 unsigned long startaddress, endaddress;
01915                 long j; /*must be signed! -- loops need to go negative to terminate*/
01916 
01917                 if (flinhtmltag)
01918                     break;
01919                 
01920                 if (!(*pmi).flactiveurls)  /*4.1b11 dmb*/
01921                     continue;
01922                 
01923                 if (remainingchars == 0)
01924                     continue;
01925                     
01926                 if ((*h) [i + 1] != '/') 
01927                     continue;
01928                     
01929                 startaddress = 0; /*we might be at the beginning of the text handle*/
01930                 
01931                 for (j = i - 1; j >= 0; j--) { /*scan to the left, looking for the start of the URL*/
01932                     
01933                     char lch = (*h) [j];
01934                     
01935                     if (isLegalURLPunctuationChar (lch))
01936                         continue;
01937                 
01938                     if (!isAlphaChar (lch)) {
01939                         
01940                         startaddress = j + 1;
01941                         
01942                         break;
01943                         }
01944                     } /*for*/
01945                     
01946                 endaddress = ct; /*we might be at the end of the text handle*/ /*4.1b12 dmb: removed -1 bug*/
01947                 
01948                 for (j = i + 1; j < ct; j++) { /*scan to the right, looking for the end of the URL*/
01949                 
01950                     char lch = (*h) [j];
01951                     
01952                     if (isLegalURLPunctuationChar (lch))
01953                         continue;
01954                     
01955                     if (!isAlphaChar (lch)) {
01956                         
01957                         endaddress = j;
01958                         
01959                         break;
01960                         } /*for*/
01961                     } /*for*/
01962                     
01963                 for (j = endaddress - 1; j > i; j--) { /*move endaddress to the left, past any non-alpha chars, like a comma after the mail address -- common!*/
01964                     
01965                     char lch = (*h) [j];
01966                     
01967                     if (isAlphaChar (lch) || (lch == '/')) {
01968                         
01969                         endaddress = j;
01970                         
01971                         break;
01972                         }
01973                     } /*for*/
01974                                     
01975                 /*6.1d1 AR: replace the text with a hot URL link*/ {
01976 
01977                     Handle hlink = nil;
01978                     Handle hurl = nil;
01979                     long ixload = startaddress;
01980                     long len = endaddress - startaddress + 1;
01981                     boolean fl;
01982 
01983                     fl = newtexthandle (str_hotlink, &hlink);
01984 
01985                     fl = fl && loadfromhandletohandle (h, &ixload, len, true, &hurl);
01986 
01987                     fl = fl && parsedialoghandle (hlink, hurl, hurl, nil, nil);
01988                     
01989                     disposehandle (hurl);
01990 
01991                     if (!fl) {
01992 
01993                         disposehandle (hlink);
01994 
01995                         return (false);
01996                         }
01997 
01998                     i = startaddress;
01999                     
02000                     if (!mergehandlestreamhandle (s, len, hlink)) /*consumes hlink*/
02001                         return (false);
02002                     
02003                     i--;
02004                     }
02005                 
02006                 break;
02007                 }
02008                 
02009             case '"': { /*possibly process a glossary entry*/
02010                 long j;
02011                 boolean flrewind = false;
02012                 long ixrewind = 0;
02013                 
02014                 if (flinhtmltag)
02015                     break;
02016 
02017                 for (j = i + 1; j < ct; j++) {
02018                     
02019                     char lch = (*h) [j];
02020                     
02021                     if (flpagemillfile) {
02022                     
02023                         if (lch == '\r') { /*special hack for PageMill files*/
02024                             
02025                             (*h) [j] = lch = ' ';
02026                             }
02027                         }
02028                     else {
02029                         if (lch == '\r') /*unterminated quote*/
02030                             break;
02031                         }
02032                     
02033                     if (lch == '<') { /*7.0b39 PBS: rewind to this character if gloss lookup fails.*/
02034 
02035                         if (!flrewind) {
02036 
02037                             flrewind = true;
02038 
02039                             ixrewind = j;
02040                             } /*if*/
02041                         } /*if*/
02042 
02043 
02044                     if (lch == '"') {
02045                         
02046                         unsigned long startterm, endterm, lenterm;
02047                         Handle hterm;
02048                         bigstring lerrorstring;
02049                         Handle hresult;
02050                         long ixload;
02051                         
02052                         startterm = i + 1;
02053                         
02054                         endterm = j - 1;
02055                         
02056                         lenterm = endterm - startterm + 1;
02057                         
02058                         if (lenterm > maxglossarynamelength || !(*pmi).flexpandglossaryitems) {
02059                             
02060                             i += lenterm + 2 - 1; /*4.1b5: skip over the quoted string, less the last char*/
02061 
02062                             if (flrewind) /*7.0b39 PBS: go back to < character.*/
02063 
02064                                 i = ixrewind - 1;
02065                             
02066                             break;
02067                             }
02068                                                 
02069                         ixload = startterm;
02070 
02071                         if (!loadfromhandletohandle (h, &ixload, lenterm, true, &hterm))
02072                             return (false);
02073 
02074                         if (!htmlrefglossary (pmi, hterm, lerrorstring, &hresult)) { /*error -- we don't substitute*/ 
02075                             
02076                             i += lenterm + 2 - 1; 
02078                             if (flrewind) /*7.0b39 PBS: go back to < character.*/
02079 
02080                                 i = ixrewind - 1;
02081                             
02082                             break;
02083                             }
02084 
02085                         /*the term *was* in the glossary, perform the substitution*/
02086                         
02087                         if (!mergehandlestreamhandle (s, lenterm + 2, hresult)) /*consumes hresult*/
02088                             return (false);
02089                         
02090                         --i; /*4.1b5: skip over the expanded glossary item, less the last char*/
02091                         
02092                         break;
02093                         }
02094                     } /*for*/
02095                 
02096                 break;
02097                 }
02098                 
02099             /* 4.1b12 dmb: these don't have html charactors equivalents, but we can do the mapping in the open-architecture isofilter */
02100             /*
02101             case '’': case '‘':
02102                 (*h) [i] = '\'';
02103                 
02104                 break;
02105                 
02106             case '”': case '“':
02107                 (*h) [i] = '"';
02108                 
02109                 break;
02110                 
02111             case '•': 
02112                 (*h) [i] = 'o';
02113                 
02114                 break;
02115                 
02116             case '…':
02117                 (*h) [i] = '.';
02118                 
02119                 if (!insertstringinhandle ("\p..", h, i + 1))
02120                     return (false);
02121                     
02122                 ct = gethandlesize (h);
02123                 
02124                 i += 2;
02125                 
02126                 break;
02127                 
02128             case '–': case '—':
02129                 (*h) [i] = '-';
02130                 
02131                 if (!insertstringinhandle ("\p-", h, i + 1))
02132                     return (false);
02133                     
02134                 ct = gethandlesize (h);
02135                 
02136                 i += 1;
02137                 
02138                 break;
02139             */
02140             /* 4.1b5 dmb: all of the following are html charactors and have iso8859 mappings */
02141             /*
02142             case ' ': // a funny space that Word produces
02143                 (*h) [i] = ' ';
02144                 
02145                 break;
02146                 
02147             case '«':
02148                 (*h) [i] = '&';
02149                 
02150                 if (!insertstringinhandle ("\plt;&lt;", h, i + 1))
02151                     return (false);
02152                     
02153                 ct = gethandlesize (h);
02154                 
02155                 i += 7;
02156                 
02157                 break;
02158                 
02159             case '»':
02160                 (*h) [i] = '&';
02161                 
02162                 if (!insertstringinhandle ("\pgt;&gt;", h, i + 1))
02163                     return (false);
02164                     
02165                 ct = gethandlesize (h);
02166                 
02167                 i += 7;
02168                 
02169                 break;
02170                 
02171             case '©':
02172                 (*h) [i] = '&';
02173                 
02174                 if (!insertstringinhandle ("\pcopy;", h, i + 1))
02175                     return (false);
02176                     
02177                 ct = gethandlesize (h);
02178                 
02179                 i += 5;
02180                 
02181                 break;
02182                 
02183             case '®':
02184                 (*h) [i] = '&';
02185                 
02186                 if (!insertstringinhandle ("\preg;", h, i + 1))
02187                     return (false);
02188                     
02189                 ct = gethandlesize (h);
02190                 
02191                 i += 4;
02192                 
02193                 break;
02194             */
02195 
02196             } /*switch*/
02197         } /*for*/
02198     
02199     return (true);
02200     
02201     #undef ct
02202     #undef i
02203     } /*processhtmltext*/
02204 
02205 
02206 static boolean autoparagraphs (handlestream *s) {
02207     
02208     //unsigned long ct, i,
02209     #define ct ((*s).eof)
02210     #define i ((*s).pos)
02211     Handle h = (*s).data;
02212     unsigned long remainingchars;
02213     char ch;
02214     boolean flinsidetag = false;
02215     
02216     //ct = gethandlesize (h);
02217     
02218     for (i = 0; i < ct; i++) {
02219     
02220         ch = (*h) [i];
02221         
02222         remainingchars = ct - i - 1;
02223         
02224         switch (ch) {
02225             
02226             case '<':
02227                 flinsidetag = true;
02228                 
02229                 break;
02230                 
02231             case '>':
02232                 flinsidetag = false;
02233                 
02234                 break;
02235         
02236             case '\r': { /*insert a <p /> in front of a double-return*/
02237                 
02238                 if (!flinsidetag) {
02239             
02240                     if (remainingchars > 0) { /*not the last char in the buffer*/
02241                         
02242                         char chnext = (*h) [i + 1];
02243                         
02244                         if (chnext == chreturn) { /*two returns, insert a pagebreak string*/
02245                             
02246                         //  if (!insertstringinhandle (pagebreakstring, h, i))
02247                         //      return (false);
02248                         //  
02249                         //  ct = gethandlesize (h);
02250                         //
02251                         //  i += stringlength (pagebreakstring) + 1; /*point past the 2nd return*/
02252                         
02253                             if (!mergehandlestreamstring (s, 0, str_pagebreak))
02254                                 return (false);
02255                             
02256                             ++i; //point past the 2nd return
02257                             
02258                             continue;
02259                             }                       
02260                         }
02261                     }
02262                 
02263                 break;
02264                 }
02265             } /*switch*/
02266         } /*for*/
02267     
02268     return (true);
02269     
02270     #undef ct
02271     #undef i
02272     } /*autoparagraphs*/
02273 
02274 
02275 #ifdef version5orgreater
02276 
02277 static boolean iso8859encode (handlestream *s, hdlhashtable hiso8859usermap) {
02278 
02279     /*
02280     4.1b4 dmb: use user preferences for mapping, if table exists.
02281                for extra speed, don't even try to mapp characters < ascii 128
02282     
02283     4.1b13 dmb: fixed heap-trashing off-by-one error in text replacement code
02284     
02285     5.0.2b14 dmb: take a handlestream, not a Handle
02286     
02287     5.0.2b16 dmb: backup after replacement, so we don't miss next char
02288     */
02289     
02290     #define lentext ((*s).eof)
02291     #define ixtext ((*s).pos)
02292     Handle htext = (*s).data;
02293     unsigned char ch;
02294     Str255 bs;
02295     tyvaluerecord val;
02296     hdlhashnode hnode;
02297 
02298     
02299     #ifndef version5orgreater
02300         hdlhashtable hiso8859usermap = nil;
02301         
02302         if (getsystemtablescript (iduseriso8859map, bs)) {
02303             
02304             pushstring (BIGSTRING ("\x08" ".[\"128\"]"), bs);   /*refer to 1st entry in table*/
02305             
02306             disablelangerror ();
02307             
02308             if (langexpandtodotparams (bs, &hiso8859usermap, bs))
02309                 ; /*map being non-nil is our flag*/
02310             
02311             enablelangerror ();
02312             }
02313     #endif
02314     
02315     if (hiso8859usermap != nil)
02316         pushhashtable (hiso8859usermap);
02317     
02318     for (ixtext = 0; ixtext < lentext; ++ixtext) {
02319 
02320         ch = (*htext) [ixtext];
02321 
02322         if (ch <= 127)
02323             continue;
02324         
02325         if (hiso8859usermap != nil) {
02326             
02327             numbertostring ((long) ch, bs);
02328             
02329             if (!hashlookup (bs, &val, &hnode) || val.valuetype != stringvaluetype)
02330                 continue;
02331             
02332             pullstringvalue (&val, bs);
02333             }
02334         else {
02335         
02336             if (iso8859table [ch] == 0) /*normal char, doesn't need processing*/
02337                 continue;
02338 
02339             /*encode the character*/
02340             
02341             copyctopstring ((char *)(iso8859table [ch]), bs);   // 3/26/97 dmb: it's a c string now
02342             }
02343         
02344         if (!mergehandlestreamstring (s, 1, bs))
02345             return (false);
02346         
02347         --ixtext; // back up, so we can move ahead
02348         } /*while*/
02349     
02350     if (hiso8859usermap != nil)
02351         pophashtable ();
02352     
02353     return (true);
02354     
02355     #undef lentext
02356     #undef ixtext
02357     } /*iso8859encode*/
02358 
02359 
02360 boolean processhtmlmacrosverb (hdltreenode hp1, tyvaluerecord *vreturned) {
02361     
02362     /*
02363     5.0.2b13 dmb: create handlestream here, and use for both processtext and autoparagraphs
02364     
02365     5.0.2b14 dmb: suck the whole damn thing into the kernel, changing our API
02366     
02367         on processMacros (s, plainprocessing = false) {
02368 
02369     6.1b17 AR: We were doing a lot of superfluous work to determine the page table,
02370                 some of it even causing crashes. Calling getoptionalpagetableavalue
02371                 is enough.  
02372     */
02373     
02374     typrocessmacrosinfo pageinfo;
02375     boolean flplainprocessing = false;
02376     Handle htext = nil;
02377     handlestream s;
02378     
02379     // get all of the prefs and tables that we need
02380     
02381     clearbytes (&pageinfo, sizeof (pageinfo));
02382 /*  
02383     if (!htmlgetpagetable (&pageinfo.hpagetable) && !htmlgetdefaultpagetable (&pageinfo.hpagetable))
02384         return (false);
02385 */  
02386     if (!htmlgetprefstable (&pageinfo.huserprefs))
02387         return (false);
02388     
02389     if (langgetparamcount (hp1) > 1) {
02390         
02391         if (!getbooleanvalue (hp1, 2, &flplainprocessing))
02392             return (false);
02393         }
02394 /*  
02395     if (langgetparamcount (hp1) > 2) {
02396         
02397         flnextparamislast = true;
02398         
02399         if (!gettablevalue (hp1, 3, &pageinfo.hpagetable))
02400             return (false);
02401         }
02402 */  
02403     if (!getoptionalpagetablevalue (hp1, 3, &pageinfo.hpagetable))
02404         return (false);
02405     
02406     if (flplainprocessing) {
02407     
02408         pageinfo.flautoparagraphs = false;
02409         
02410         pageinfo.flclaycompatibility = false;
02411         
02412         pageinfo.flactiveurls = false;
02413         }
02414     else {
02415         if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x0e" "autoParagraphs"), &pageinfo.flautoparagraphs))
02416             return (false);
02417         
02418         if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x0a" "activeURLs"), &pageinfo.flactiveurls))
02419             return (false);
02420         
02421         if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x11" "clayCompatibility"), &pageinfo.flclaycompatibility))
02422             return (false);
02423         }
02424     
02425     if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x0d" "processMacros"), &pageinfo.flprocessmacros))
02426         return (false);
02427     
02428     if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x13" "expandGlossaryItems"), &pageinfo.flexpandglossaryitems))
02429         return (false);
02430     
02431     if (!htmlgetbooleanpref (&pageinfo, BIGSTRING ("\x09" "isoFilter"), &pageinfo.flisofilter))
02432         return (false);
02433     
02434     // get the text and process it!
02435     
02436     if (!getexempttextvalue (hp1, 1, &htext))
02437         return (false);
02438     
02439     openhandlestream (htext, &s);
02440     
02441     if (!processhtmltext (&s, &pageinfo))
02442         goto error;
02443     
02444     htmldisposemacrocontext (&pageinfo);
02445 
02446     if (pageinfo.flautoparagraphs) {
02447         
02448         if (!autoparagraphs (&s))
02449             goto error;
02450         }
02451     
02452     if (pageinfo.flisofilter) {
02453         
02454         hdlhashtable hisomap;
02455         
02456         if (!langfastaddresstotable (builtinstable, str_iso8859map, &hisomap))
02457             goto error;
02458         
02459         if (!iso8859encode (&s, hisomap))
02460             goto error;
02461         }
02462     
02463     closehandlestream (&s);
02464     
02465     return (setheapvalue (htext, stringvaluetype, vreturned));
02466     
02467     error: {
02468         
02469         htmldisposemacrocontext (&pageinfo);
02470         
02471         disposehandle (htext);
02472         
02473         return (false);
02474         }
02475     } /*processhtmlmacrosverb*/
02476 
02477 #else
02478 
02479 boolean processhtmlmacrosverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02480     
02481     /*
02482     5.0.2b13 dmb: create handlestream here, and use for both processtext and autoparagraphs
02483     */
02484     
02485     Handle htext = nil;
02486     boolean flautoparagraphs, flactiveurls, claycompatibity;
02487     tyvaluerecord callbackval;
02488     tyaddress callback;
02489     ptraddress savecallback;
02490     handlestream s;
02491     
02492     if (!getbooleanvalue (hparam1, 2, &flautoparagraphs))
02493         return (false);
02494     
02495     if (!getbooleanvalue (hparam1, 3, &flactiveurls))
02496         return (false);
02497     
02498     if (!getbooleanvalue (hparam1, 4, &claycompatibity))
02499         return (false);
02500     
02501     flnextparamislast = true;
02502     
02503     #if version42orgreater
02504     
02505     if (!getaddressparam (hparam1, 5, &callbackval))
02506         return (false);
02507     
02508     if (!getaddressvalue (callbackval, &callback.ht, callback.bs))
02509         return (false);
02510     
02511     savecallback = callbackscript;
02512     
02513     callbackscript = &callback;
02514     
02515     #else
02516     
02517     if (!getbinaryvalue (hparam1, 5, true, &osaval.data.binaryvalue))
02518         return (false);
02519     
02520     #endif
02521     
02522     if (!getexempttextvalue (hparam1, 1, &htext))
02523         goto error;
02524     
02525     openhandlestream (htext, &s);
02526     
02527     if (!processtext (&s, flactiveurls, claycompatibity))
02528         goto error;
02529         
02530     if (flautoparagraphs) {
02531         
02532         if (!autoparagraphs (&s))
02533             goto error;
02534         }
02535     
02536     closehandlestream (&s);
02537     
02538     callbackscript = savecallback;
02539     
02540     return (setheapvalue (htext, stringvaluetype, vreturned));
02541     
02542     error: {
02543         
02544         callbackscript = savecallback;
02545         
02546         disposehandle (htext);
02547         
02548         return (false);
02549         }
02550     } /*processhtmlmacrosverb*/
02551 
02552 #endif
02553 
02554 #ifdef MACVERSION
02555 #pragma mark === stringOps ucmd ===
02556 #endif
02557 
02558 static unsigned char hexchartonum (unsigned char ch) {
02559 
02560     if ((ch >= 'a') && (ch <= 'z')) /*DW 10/13/95 -- fix for toys.parseArgs*/
02561         ch -= 32;
02562 
02563     if ((ch >= '0') && (ch <= '9'))
02564         ch = ch - '0';
02565     else
02566         ch = (ch - 'A') + 10;
02567     
02568     return (ch);
02569     } /*hexchartonum*/
02570 
02571 
02572 static unsigned char numtohexchar (unsigned char ch) {
02573 
02574     if (ch < 10)
02575         ch = ch + '0';
02576     else
02577         ch = (ch - 10) + 'A';
02578     
02579     return (ch);
02580     } /*numtohexchar*/
02581 
02582 
02583 static void decodehandle (Handle htext) {
02584 
02585     /*
02586     5.1.5b12 dmb: make sure there are two characters following %
02587     */
02588     
02589     unsigned long ixtext, lentext;
02590     unsigned char *p;
02591 
02592     p = (unsigned char *) *htext;
02593 
02594     lentext = gethandlesize (htext);
02595 
02596     ixtext = 0;
02597 
02598     while (true) {
02599 
02600         if (ixtext >= lentext)
02601             break;
02602 
02603         switch (*p) {
02604 
02605             case '%':
02606                 if (lentext - ixtext > 2) { // there are two characters following %
02607                     
02608                     unsigned char ch1 = *(p + 1), ch2 = *(p + 2);
02609 
02610                     //{Str255 s; s [0] = 3; s [1] = '%'; s [2] = ch1; s [3] = ch2; DebugStr (s);}
02611 
02612                     *p = (hexchartonum (ch1) * 16) + hexchartonum (ch2);
02613                     
02614                     moveleft (p + 3, p + 1, lentext - ixtext - 3);
02615 
02616                     lentext -= 2;
02617                     }
02618 
02619                 break;
02620 
02621             case '+':
02622                 *p = ' ';
02623 
02624                 break;
02625             } /*switch*/
02626 
02627         p++;
02628 
02629         ixtext++;
02630         } /*while*/
02631 
02632     sethandlesize (htext, lentext);
02633     } /*decodehandle*/
02634 
02635 
02636 boolean urldecodeverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02637 
02638     Handle htext;
02639     
02640     flnextparamislast = true;
02641     
02642     if (!getexempttextvalue (hparam1, 1, &htext))
02643         return (false);
02644     
02645     decodehandle (htext);
02646     
02647     return (setheapvalue (htext, stringvaluetype, vreturned));
02648     } /*urldecodeverb*/
02649 
02650 
02651 static void encodehandle (Handle htext) {
02652     
02653     /*
02654     4.1b5 dmb: fixed moveleft data length calculation so we don't overwrite memory
02655     
02656     5.0d12 dmb: gotta encode % too.
02657     */
02658     
02659     unsigned long ixtext;
02660     unsigned long lentext;
02661     unsigned char ch;
02662 
02663     ixtext = 0;
02664 
02665     lentext = gethandlesize (htext);
02666 
02667     while (true) {
02668 
02669         if (ixtext >= lentext)
02670             break;
02671 
02672         ch = (*htext) [ixtext];
02673 
02674         if ((ch < 33) || (ch > 126))
02675             goto hexpush;
02676 
02677         switch (ch) {
02678 
02679             case ' ': case '$': case '&': case '=': case '+': case '"': 
02680             case '\\': case '\'': case ':': case '/': case '#': case '?':
02681             case '%':
02682                 goto hexpush;
02683 
02684             } /*switch*/
02685 
02686         /*normal character, doesn't need encoding*/
02687 
02688         ixtext++;
02689 
02690         continue;
02691 
02692         hexpush: /*encode the character*/
02693 
02694         sethandlesize (htext, lentext + 2);
02695 
02696         moveright (*htext + ixtext + 1, *htext + ixtext + 3, lentext - ixtext - 1); /*4.1b5: added -1*/
02697 
02698         (*htext) [ixtext++] = '%';
02699 
02700         (*htext) [ixtext++] = numtohexchar (ch / 16);
02701 
02702         (*htext) [ixtext++] = numtohexchar (ch % 16);
02703 
02704         lentext += 2;
02705         } /*while*/
02706     } /*encodehandle*/
02707 
02708 
02709 boolean urlencodeverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02710 
02711     Handle htext;
02712     
02713     flnextparamislast = true;
02714     
02715     if (!getexempttextvalue (hparam1, 1, &htext))
02716         return (false);
02717     
02718     encodehandle (htext);
02719     
02720     return (setheapvalue (htext, stringvaluetype, vreturned));
02721     } /*urlencodeverb*/
02722 
02723 
02724 static boolean nthfieldhandle (Handle htext, byte fielddelim, long fieldnum, Handle *hreturnedtext) {
02725 
02726     /*
02727     6.1b18 AR: Use textnthword which is proven to work. The previous version of this function
02728     was buggy, e.g. it didn't work correctly if the first char was a field delimiter.
02729     */
02730 
02731     unsigned long ixload = 0;
02732     unsigned long ctload = 0;
02733     
02734     if (textnthword ((ptrbyte)*htext, gethandlesize (htext), fieldnum, fielddelim, true, (long *)(&ixload), (long *)(&ctload)))
02735         return (loadfromhandletohandle (htext, (long *)(&ixload), ctload, false, hreturnedtext));
02736 
02737     return (newemptyhandle (hreturnedtext));
02738     } /*nthfieldhandle*/
02739 
02740 
02741 boolean parseargsverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02742 
02743     /*
02744     6.1b7 AR: Bug fix: We no longer depend on the field value
02745     to not contain "=" signs.
02746     */
02747 
02748     #ifdef oplanglists
02749         Handle htext, hreturnedtext = nil, hfieldname, hfieldvalue;
02750         hdllistrecord list = nil;
02751         short fieldnum = 1;
02752         long lenfield, lenfieldname;
02753 
02754         flnextparamislast = true;
02755         
02756         if (!getexempttextvalue (hparam1, 1, &htext))
02757             return (false);
02758         
02759         if (!opnewlist (&list, false))
02760             goto error;
02761         
02762         while (true) {
02763             
02764             if (!nthfieldhandle (htext, '&', fieldnum++, &hreturnedtext))
02765                 break;
02766 
02767             lenfield = gethandlesize (hreturnedtext);
02768             
02769             if (lenfield == 0) { //ran out of fields
02770                 
02771                 disposehandle (hreturnedtext);
02772                 
02773                 break;
02774                 }
02775             
02776             if (!nthfieldhandle (hreturnedtext, '=', 1, &hfieldname))
02777                 goto error;
02778             
02779             lenfieldname = gethandlesize (hfieldname);
02780 
02781             decodehandle (hfieldname);
02782             
02783             if (!langpushlisttext (list, hfieldname))
02784                 goto error;
02785 
02786 /*          if (!nthfieldhandle (hreturnedtext, '=', 2, &hfieldvalue))
02787                 goto error;
02788 */
02789             if (lenfieldname < lenfield)
02790                 lenfieldname++;
02791             
02792             if (!loadhandleremains (lenfieldname, hreturnedtext, &hfieldvalue))
02793                 goto error;
02794             
02795             decodehandle (hfieldvalue);
02796 
02797             if (!langpushlisttext (list, hfieldvalue))
02798                 goto error;
02799 
02800             disposehandle (hreturnedtext);
02801             }
02802         
02803         disposehandle (htext);
02804         
02805         return (setheapvalue ((Handle) list, listvaluetype, vreturned));
02806 
02807         error: {
02808         
02809             disposehandle (hreturnedtext);
02810             
02811             disposehandle (htext);
02812             
02813             opdisposelist (list);
02814 
02815             return (false);
02816             }
02817     #else
02818         Handle htext, hreturnedtext = nil, hfieldname, hfieldvalue;
02819         AEDescList list = {typeNull, nil};
02820         short fieldnum = 1, ixlist = 1;
02821 
02822         flnextparamislast = true;
02823         
02824         if (!getexempttextvalue (hparam1, 1, &htext))
02825             return (false);
02826         
02827         if (!IACnewlist (&list))
02828             goto error;
02829         
02830         while (true) {
02831             
02832             if (!nthfieldhandle (htext, '&', fieldnum++, &hreturnedtext))
02833                 break;
02834             
02835             if (gethandlesize (hreturnedtext) == 0) { //ran out of fields
02836                 
02837                 disposehandle (hreturnedtext);
02838                 
02839                 break;
02840                 }
02841             
02842             if (!nthfieldhandle (hreturnedtext, '=', 1, &hfieldname))
02843                 goto error;
02844             
02845             decodehandle (hfieldname);
02846             
02847             if (!IACpushtextitem (&list, hfieldname, ixlist++))
02848                 goto error;
02849             
02850             if (!nthfieldhandle (hreturnedtext, '=', 2, &hfieldvalue))
02851                 goto error;
02852             
02853             decodehandle (hfieldvalue);
02854             
02855             if (!IACpushtextitem (&list, hfieldvalue, ixlist++))
02856                 goto error;
02857             
02858             disposehandle (hreturnedtext);
02859             }
02860         
02861         disposehandle (htext);
02862         
02863         #if TARGET_API_MAC_CARBON == 1
02864         
02865             {
02866             Handle h;
02867             
02868             copydatahandle (&list, &h);
02869             
02870             return (setheapvalue (h, listvaluetype, vreturned));            
02871             }
02872         
02873         #else
02874         
02875             return (setheapvalue (list.dataHandle, listvaluetype, vreturned));
02876         
02877         #endif
02878         
02879         error: {
02880         
02881             disposehandle (hreturnedtext);
02882             
02883             disposehandle (htext);
02884             
02885             AEDisposeDesc (&list); // 5.0d12 dmb
02886 
02887             if (!fllangerror)
02888                 oserror (IACglobals.errorcode);
02889 
02890             return (false);
02891             }
02892     #endif
02893     } /*parseargsverb*/
02894 
02895 
02896 boolean iso8859encodeverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02897     
02898     Handle htext;
02899     hdlhashtable htable;
02900     handlestream s;
02901     
02902     flnextparamislast = true;
02903     
02904     if (!gettablevalue (hparam1, 2, &htable))
02905         return (false);
02906     
02907     if (!getexempttextvalue (hparam1, 1, &htext))
02908         return (false);
02909     
02910     openhandlestream (htext, &s);
02911     
02912     if (!iso8859encode (&s, htable))
02913         return (false);
02914     
02915     closehandlestream (&s);
02916     
02917     return (setheapvalue (htext, stringvaluetype, vreturned));
02918     } /*iso8859encodeverb*/
02919 
02920 
02921 static boolean getGifBounds (hdlfilenum fnum, unsigned short * height, unsigned short * width) {
02922 
02923     unsigned char buf[20];  /*0-2 - signature, 3-5 - version, 6 low byte width, 7 high byte width, 8-9 low/high height*/
02924     
02925     if (fileread (fnum, 10, buf)) {
02926         *width = ((unsigned short) buf[7] << 8) + (unsigned short)buf[6];
02927         *height = ((unsigned short) buf[9] << 8) + (unsigned short)buf[8];
02928         return (true);
02929         }
02930 
02931     return (false);
02932     } /*getGifBounds*/
02933 
02934 
02935 boolean getgifheightwidthverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
02936 
02937     tyfilespec fs;
02938     hdlfilenum fnum;
02939     boolean fl;
02940     unsigned short height, width;
02941     hdllistrecord list = nil;
02942 
02943     flnextparamislast = true;
02944     
02945     if (!getfilespecvalue (hparam1, 1, &fs))
02946         return (false);
02947 
02948     fl = openfile (&fs, &fnum, true);
02949 
02950     if (fl) {
02951     
02952         fl = getGifBounds (fnum, &height, &width);
02953         
02954         closefile(fnum);
02955         
02956         if (fl) {
02957             #ifdef xxxoplanglists
02958                 if (opnewlist (&list, false)) {
02959                     if (langpushlistlong (list, (long) height)) {
02960                         if (langpushlistlong (list, (long) width)) {
02961                             return (setheapvalue ((Handle) list, listvaluetype, vreturned));
02962                             }
02963                         }
02964                     }
02965             #else
02966                 Point pt;
02967                 pt.v = width;
02968                 pt.h = height;
02969                 return (setpointvalue (pt, vreturned) && coercetolist (vreturned, listvaluetype));
02970             #endif
02971             }
02972         }
02973 
02974     if (list != nil)
02975         opdisposelist (list);
02976 
02977     return (false);
02978     } /*getgifheightwidthverb*/
02979 
02980 
02981 // This is JPEG code courtsey of Jim Correia  correia@barebones.com
02982 
02983 #define M_SOF0  0xC0        // Start Of Frame N 
02984 #define M_SOF1  0xC1        // N indicates which compression process 
02985 #define M_SOF2  0xC2        // Only SOF0-SOF2 are now in common use 
02986 #define M_SOF3  0xC3
02987 #define M_SOF5  0xC5        // NB: codes C4 and CC are NOT SOF markers 
02988 #define M_SOF6  0xC6
02989 #define M_SOF7  0xC7
02990 #define M_SOF9  0xC9
02991 #define M_SOF10 0xCA
02992 #define M_SOF11 0xCB
02993 #define M_SOF13 0xCD
02994 #define M_SOF14 0xCE
02995 #define M_SOF15 0xCF
02996 #define M_SOI   0xD8        // Start Of Image (beginning of datastream) 
02997 #define M_EOI   0xD9        // End Of Image (end of datastream) 
02998 #define M_SOS   0xDA        // Start Of Scan (begins compressed data) 
02999 #define M_COM   0xFE        // COMment 
03000 
03001 
03002 static boolean read_1_byte(hdlfilenum file_ref, unsigned char * c) {
03003 
03004     long    count = 1;
03005     return  (fileread(file_ref, count, c));
03006     } /*read_1_byte*/
03007 
03008 
03009 static boolean read_2_bytes(hdlfilenum file_ref, unsigned short * b) {
03010 
03011     long    count = 2;
03012     boolean res;
03013 
03014     res = fileread(file_ref, count, b);
03015 
03016     *b = conditionalshortswap(*b);
03017 
03018     return (res);
03019     } /*read_2_bytes*/
03020 
03021 
03022 static boolean first_marker(hdlfilenum file_ref, unsigned char * c) {
03023 
03024     boolean           result = noErr;
03025     unsigned char   c1,c2;
03026     long            count;
03027     
03028     count = 1;
03029     result = fileread(file_ref, count, &c1);
03030     
03031     if ( result == true ) {
03032         count = 1;
03033         result = fileread(file_ref, count, &c2);
03034         }
03035     
03036     if ( result == true ) {
03037         if ( ( c1 != 0xFF ) && ( c2 != M_SOI ) )
03038             result = false;
03039         }
03040     
03041     *c = c2;
03042     
03043     return (result);
03044     } /*first_marker*/
03045 
03046 
03047 static boolean next_marker(hdlfilenum file_ref, unsigned char * c) {
03048 
03049     boolean           result = true;
03050     long            discarded_bytes = 0;
03051 
03052     //  Find 0xFF byte; count and skip any non-FFs.
03053     
03054     result = read_1_byte(file_ref, c);
03055     while ( ( result == true ) && (*c != 0xFF) ) {
03056         discarded_bytes++;
03057         result = read_1_byte(file_ref, c);
03058         }
03059     
03060     //  Get marker code byte, swallowing any duplicate FF bytes.     Extra FFs
03061     //  are legal as pad bytes, so don't count them in discarded_bytes.
03062     
03063     do {
03064         result = read_1_byte(file_ref, c);
03065     } while (( result == true ) && (*c == 0xFF));
03066 
03067     return (result);
03068     }  /*next_marker*/
03069 
03070 
03071 static boolean skip_variable (hdlfilenum file_ref) {
03072 
03073     boolean result = true;
03074     unsigned short length;
03075 
03076     // Get the marker parameter length count
03077     result = read_2_bytes(file_ref, &length);
03078 
03079     // Length includes itself, so must be at least 2
03080     if ( ( result == true ) && ( length < 2 ) )
03081         result = false;
03082         
03083     if ( result == true ) {
03084         length -= 2;
03085 
03086         // Skip over the remaining bytes
03087 
03088         while ( ( result == true ) && ( length > 0 ) ) {
03089             unsigned char c;
03090             result = read_1_byte(file_ref, &c);
03091             length--;
03092             }
03093         }
03094 
03095     return (result);        //Added RAB: 1/29/98
03096     } /*skip_variable*/
03097 
03098 
03099 static boolean ScanJPEGHeader(hdlfilenum file_ref, unsigned short * height, unsigned short * width) {
03100 
03101     boolean   result = true;
03102     unsigned char marker;
03103     boolean done = false;
03104     
03105     *height = 0;
03106     *width = 0;
03107 
03108     // Expect SOI at start of file
03109     if ( ( true != first_marker(file_ref, &marker) ) || ( marker != M_SOI ) ) {
03110         result = false;
03111         }
03112 
03113     // Scan miscellaneous markers until we reach SOS.
03114         
03115     if ( result != true )
03116         done = true;
03117     
03118     while ( ! done ) {
03119         result = next_marker(file_ref, &marker);
03120 
03121         if ( result != true )
03122             break;      
03123         
03124         switch (marker) {
03125             case M_SOF0:        // Baseline 
03126             case M_SOF1:        // Extended sequential, Huffman 
03127             case M_SOF2:        // Progressive, Huffman 
03128             case M_SOF3:        // Lossless, Huffman 
03129             case M_SOF5:        // Differential sequential, Huffman 
03130             case M_SOF6:        // Differential progressive, Huffman 
03131             case M_SOF7:        // Differential lossless, Huffman 
03132             case M_SOF9:        // Extended sequential, arithmetic 
03133             case M_SOF10:       // Progressive, arithmetic 
03134             case M_SOF11:       // Lossless, arithmetic 
03135             case M_SOF13:       // Differential sequential, arithmetic 
03136             case M_SOF14:       // Differential progressive, arithmetic 
03137             case M_SOF15:       // Differential lossless, arithmetic 
03138                     {
03139                     unsigned short  length;
03140                     unsigned short  image_height, image_width;
03141                     unsigned char   data_precision;
03142                     
03143                     result = read_2_bytes(file_ref, &length);
03144                     
03145                     if ( ( result == true ) && ( length < 7 ) )
03146                         result = false;
03147                     
03148                     if ( result == true )
03149                         result = read_1_byte(file_ref, &data_precision);
03150                         
03151                     if ( result == true )
03152                         result = read_2_bytes(file_ref, &image_height);
03153                         
03154                     if ( result == true )
03155                         result = read_2_bytes(file_ref, &image_width);
03156 
03157                     if ( result == true ) {
03158                         *height = image_height;
03159                         *width = image_width;
03160                         }
03161                     
03162                     done = true;
03163                     }
03164                 break;
03165     
03166             case M_SOS:             // stop before hitting compressed data 
03167                 done = true;
03168                 break;
03169     
03170             case M_EOI:             // in case it's a tables-only JPEG stream 
03171                 done = true;
03172                 break;
03173     
03174             case M_COM:
03175                 skip_variable(file_ref);
03176                 break;
03177     
03178             default:                // Anything else just gets skipped 
03179                 skip_variable(file_ref);    // we assume it has a parameter count... 
03180                 break;
03181             }
03182         }
03183             
03184     return (result);
03185     } /*ScanJPEGHeader*/
03186 
03187 
03188 boolean getjpegheightwidthverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
03189 
03190     tyfilespec fs;
03191     hdlfilenum fnum;
03192     boolean fl;
03193     unsigned short height, width;
03194     hdllistrecord list = nil;
03195 
03196     flnextparamislast = true;
03197     
03198     if (!getfilespecvalue (hparam1, 1, &fs))
03199         return (false);
03200 
03201     fl = openfile (&fs, &fnum, true);
03202 
03203     if (fl) {
03204         
03205         fl = ScanJPEGHeader (fnum, &height, &width);
03206         
03207         closefile (fnum);
03208         
03209         if (fl) {   
03210             #ifdef xxxoplanglists
03211                 if (opnewlist (&list, false)) {
03212                     if (langpushlistlong (list, (long) height)) {
03213                         if (langpushlistlong (list, (long) width)) {
03214                             return (setheapvalue ((Handle) list, listvaluetype, vreturned));
03215                             }
03216                         }
03217                     }
03218             #else
03219                 Point pt;
03220                 pt.v = width;
03221                 pt.h = height;
03222                 return (setpointvalue (pt, vreturned) && coercetolist (vreturned, listvaluetype));
03223             #endif
03224             }
03225         }
03226     
03227     if (list != nil)
03228         opdisposelist (list);
03229     
03230     /* JES: 10/28/2002, 9.1b1 -- ScriptError if the file couldn't be parsed for JPEG height/width instead of a silent failure */
03231     if (!fl) {
03232         langerrormessage (BIGSTRING ("\x49""Can't get JPEG height and width because the file isn't a valid JPEG file."));
03233         }
03234     
03235     return (false);
03236     } /*getjpegheightwidthverb*/
03237 
03238 
03239 #ifdef MACVERSION
03240 #pragma mark === build pagtable ===
03241 #endif
03242 
03243 static tyvaluetype langgetextendedvaluetype (const tyvaluerecord *val) {
03244     
03245     /*
03246     5.0.2 dmb: another missing bit of langexternal functionality
03247     */
03248 
03249     if ((*val).valuetype != externalvaluetype)
03250         return ((*val).valuetype);
03251     
03252     return (tyvaluetype) (outlinevaluetype + langexternalgettype (*val));
03253     } /*langgetextendedvaluetype*/
03254 
03255 
03256 static boolean additemtopagetable (hdlhashtable htable, hdlhashnode hnode, hdlhashtable hpagetable) {
03257 
03258     /*
03259     on addItemToPageTable (adr) { «adr points to an attribute
03260     */
03261     
03262     tyaddress adr;
03263     tyvaluetype objecttype;
03264     tyvaluerecord val;
03265     bigstring name;
03266     boolean fl;
03267     hdldatabaserecord hdb;
03268     
03269     adr.ht = htable;
03270     gethashkey (hnode, adr.bs);
03271     
03272     //  local (name = nameOf (adr^));
03273     copystring (adr.bs, name);
03274     
03275     //  if name beginsWith "#" {
03276     //      name = string.delete (name, 1, 1)};
03277     
03278     if (getstringcharacter (name, 0) == '#')
03279         deletestring (name, 1, 1);
03280     
03281     //  if not defined (adrpagetable^.[name]) {
03282     if (!hashtablesymbolexists (hpagetable, name)) {
03283     
03284         //  local (lowername = string.lower (name));
03285         bigstring lowername;
03286         copystring (name, lowername);
03287         alllower (lowername);
03288         
03289         //  local (objecttype = typeOf (adr^));
03290         //  case objecttype {
03291         //      outlinetype;
03292         //      tabletype {
03293         //          if lowername == "prefs" {
03294         //              local (i);
03295         //              for i = 1 to sizeOf (adr^) {
03296         //                  addItemToPageTable (@adr^ [i])}}
03297         //          else {
03298         //              adrpagetable^.[name] = adr}}}
03299         //  else {
03300         //      adrpagetable^.[name] = string (adr^)};
03301         
03302         val = (**hnode).val;
03303         objecttype = langgetextendedvaluetype (&val);
03304         
03305         switch (objecttype) {
03306             
03307             case tablevaluetype:
03308                 if (equalstrings (lowername, BIGSTRING ("\x05" "prefs"))) {
03309                     hdlhashtable ht;
03310                     hdlhashnode hn;
03311                     
03312                     if (langexternalvaltotable (val, &ht, hnode)) { // should never fail
03313                         
03314                         for (hn = (**ht).hfirstsort; hn != nil; hn = (**hn).sortedlink)
03315                             if (!additemtopagetable (ht, hn, hpagetable))
03316                                 return (false);
03317                         
03318                         return (true);
03319                         }
03320                     }
03321                 // else fall through
03322             case outlinevaluetype: 
03323                 if (!langassignaddressvalue (hpagetable, name, &adr))
03324                     return (false);
03325                 
03326                 break;
03327             
03328             default:
03329                 hdb = tablegetdatabase (htable);
03330 
03331                 if (hdb)
03332                     dbpushdatabase (hdb);
03333 
03334                 fl = copyvaluerecord (val, &val);
03335 
03336                 if (hdb)
03337                     dbpopdatabase ();
03338 
03339                 if (!fl || !strongcoercetostring (&val))
03340                     return (false);
03341                 
03342                 if (!hashtableassign (hpagetable, name, val))
03343                     return (false);
03344                 
03345                 exemptfromtmpstack (&val);
03346                 
03347                 break;
03348             }
03349         
03350         //  if lowername == "ftpsite" { // dmb: move this into buildpagetableverb
03351         //      adrpagetable^.subdirectoryPath = subdirpath;
03352         //      adrpagetable^.adrSiteRootTable = nomad}; «4.2
03353         
03354         //  if lowername == "template" { «4.1, 4.2
03355         //      if (objecttype == wptexttype) or (objecttype == outlinetype) {
03356         //          «Wed, Nov 20, 1996 at 7:58:21 AM by DW -- allow outlines to be templates
03357         //          adrpagetable^.indirectTemplate = false;
03358         
03359         if (equalstrings (lowername, str_template)) {
03360             
03361             if ((objecttype == wordvaluetype) || (objecttype == outlinevaluetype)) {
03362                 
03363                 if (!langassignbooleanvalue (hpagetable, str_indirecttemplate, false))
03364                     return (false);
03365                     
03366                 //  if objecttype == outlinetype {
03367                 //      adrpagetable^.[name] = adr^}}}};
03368                 
03369                 if (objecttype == outlinevaluetype) {
03370                 
03371                     if (!copyvaluerecord (val, &val))
03372                         return (false);
03373                     
03374                     if (!hashtableassign (hpagetable, name, val))
03375                         return (false);
03376                     
03377                     exemptfromtmpstack (&val);
03378                     }
03379                 }
03380             }
03381         }
03382                 
03383     return (true);
03384     } /*additemtopagetable*/
03385 
03386 
03387 static boolean buildpagetableverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
03388 
03389     /*
03390     on buildTable (adrobject, adrpagetable) {
03391     */
03392 
03393     hdlhashtable hpagetable;
03394     tyaddress nomad;
03395     hdlhashtable nomadtable;
03396     hdlhashnode hn;
03397     bigstring subdirpath;
03398     #ifdef MACVERSION
03399     byte pc = ':';
03400     #else
03401     byte pc = '\\';
03402     #endif
03403     
03404     if (!getvarparam (hparam1, 1, &nomad.ht, nomad.bs))
03405         return (false);
03406     
03407     flnextparamislast = true;
03408     
03409     if (!gettablevalue (hparam1, 2, &hpagetable))
03410         return (false);
03411 
03412     //  local (nomad = parentOf (adrobject^), subdirpath = "");
03413     setemptystring (subdirpath);
03414     
03415     //  loop { «pop out to the root looking for #directives
03416     while (true) {
03417             
03418         //  local (i);
03419         //  if nomad == nil or nomad == @root {
03420         //      break};
03421         nomadtable = nomad.ht;
03422         if ((nomadtable == roottable) || !findinparenttable (nomadtable, &nomad.ht, nomad.bs))
03423             break;
03424         
03425         //  for i = 1 to sizeOf (nomad^) {
03426         for (hn = (**nomadtable).hfirstsort; hn != nil; hn = (**hn).sortedlink) {
03427             
03428             bigstring name;
03429             
03430             gethashkey (hn, name);
03431             
03432             //  if nameOf (nomad^ [i]) beginsWith "#" {
03433             //      addItemToPageTable (@nomad^ [i])}
03434             if (getstringcharacter (name, 0) == '#') {
03435                 
03436                 //  if lowername == "ftpsite" {  // dmb: pulled this out of additemtopagetable
03437                     //  adrpagetable^.subdirectoryPath = subdirpath;
03438                     //  adrpagetable^.adrSiteRootTable = nomad}; «4.2
03439                 if (equalidentifiers (name, BIGSTRING ("\x08" "#ftpsite")) && !hashtablesymbolexists (hpagetable, str_ftpsite)) {
03440                     
03441                     if (!langassignstringvalue (hpagetable, BIGSTRING ("\x10" "subdirectoryPath"), subdirpath))
03442                         return (false);
03443                     
03444                     if (!langassignaddressvalue (hpagetable, BIGSTRING ("\x10" "adrSiteRootTable"), &nomad))
03445                         return (false);
03446                     }
03447                 
03448                 if (!additemtopagetable (nomadtable, hn, hpagetable))
03449                     return (false);
03450                 }
03451             
03452             //  else {
03453             //      break}};
03454             //else
03455             //  break;
03456             }
03457         
03458         //  if defined (nomad^.tools) {
03459         //      addItemToPageTable (@nomad^.tools)};
03460         
03461         if (hashtablelookupnode (nomadtable, BIGSTRING ("\x05" "tools"), &hn))
03462             additemtopagetable (nomadtable, hn, hpagetable);
03463         
03464         //  if defined (nomad^.glossary) {
03465         //      addItemToPageTable (@nomad^.glossary)};
03466         if (hashtablelookupnode (nomadtable, BIGSTRING ("\x08" "glossary"), &hn))
03467             additemtopagetable (nomadtable, hn, hpagetable);
03468         
03469         //  subdirpath = nameOf (nomad^) + pc + subdirpath;
03470         //  nomad = parentOf (nomad^)}
03471         insertchar (pc, subdirpath);
03472         insertstring (nomad.bs, subdirpath);
03473         }
03474     
03475     return (setbooleanvalue (true, vreturned));
03476     } /*buildpagetableverb*/
03477 
03478 
03479 #if 0
03480 
03481 static boolean refglossaryverb (hdltreenode hp1, tyvaluerecord *v) {
03482     
03483     /*
03484     on refGlossary (name)
03485         «5.0 DW -- html.buildObject sets up a global for us, html.data.adrPageTable
03486             «it points to the pagetable for the page currently being built
03487             «we're called from inside html.processMacros, which is protected by a semaphore
03488             «if you're calling this routine directly, you must set up html.data.adrPageTable yourself.
03489             «11/12/97 at 8:34:30 AM by DW       
03490     */
03491     
03492     typrocessmacrosinfo pageinfo;
03493     Handle href, hresult;
03494     bigstring lerrorstring;
03495     handlestream s;
03496     
03497     // get all of the prefs and tables that we need
03498     
03499     if (!htmlgetpagetable (&pageinfo.hpagetable) || !htmlgetprefstable (&pageinfo.huserprefs))
03500         return (false);
03501     
03502     return (htmlrefglossary (&pageinfo, href, lerrorstring, &hresult));
03503     } /*refglossaryverb*/
03504 
03505 #endif
03506 
03507 
03508 #ifdef MACVERSION
03509 #pragma mark === verbs ===
03510 #endif
03511 
03512 static boolean getprefverb (hdltreenode hp1, tyvaluerecord *v) {
03513     
03514     /*
03515     on getPref (prefName, adrpagedata=@websites.["#data"]) { «new in 4.1
03516         «Look for a preference directive, a global pref, or return a default
03517     */
03518     
03519     bigstring prefname;
03520     typrocessmacrosinfo pageinfo;
03521     
03522     if (!getstringvalue (hp1, 1, prefname))
03523         return (false);
03524     
03525     if (!getoptionalpagetablevalue (hp1, 2, &pageinfo.hpagetable))
03526         return (false);
03527     
03528     if (!htmlgetprefstable (&pageinfo.huserprefs))
03529         return (false);
03530     
03531     return (htmlgetpref (&pageinfo, prefname, v));
03532     } /*getprefverb*/
03533 
03534 
03535 #if 0
03536 
03537 static boolean getonedirectiveverb (hdltreenode hp1, tyvaluerecord *v) {
03538     
03539     /*
03540     on getOneDirective (directiveName, s) { «new in 4.0.1
03541         «Revised for ContentServer.
03542             «Friday, March 13, 1998 at 9:47:37 PM by PBS
03543             «Now case-insensitive.
03544             «Respects directivesOnlyAtBeginning pref.
03545         «Old code
03546             «local (ix = string.patternMatch (string.lower (directivename), string.lower (s)))
03547             «if ix > 0
03548                 «s = string.delete (s, 1, ix + sizeof (directivename))
03549                 «s = string.delete (s, string.patternmatch (cr, s), infinity)
03550                 «return (evaluate (s))
03551             «else
03552                 «return ("")
03553         local (value = "");
03554         local (flDirectivesOnlyAtBeginning = html.getPref ("directivesOnlyAtBeginning"));
03555         if typeOf (s) == outlineType {
03556             flDirectivesOnlyAtBeginning = false};
03557         table.assign (@s, string.replaceAll (string (s), "\n", ""));
03558         if directiveName beginsWith "#" { //pop off leading # character
03559             directiveName = string.mid (directiveName, 2, infinity)};
03560         loop { //loop through directives
03561             if sizeof (s) == 0 {
03562                 break};
03563             local (line = string.nthField (s, "\r", 1));
03564             if line beginsWith "#" {
03565                 local (name);
03566                 name = string.nthField (line, ' ', 1); //get the name of the directive
03567                 name = string.mid (name, 2, infinity); //pop off leading # character
03568                 if string.lower (name) == string.lower (directiveName) { //is this the directive asked for?
03569                     local (ix = string.patternMatch (" ", line));
03570                     value = string.mid (line, ix + 1, infinity);
03571                     return (evaluate (value))}}
03572             else {
03573                 if flDirectivesOnlyAtBeginning {
03574                     break}};
03575             s = string.delete (s, 1, sizeof (line) + 1);
03576             if sizeOf (s) < 3 {
03577                 break}};
03578         return (value)}
03579     */
03580     
03581     } /*getonedirectiveverb*/
03582 
03583 #endif
03584 
03585 
03586 static boolean htmlrundirective (typrocessmacrosinfo *pmi, Handle s, bigstring fieldname) {
03587     
03588     /*
03589     run the directive, consuming it.
03590     
03591     the only field we look at in pmi is the pagetable
03592     */
03593     
03594     bigstring lerrorstring;
03595     tyvaluerecord val;
03596     
03597     textfirstword ((ptrbyte)(*s), gethandlesize (s), chspace, fieldname);
03598     
03599     pullfromhandle (s, 0, stringlength (fieldname) + 1, nil);
03600     
03601     if (langruntraperror (s, &val, lerrorstring)) {
03602         
03603         if (!hashtableassign ((*pmi).hpagetable, fieldname, val))
03604             return (false);
03605         
03606         exemptfromtmpstack (&val);
03607         }
03608     else {
03609         lang2paramerror (evaldirectiveerror, fieldname, lerrorstring);
03610         
03611         return (false);
03612         }
03613     
03614     alllower (fieldname);
03615     
03616     if (equalstrings (fieldname, str_template))
03617         langassignbooleanvalue ((*pmi).hpagetable, str_indirecttemplate, true);
03618     
03619     return (true);
03620     } /*htmlrundirective*/
03621 
03622 
03623 static boolean rundirectiveverb (hdltreenode hp1, tyvaluerecord *v) {
03624     
03625     /*
03626     on runDirective (linetext, adrpagetable=@websites.["#data"]) { «4.2 -- extracted from renderObject macro
03627         «linetext contains a #directive line
03628             «process the directive and return the name of the directive
03629         «11/12/97 at 8:44:54 AM by DW -- adrpagetable is an optional param
03630             «Had to change the implementation.
03631             «Old method: build a script that's an assignment statement.
03632             «New method: evaluate the expression and assign into the pagetable field.
03633             «The old method for constructing an address won't work for non-global tables.
03634             «I don't believe this will break anything.
03635         
03636         local (s = string.commentDelete (linetext));
03637         local (fieldname = string.nthField (s, ' ', 1));
03638         s = string.delete (s, 1, sizeof (fieldname) + 1); «delete name and space
03639         try {delete (@adrpagetable^.[fieldname])}; «avoid Can't Assign Over error
03640         
03641         try {
03642             adrpagetable^.[fieldname] = evaluate (s)}
03643         else {
03644             scriptError ("Error evaluating #" + linetext + ": " + tryError)};
03645         
03646         local (lastdirective = string.lower (fieldname)); «4.2
03647         if lastdirective == "template" { «4.0.2
03648             adrpagetable^.indirectTemplate = true};
03649         return (lastdirective)}
03650     */
03651     
03652     Handle s;
03653     typrocessmacrosinfo pageinfo;
03654     bigstring fieldname;
03655     
03656     if (!getexempttextvalue (hp1, 1, &s))
03657         return (false);
03658     
03659     if (!textcommentdelete (s))
03660         goto error;
03661     
03662     if (!getoptionalpagetablevalue (hp1, 2, &pageinfo.hpagetable))
03663         goto error;
03664 
03665     if (!htmlrundirective (&pageinfo, s, fieldname))
03666         goto error;
03667     
03668     return (setstringvalue (fieldname, v));
03669     
03670     error:
03671         disposehandle (s);
03672         
03673         return (false);
03674     } /*rundirectiveverb*/
03675 
03676 
03677 static boolean rundirectivesverb (hdltreenode hp1, tyvaluerecord *v) {
03678     
03679     /*
03680     on runDirectives (wpstring, adrpagetable=@websites.["#data"]) { «4.2 -- extracted from renderObject macro
03681         «wpstring contains a page of text
03682             «run all the #directives and return the cleaned up text
03683         
03684         wpstring = string.replaceAll (wpstring, "\n", ""); «work around Windows problem -- 11/11/97 DW
03685         local (s = "");
03686         loop { «process #directives
03687             if sizeof (wpstring) == 0 {
03688                 break};
03689             local (line = string.nthField (wpstring, "\r", 1));
03690             if line beginsWith "#" {
03691                 html.runDirective (string.delete (line, 1, 1), adrpagetable)}
03692             else {
03693                 if html.getPref ("directivesOnlyAtBeginning", adrpagetable) {
03694                     s = s + wpstring;
03695                     break};
03696                 s = s + line + "\r"};
03697             wpstring = string.delete (wpstring, 1, sizeof (line) + 1)};
03698         return (s)}
03699     
03700     5.0.2b16 dmb: finished kernelization; fixed hang when not fldirectivesonlyatbeginning
03701     */
03702     
03703     Handle wpstring;
03704     handlestream s;
03705     long ct, pos;
03706     Handle line;
03707     boolean fldirectivesonlyatbeginning;
03708     typrocessmacrosinfo pageinfo;
03709     bigstring bsdirective;
03710     
03711     if (!getoptionalpagetablevalue (hp1, 2, &pageinfo.hpagetable))
03712         return (false);
03713     
03714     if (!htmlgetprefstable (&pageinfo.huserprefs))
03715         return (false);
03716     
03717     if (!htmlgetbooleanpref (&pageinfo, str_directivesonlyatbeginning, &fldirectivesonlyatbeginning))
03718         return (false);
03719     
03720     if (!getexempttextvalue (hp1, 1, &wpstring))
03721         return (false);
03722     
03723     openhandlestream (wpstring, &s);
03724     
03725     bundle { // wpstring = string.replaceAll (wpstring, "\n", ""); «work around Windows problem -- 11/11/97 DW
03726         for (s.pos = 0; s.pos < s.eof; ++s.pos) {
03727             
03728             if ((*wpstring) [s.pos] == '\n')
03729                 pullfromhandlestream (&s, 1, nil);
03730             }
03731         
03732         s.pos = 0; // reset
03733         }
03734     
03735     while (!athandlestreameof (&s)) { // process #directives
03736         
03737         for (pos = s.pos; pos < s.eof; ++pos)
03738             if ((*wpstring) [pos] == '\r')
03739                 break;
03740         
03741         // pos is at eof, or a return
03742         ct = pos - s.pos;
03743         
03744         if ((ct > 0) && ((*wpstring) [s.pos] == '#')) { // found a directive line
03745             
03746             if (!newhandle (ct, &line))
03747                 break;
03748             
03749             pullfromhandlestream (&s, ct, *line); // pull directive out of wptext
03750             
03751             if (!athandlestreameof (&s))
03752                 pullfromhandlestream (&s, 1, nil); // strip cr of directive line
03753             
03754             pullfromhandle (line, 0, 1, nil); // strip # from directive
03755             
03756             if (!htmlrundirective (&pageinfo, line, bsdirective)) // run the directive
03757                 break;
03758             }
03759         else {
03760             
03761             if (fldirectivesonlyatbeginning)
03762                 break;
03763             
03764             s.pos = pos + 1; // just past cr, or eof
03765             }
03766         }
03767     
03768     closehandlestream (&s);
03769     
03770     return (!fllangerror && setheapvalue (wpstring, stringvaluetype, v));
03771     } /*rundirectivesverb*/
03772 
03773 
03774 static boolean runoutlinedirectivesverb (hdltreenode hp1, tyvaluerecord *v) {
03775 #pragma unused(v)
03776 
03777     /*
03778     on runOutlineDirectives (adroutline, adrpagetable=@websites.["#data"]) { «4.2
03779         «the outline can contain #directives
03780             «run all the #directives and return the outline with the directives deleted
03781             «please send us a *COPY* of your outline. thanks!
03782     */
03783     
03784     typrocessmacrosinfo pageinfo;
03785     hdloutlinerecord ho;
03786     hdlheadrecord nomad, nextnomad, hsummit;
03787     bigstring lastdirective;
03788     bigstring objectname;
03789     tyvaluerecord val;
03790     tyexternalid newtype;
03791     Handle hdirective;
03792     boolean fl = false;
03793     
03794     if (!getoutlinevalue (hp1, 1, &ho))
03795         return (false);
03796     
03797     if (!getoptionalpagetablevalue (hp1, 2, &pageinfo.hpagetable))
03798         return (false);
03799     
03800     //  local (oldtarget = target.get ());
03801     //  target.set (adroutline);
03802     //  op.firstSummit ();
03803     //  loop {
03804     oppushoutline (ho);
03805     
03806     nomad = (**ho).hsummit;
03807     
03808     while (true) {
03809         
03810         nextnomad = (**nomad).headlinkdown;
03811 
03812         //  local (s = op.getLineText ());
03813         //  if s beginsWith "#" {
03814 
03815         /*
03816         opgetheadstring (nomad, bs);
03817         
03818         if (stringlength (bs) > 0 && getstringcharacter (bs, 0) == '#')
03819         */
03820 
03821         hdirective = (**nomad).headstring; /*be sure to make a copy before modifying*/
03822 
03823         if ((hdirective != nil) && (gethandlesize (hdirective) > 0) && (**hdirective == '#')) {
03824 
03825             //  s = string.delete (s, 1, 1); «pop off the #
03826             //  local (lastdirective = html.runDirective (s, adrpagetable));
03827             
03828             /*
03829             deletestring (bs, 1, 1); // pop off the #
03830             
03831             if (!newtexthandle (bs, &hdirective))
03832                 goto error;
03833             */
03834             
03835             long ixload = 1; /*don't copy leading pound sign*/
03836             long ctload = gethandlesize (hdirective) - 1;
03837 
03838             if (!loadfromhandletohandle (hdirective, &ixload, ctload, false, &hdirective))
03839                 goto error;
03840 
03841             if (!htmlrundirective (&pageinfo, hdirective, lastdirective))
03842                 goto error;
03843             
03844             opsetoutline (ho);
03845             
03846             //  local (newtype = nil); «4.2
03847             //  case lastdirective { «4.2
03848             //      "define" {
03849             //          newtype = outlinetype};
03850             //      "definescript" {
03851             //          newtype = scripttype}};
03852             //  if newtype != nil {
03853             
03854             newtype = (tyexternalid) -1;
03855             
03856             if (equalstrings (lastdirective, BIGSTRING ("\x06" "define")))
03857                 newtype = idoutlineprocessor;
03858             
03859             else if (equalstrings (lastdirective, BIGSTRING ("\x0c" "definescript")))
03860                 newtype = idscriptprocessor;
03861             
03862             if (newtype != (tyexternalid) -1) {
03863             
03864                 //  local (theList);
03865                 //  local (objectname = adrpagetable^.[lastdirective]);
03866                 //  delete (@adrpagetable^.[lastdirective]);
03867                 
03868                 if (!langlookupstringvalue (pageinfo.hpagetable, lastdirective, objectname))
03869                     goto error;
03870                 
03871                 hashtabledelete (pageinfo.hpagetable, lastdirective);
03872                 
03873                 //  op.expand (1);
03874                 //  if op.go (right, 1) {
03875                 //      theList = op.outlineToList (adroutline)}
03876                 //  else {
03877                 //      scriptError ("Empty sub-outline in \"" + objectname + "\"#define directive.")};
03878                 //  local (adrnewoutline = @adrpagetable^.[objectname]);
03879                 //  new (newtype, adrnewoutline);
03880                 //  op.listToOutline (theList, adrnewoutline);
03881                 //  target.set (adrnewoutline);
03882                 //  op.firstsummit ();
03883                 //  op.deleteline ();
03884                 
03885                 if (!opnavigate (right, &nomad)) {
03886                     
03887                     langparamerror (emptydefinedirective, objectname);
03888                     
03889                     goto error;
03890                     }
03891                 
03892                 hsummit = (**ho).hsummit;
03893                 
03894                 (**ho).hsummit = nomad; // temp, while outline gets copied
03895                 
03896                 fl = langexternalnewvalue (newtype, (Handle) ho, &val);
03897                 
03898                 (**ho).hsummit = hsummit;
03899                 
03900                 if (!fl)
03901                     goto error;
03902                 
03903                 if (!hashtableassign (pageinfo.hpagetable, objectname, val))
03904                     goto error;
03905                 
03906                 //  target.set (adroutline);
03907                 //  op.go (left, 1)};
03908                 
03909                 opnavigate (left, &nomad);
03910                 }
03911             
03912             //  op.deleteline ()}
03913             opdeletenode (nomad);
03914             }
03915         else {
03916             //  if script.isComment () { «new in 4.0b7 -- omit top-level comment lines
03917             //      op.deleteline ()}
03918             //  else {
03919             //      if not op.go (down, 1) {
03920             //          break}}}};
03921             if ((**nomad).flcomment)
03922                 opdeletenode (nomad);
03923             }
03924         
03925         if (nextnomad == nomad)
03926             break;
03927         
03928         nomad = nextnomad;
03929         } 
03930     
03931     //  try {target.set (oldtarget)}}
03932     oppopoutline ();
03933     
03934     return (true);
03935     
03936     error:
03937         oppopoutline ();
03938         
03939         return (false);
03940     } /*runoutlinedirectivesverb*/
03941 
03942 
03943 static boolean cleanforexportverb (hdltreenode hp1, tyvaluerecord *v) {
03944     
03945     /*
03946 on cleanForExport (text) { «prepare text to leave Mac environment
03947     «10/31/97 at 6:45:58 AM by DW -- moved from toys.cleanForExport.
03948     if sys.os () == "MacOS" {
03949         on replace (searchfor, replacewith) {
03950             if string.patternMatch (searchfor, text) != 0 { «optimization
03951                 text = string.replaceAll (text, searchfor, replacewith)}};
03952         
03953         replace ("’", "'");
03954         replace ("‘", "'");
03955         replace ("“", “"”);
03956         replace ("”", “"”);
03957         replace ("•", "o");
03958         replace ("...", "...");
03959         replace (" ", " "); «a funny space that Word produces
03960         replace ("–", "--");
03961         replace ("«", "&lt;&lt;");
03962         replace ("»", "&gt;&gt;")};
03963     return (text)}
03964     */
03965     
03966     Handle x;
03967     
03968     flnextparamislast = true;
03969     
03970     if (!getexempttextvalue (hp1, 1, &x))
03971         return (false);
03972     
03973     if (!htmlcleanforexport (x))
03974         return (false);
03975     
03976     return (setheapvalue (x, stringvaluetype, v));
03977     } /*cleanforexportverb*/
03978 
03979 
03980 #if 0
03981 
03982 static boolean normalizenameverb (hdltreenode hp1, tyvaluerecord *v) {
03983 
03984     /*
03985     on normalizeName (name, pageTable=nil, adrObject=nil) {
03986         «2/12/98 at 3:26:44 PM by PBS
03987             «Support for normalizing folder names.
03988             «URL-encode returned name.
03989             «Support for normalizing a name that isn't the page being generated.
03990         «4/13/98 PBS
03991             «There are three contexts in which this operates:
03992                 «1) Normalizing a name of a new page that has no prefs.
03993                 «2) Normalizing a name based on the prefs of the page being rendered.
03994                 «3) Normalizing a name based on the prefs of a remote page.
03995         
03996         local (flDropNonAlphas, flLowerCaseFileNames);
03997         local (maxLength);
03998         local (extension = "");
03999         local (flFolder = false);
04000         
04001         if pageTable == nil and adrObject == nil { //it's a new page without prefs
04002             flDropNonAlphas = html.getPref ("dropNonAlphas");
04003             flLowerCaseFileNames = html.getPref ("lowerCaseFileNames");
04004             maxLength = number (html.getPref ("maxFileNameLength"));
04005             extension = html.getPref ("fileExtension")}
04006         else { //it's an existing page with prefs
04007             if pageTable == nil {
04008                 pageTable = @websites.["#data"]};
04009             if adrObject == nil {
04010                 adrObject = pageTable^.adrObject};
04011             if adrObject == pageTable^.adrObject { //it's the current page
04012                 flDropNonAlphas = html.getPref ("dropNonAlphas", pageTable);
04013                 flLowerCaseFileNames = html.getPref ("lowerCaseFileNames", pageTable);
04014                 maxLength = number (html.getPref ("maxFileNameLength", pageTable));
04015                 extension = html.getPref ("fileExtension", pageTable)}
04016             else { //it's a remote page
04017                 flDropNonAlphas = html.getPagePref ("dropNonAlphas", adrObject, pageTable);
04018                 flLowerCaseFileNames = html.getPagePref ("lowerCaseFileNames", adrObject, pageTable);
04019                 maxLength = number (html.getPagePref ("maxFileNameLength", adrObject, pageTable));
04020                 extension = html.getPagePref ("fileExtension", adrObject, pageTable)};
04021             if typeOf (adrObject^) == tableType {
04022                 flFolder = true}};
04023         
04024         if flDropNonAlphas {
04025             name = string.dropNonAlphas (name)};
04026         if flLowerCaseFileNames {
04027             name = string.lower (name)};
04028         if flFolder {
04029             extension = ""};
04030         maxLength = maxLength - sizeOf (extension);
04031         if sizeof (name) > maxLength {
04032             name = string.mid (name, 1, maxLength)};
04033         return (name)}
04034     */
04035     
04036     } /*normalizenameverb*/
04037 
04038 #endif
04039 
04040 
04041 static boolean glossarypatcherverb (hdltreenode hp1, tyvaluerecord *v) {
04042 
04043     /*
04044     on glossaryPatcher (adrpagedata=@websites.["#data"]) {
04045         «scan the fully rendered page for [[#glossPatch xxx|yyy]]
04046             «generate relative href's for these references
04047             «Sun, Nov 3, 1996: if xxx is empty, just generate the URL, not an href
04048                 «This supports the JavaScript popup menu in the DaveNet website
04049             «Author: Dave Winer, dwiner@well.com
04050             «Mon, Jan 20, 1997 at 3:30:04 PM by PH
04051                 «Only do the work if the pref is set to true
04052     */
04053     
04054     typrocessmacrosinfo pageinfo;
04055     boolean fluseglosspatcher;
04056     tyaddress nomad;
04057     tyaddress ftpsite;
04058     tyvaluerecord vrenderedtext;
04059     handlestream s;
04060     long ix, ixstart, ixend, ixload, lenpath;
04061     long ixword = 0;
04062     long lenword =  0;
04063     Handle hurl = nil;
04064     Handle h = nil;
04065     Handle hpath = nil;
04066     Handle hlinetext = nil;
04067     hdlhashnode hnode;
04068     
04069     if (!getoptionalpagetablevalue (hp1, 1, &pageinfo.hpagetable))
04070         return (false);
04071     
04072     if (!htmlgetprefstable (&pageinfo.huserprefs))
04073         return (false);
04074     
04075     //  if not html.getPref ("useGlossPatcher", adrpagedata) {
04076     //      return};
04077     
04078     if (!htmlgetbooleanpref (&pageinfo, str_useglosspatcher, &fluseglosspatcher))
04079         return (false);
04080     
04081     if (!fluseglosspatcher)
04082         return (setbooleanvalue (true, v));
04083     
04084     //  with adrpagedata^ {
04085     //      local (pattern = "[[#glossPatch ");
04086     //      local (ix, ixstart, ixend);
04087     //      loop {
04088     
04089     if (!hashtablelookup (pageinfo.hpagetable, str_renderedtext, &vrenderedtext, &hnode)) {
04090         
04091         langparamerror (unknownidentifiererror, str_renderedtext);
04092         
04093         return (false);
04094         }
04095 
04096     //  local (url = "", nomad = adrobject);
04097     //  loop {
04098     //      nomad = parentof (nomad^);
04099     //      if nomad == parentOf (ftpsite^) {
04100     //          break};
04101     //      url = "../" + url};
04102         
04103     if (!langlookupaddressvalue (pageinfo.hpagetable, str_adrobject, &nomad))
04104         return (false);
04105         
04106     if (!langlookupaddressvalue (pageinfo.hpagetable, str_ftpsite, &ftpsite))
04107         return (false);
04108 
04109     /*findinparenttable (ftpsite.ht, &ftpsite.ht, ftpsite.bs);*/
04110 
04111     if (!newemptyhandle (&hurl))
04112         goto exit;
04113         
04114     while (true) {
04115             
04116         if (nomad.ht == ftpsite.ht || nomad.ht == nil) /*6.1b10 AR: Added nil check to prevent crashes*/
04117             break;
04118             
04119         if (!findinparenttable (nomad.ht, &nomad.ht, nomad.bs))
04120             break;
04121 
04122         if (!pushtexthandle (BIGSTRING ("\x03" "../"), hurl))
04123             goto exit;
04124         }/*while*/
04125 
04126     openhandlestream (vrenderedtext.data.stringvalue, &s);
04127     
04128     while (true) {
04129 
04130         //  ixstart = string.patternMatch (pattern, renderedtext);
04131         //  if ixstart == 0 {
04132         //      break};
04133         
04134         ixstart = textpatternmatch ((byte *)(*s.data + s.pos), s.eof - s.pos, str_glosspatch, false);
04135         
04136         if (ixstart < 0)
04137             break;
04138         
04139         ixstart += s.pos;
04140         
04141         //  ix = ixstart + sizeof (pattern);
04142         //  ixend = string.patternMatch ("]]", string.mid (renderedtext, ix, infinity)) + ix;
04143         //  ix = ixend + 1;
04144         //  if ix == 1 {
04145         //      ix = sizeOf (renderedtext)};
04146         
04147         ix = ixstart + stringlength (str_glosspatch);
04148         
04149         ixend = textpatternmatch ((byte *)(*s.data + ix), s.eof - ix, BIGSTRING ("\x02" "]]"), false);
04150         
04151         if (ixend < 0)
04152             ixend = s.eof;
04153         else
04154             ixend += ix + 2;
04155                 
04156         //  local (s, linetext, patch);
04157         //  s = string.mid (renderedtext, ixstart, ixend - ixstart + 1);
04158         //  s = string.delete (s, 1, sizeof (pattern));
04159 
04160         ixload = ixstart + stringlength (str_glosspatch);
04161 
04162         if (!loadfromhandletohandle (s.data, &ixload, ixend - ixstart - stringlength (str_glosspatch), false, &h))
04163             goto exit;
04164 
04165         //  «url = url + string.nthField (s, '|', 2) + html.getPref ("fileExtension", adrpagedata)
04166         //  bundle { //add the path to url, it's more complicated because the extension may already be there
04167         //      local (path = string.nthField (s, '|', 2));
04168         //      local (extension = string.nthField (path, '.', 2));
04169         //      if extension == "" { //no extension
04170         //          path = path + html.getPref ("fileExtension", adrpagedata)};
04171         //      url = url + path};
04172         
04173         if (!nthfieldhandle (h, '|', 2, &hpath))
04174             goto exit;
04175 
04176         lenpath = gethandlesize (hpath);
04177 
04178         textnthword ((ptrbyte)(*hpath), lenpath, 2, '.', true, &ixword, &lenword);
04179         
04180         if ((lenword == 0) && (lenpath > 0) && ((*hpath)[lenpath - 1] != '/')) {
04181 
04182             bigstring extension;
04183         
04184             if (!htmlgetstringpref (&pageinfo, str_fileextension, extension))
04185                 goto exit;
04186             
04187             if (!pushtexthandle (extension, hpath))
04188                 goto exit;
04189             }
04190         
04191         if (!inserthandleinhandle (hurl, hpath, 0))
04192             goto exit;
04193         
04194         //  linetext = string.nthField (s, '|', 1);
04195         //  if linetext == "" {
04196         //      patch = url}
04197         //  else {
04198         //      patch = "<a href=\"" + url + "\">" + linetext + "</a>"};
04199         //  renderedtext = string.delete (renderedtext, ixstart, ixend - ixstart + 1);
04200         //  renderedtext = string.insert (patch, renderedtext, ixstart)}}}
04201             
04202         if (!nthfieldhandle (h, '|', 1, &hlinetext))
04203             goto exit;
04204 
04205         s.pos = ixstart;
04206 
04207         if (gethandlesize (hlinetext) == 0) {
04208 
04209             boolean fl;
04210 
04211             fl = mergehandlestreamhandle (&s, ixend - ixstart, hpath);
04212             
04213             hpath = nil; /*already disposed*/
04214             
04215             if (!fl)
04216                 goto exit;
04217             }
04218 
04219         else {
04220 
04221             Handle hlink;
04222             
04223             if (!newtexthandle (str_hotlink, &hlink))
04224                 goto exit;
04225 
04226             if (!parsedialoghandle (hlink, hpath, hlinetext, nil, nil)) {
04227                 disposehandle (hlink);
04228                 goto exit;
04229                 }
04230 
04231             if (!mergehandlestreamhandle (&s, ixend - ixstart, hlink))
04232                 goto exit;
04233             }
04234 
04235         disposehandle (hpath);
04236 
04237         hpath = nil;
04238 
04239         disposehandle (h);
04240 
04241         h = nil;
04242 
04243         disposehandle (hlinetext);
04244         
04245         hlinetext = nil;
04246         }/*while*/
04247     
04248     disposehandle (hurl);
04249 
04250     closehandlestream (&s);
04251 
04252     // we changed string value in place, so notify
04253     langsymbolchanged (pageinfo.hpagetable, str_renderedtext, hnode, true);
04254     
04255     return (setbooleanvalue (true, v));
04256 
04257 exit:
04258 
04259     disposehandle (hurl);
04260 
04261     disposehandle (hpath);
04262 
04263     disposehandle (h);
04264 
04265     disposehandle (hlinetext);
04266 
04267     return (false);
04268     } /*glossarypatcherverb*/
04269 
04270 
04271 static boolean expandurlsverb (hdltreenode hp1, tyvaluerecord *v) {
04272     
04273     /*
04274     5.0.2b18 dmb: new verb, a very abridged version of processhtmlmacros
04275     */
04276 
04277     typrocessmacrosinfo pageinfo;
04278     Handle htext = nil;
04279     handlestream s;
04280     
04281     clearbytes (&pageinfo, sizeof (pageinfo));
04282     
04283     pageinfo.flactiveurls = true;
04284     
04285     if (!getexempttextvalue (hp1, 1, &htext))
04286         return (false);
04287     
04288     openhandlestream (htext, &s);
04289     
04290     if (!processhtmltext (&s, &pageinfo))
04291         return (false);
04292     
04293     closehandlestream (&s);
04294     
04295     return (setheapvalue (htext, stringvaluetype, v));
04296     } /*expandurlsverb*/
04297 
04298 
04299 /*on traversalSkip (adr) { «4.2
04300     «return true if this object should be omitted from a traversal
04301     local (name = nameof (adr^));
04302     if name beginsWith '#' { «4.0.1
04303         return (true)};
04304     case string.lower (name) {
04305         "glossary";
04306         "images"; «4.2
04307         "tools" {
04308             return (true)}};
04309     return (false)}
04310 */
04311 
04312 static boolean traversalskipverb (hdltreenode hp1, tyvaluerecord *v) {
04313 
04314     tyvaluerecord val;
04315     hdlhashtable ht;
04316     bigstring bsname;
04317     
04318     flnextparamislast = true;
04319     
04320     if (!getaddressparam (hp1, 1, &val))
04321         return (false);
04322     
04323     if (!getaddressvalue (val, &ht, bsname))
04324         return (false);
04325     
04326     if (getstringcharacter (bsname, 0) == '#') {
04327     
04328         setbooleanvalue (true, v);
04329 
04330         return (true);
04331         }
04332 
04333     alllower (bsname);
04334     
04335     if (equalstrings (bsname, str_glossary)
04336             || equalstrings (bsname, str_images)
04337             || equalstrings (bsname, str_tools)) {
04338     
04339         setbooleanvalue (true, v);
04340         
04341         return (true);
04342         }
04343     
04344     setbooleanvalue (false, v);
04345         
04346     return (true);  
04347     } /*traversalskipverb*/
04348 
04349 
04350 static boolean getpagetableaddressverb (hdltreenode hp1, tyvaluerecord *v) {
04351 
04352     /*
04353     6.1d7 AR: Implemented.
04354     */
04355 
04356     tyvaluerecord val;
04357     hdlhashtable ptatable;
04358     bigstring bsname;
04359     hdlhashnode hnode;
04360     
04361     if (!langcheckparamcount (hp1, 0))
04362         return (false);
04363 
04364     if (!langfastaddresstotable (systemtable, BIGSTRING ("\x17" "temp.pageTableAddresses"), &ptatable))
04365         return (false);
04366     
04367     numbertostring (getthreadid (getcurrentthread ()), bsname);
04368 
04369     if (!langhashtablelookup (ptatable, bsname, &val, &hnode))
04370         return (false);
04371     
04372     if (!copyvaluerecord (val, v) || !coercetoaddress (v))
04373         return (false);
04374 
04375     return (true);  
04376     } /*getpagetableaddressverb*/
04377 
04378 
04379 #ifdef MACVERSION
04380 #pragma mark === search indexing ===
04381 #endif
04382 
04383 static boolean stripmarkup (handlestream *s) {
04384 
04385     char *p;
04386     byte ch;
04387     byte bsskipthru [8];
04388     long ix;
04389     boolean fldidspace = true; // so leading spaces are omitted
04390     
04391     p = *(*s).data;
04392     
04393     for ((*s).pos = 0; (*s).pos < (*s).eof; ++(*s).pos) {
04394         
04395         ch = p [(*s).pos];
04396         
04397         setemptystring (bsskipthru);
04398         
04399         switch (ch) { // set chreplace or bsreplace
04400         
04401             case '{': // scan to matching close
04402                 setstringwithchar ('}', bsskipthru);
04403                 
04404                 break;
04405             
04406             case '<': // scan to matching close
04407                 setstringwithchar ('>', bsskipthru);
04408                 
04409                 break;
04410             
04411             case '\r':
04412             case '\n':
04413             case '\t':
04414                 ch = chspace;
04415                 
04416                 break;
04417             } /*switch*/
04418         
04419         if (!isemptystring (bsskipthru)) {
04420         
04421             ix = textpatternmatch ((byte *)(p + (*s).pos), (*s).eof - (*s).pos, bsskipthru, false);
04422             
04423             if (ix < 0)
04424                 break;
04425             
04426             pullfromhandlestream (s, ix + stringlength (bsskipthru) - 1, nil);
04427             
04428             ch = chspace; // replace leading char with space
04429             
04430             if (athandlestreameof (s))
04431                 break;
04432             }
04433         
04434         if (ch == chspace) { // the only possible replacement character
04435             
04436             if (fldidspace) { // kill this char instead of adding another
04437             
04438                 pullfromhandlestream (s, 1, nil);
04439                 
04440                 --(*s).pos;
04441                 }
04442             else {
04443             
04444                 p [(*s).pos] = ch;
04445                 
04446                 fldidspace = true;
04447                 }
04448             }
04449         else
04450             fldidspace = false;
04451         }
04452     
04453     return (true);
04454     } /*stripmarkup*/
04455 
04456 
04457 static boolean parsepageaddress (bigstring bsaddress, bigstring bspage, bigstring bsparent) {
04458     
04459     /*
04460     5.1.4 dmb: bsaddress might be an odb path, a file path, or an url. figure it out.
04461     */
04462     
04463     hdlhashtable hparent;
04464     long ctwords;
04465     tyfilespec fs;
04466     boolean fl;
04467     
04468     disablelangerror ();
04469     
04470     fl = langexpandtodotparams (bsaddress, &hparent, bspage);
04471     
04472     enablelangerror ();
04473     
04474     if (fl) { // an odb address
04475         
04476         if (!findinparenttable (hparent, &hparent, bsparent))
04477             setemptystring (bsparent);
04478         
04479         return (true);
04480         }
04481     
04482     if (patternmatch (BIGSTRING ("\x03" "://"), bsaddress)) { // an url
04483     
04484         ctwords = countwords (bsaddress, '/');
04485         
04486         nthword (bsaddress, ctwords, '/', bspage);
04487         
04488         if (ctwords > 1)
04489             nthword (bsaddress, ctwords - 1, '/', bsparent);
04490         
04491         return (true);
04492         }
04493     
04494     disablelangerror ();
04495     
04496     fl = pathtofilespec (bsaddress, &fs);
04497     
04498     if (fl) { // maybe a valid file
04499         
04500         filefrompath (bsaddress, bspage);
04501         
04502         folderfrompath (bsaddress, bsparent);
04503         
04504         cleanendoffilename (bsparent);
04505         
04506         filefrompath (bsparent, bsparent);
04507         }
04508     
04509     enablelangerror ();
04510     
04511     return (fl);
04512     } /*parsepageaddress*/
04513     
04514 
04515 static boolean isstalepageaddress (bigstring bsaddress) {
04516     
04517     /*
04518     5.1.4 dmb: try to determine if the page address is stale. don't do 
04519     urls since they require a net connection. (ideally, we should handle 
04520     file:// urls at least)
04521     */
04522     
04523     hdlhashtable hparent;
04524     bigstring bspage;
04525     tyfilespec fs;
04526     boolean flfolder;
04527     boolean fl;
04528     
04529     disablelangerror ();
04530     
04531     fl = langexpandtodotparams (bsaddress, &hparent, bspage);
04532     
04533     enablelangerror ();
04534     
04535     if (fl) { // an odb address
04536     
04537         return (hashtablesymbolexists (hparent, bspage));
04538         }
04539     
04540     if (patternmatch (BIGSTRING ("\x03" "://"), bsaddress)) { // an url
04541     
04542         return (false); // not handled now
04543         }
04544     
04545     disablelangerror ();
04546     
04547     fl = pathtofilespec (bsaddress, &fs);
04548     
04549     enablelangerror ();
04550     
04551     if (fl) { // maybe a valid file
04552     
04553         return (fileexists (&fs, &flfolder));
04554         }
04555     
04556     return (false);
04557     } /*isstalepageaddress*/
04558     
04559 
04560 static boolean cleanindextable (hdlhashtable hpages) {
04561     
04562     /*
04563     5.1.4 dmb: hpages is an index table for a specific word, containing 
04564     some number of pagekey/values. loop through the table and delete 
04565     any value whose key doesn't appear to refer to a valid item. right now, 
04566     we only test for files and odb items; we don't want to assume a net connection
04567     for url validation.
04568     */
04569     
04570     hdlhashnode hkey, hnext;
04571     bigstring bsaddress;
04572     
04573     for (hkey = (**hpages).hfirstsort; hkey != nil; hkey = hnext) {
04574         
04575         hnext = (**hkey).sortedlink;
04576         
04577         gethashkey (hkey, bsaddress);
04578         
04579         if (isstalepageaddress (bsaddress))
04580             if (!hashtabledelete (hpages, bsaddress))
04581                 return (false);
04582         }
04583     
04584     return (true);
04585     } /*cleanindextable*/
04586 
04587 
04588 static boolean deindexpage (hdlhashtable hindex, bigstring bspagekey) {
04589 
04590     /*
04591     5.1.4 dmb: Loop through the entire index, removing all references to this page.
04592     */
04593     
04594     hdlhashtable hwords, hpages;
04595     hdlhashnode hletter, hword;
04596     hdlhashnode hnextletter, hnextword;
04597     bigstring bsletter, bsword;
04598     
04599     for (hletter = (**hindex).hfirstsort; hletter != nil; hletter = hnextletter) {
04600         
04601         hnextletter = (**hletter).sortedlink;
04602         
04603         gethashkey (hletter, bsletter);
04604         
04605         if (langexternalvaltotable ((**hletter).val, &hwords, hletter)) {
04606             
04607             for (hword = (**hwords).hfirstsort; hword != nil; hword = hnextword) {
04608                 
04609                 hnextword = (**hword).sortedlink;
04610                 
04611                 gethashkey (hword, bsword);
04612                 
04613                 if (langexternalvaltotable ((**hword).val, &hpages, hword)) {
04614                     
04615                     if (bspagekey == nil) {
04616                     
04617                         if (!cleanindextable (hpages))
04618                             return (false);
04619                         }
04620                     else {
04621                     
04622                         if (hashtablesymbolexists (hpages, bspagekey))
04623                             if (!hashtabledelete (hpages, bspagekey))
04624                                 return (false);
04625                         }
04626                     
04627                     if ((**hpages).hfirstsort == nil) // the table is empty
04628                         if (!hashtabledelete (hwords, bsword))
04629                             return (false);
04630                     }
04631                 else if (equalstrings (bsword, BIGSTRING ("\x07" "item #1"))) {
04632                     
04633                     if (!hashtabledelete (hwords, bsword))
04634                         return (false);
04635                     }
04636                 }
04637             
04638             if ((**hwords).hfirstsort == nil) // the table is empty
04639                 if (!hashtabledelete (hindex, bsletter))
04640                     return (false);
04641             }
04642         else if (equalstrings (bsletter, BIGSTRING ("\x07" "item #1"))) {
04643     
04644             if (!hashtabledelete (hindex, bsletter))
04645                 return (false);
04646             }
04647         }
04648     
04649     return (true);
04650     } /*deindexpage*/
04651 
04652 
04653 static boolean indexpage (bigstring bsaddress, bigstring bsurl, bigstring bstitle, handlestream *pagetext, 
04654         hdlhashtable hindex, hdlhashtable hstopwords, bigstring bsparent, bigstring bspage) {
04655 #pragma unused (bsurl)
04656 
04657     /*
04658     Prepare text to be indexed.
04659         Replace whitespace characters and punctuation with spaces.
04660         (Don't replace #, <, >, {, and } characters.)
04661         Then strip HTML tags and macros.
04662     */
04663     
04664     handlestream *s = pagetext;
04665     byte *p;
04666     hdlhashtable hwords, hpages;
04667     tyvaluerecord vcount;
04668     long ctwords, ixword;
04669     long len, offset;
04670     bigstring bsword;
04671     byte bsletter [4];
04672     hdlhashnode hnode;
04673     
04674     if (!deindexpage (hindex, bsaddress))
04675         return (false);
04676     
04677     for ((*s).pos = 0; (*s).pos < (*s).eof; ++(*s).pos) {
04678         
04679         p = (byte *)(*(*s).data + (*s).pos);
04680         
04681         switch (*p) { // set chreplace or bsreplace
04682         
04683             case '\r':
04684             case '\n':
04685             case '\t':
04686             case '.':
04687             case '-':
04688             case '\\':
04689             case '/':
04690             case ':':
04691             case '?':
04692             case '!':
04693             case ';':
04694             case '@':
04695             case '$':
04696             case '%':
04697             case '^':
04698             case '&':
04699             case '*':
04700             case ')':
04701             case '(':
04702             case '[':
04703             case ']':
04704                 *p = chspace;
04705             
04706             default:
04707                 break;
04708             }
04709         }
04710     
04711     if (!stripmarkup (s))
04712         return (false);
04713     
04714     p = (unsigned char *)(*(*s).data);
04715     
04716     ctwords = textcountwords (p, (*s).eof, chspace, true);
04717     
04718     for (ixword = 1; ixword <= ctwords; ++ixword) {
04719         
04720         textnthword (p, (*s).eof, ixword, chspace, true, &offset, &len);
04721         
04722         texttostring (p + offset, len, bsword);
04723     
04724         if (getstringcharacter (bsword, 0) == '#') //it's a directive
04725             continue;
04726         
04727         dropnonalphas (bsword);
04728         
04729         alllower (bsword);
04730         
04731         poptrailingchars (bsword, 's'); //pop off trailing s's
04732         
04733         if (stringlength (bsword) < 3) //we don't index 1- and 2-letter words, except for "op" and "wp"
04734             if (!equalstrings (bsword, BIGSTRING ("\x02" "op")) && !equalstrings (bsword, BIGSTRING ("\x02" "wp")))
04735                 continue;
04736         
04737         if (hashtablesymbolexists (hstopwords, bsword)) //check if this is on the list of words not to index
04738             continue; //don't index
04739     
04740         setstringwithchar (getstringcharacter (bsword, 0), bsletter);
04741         
04742         if (!isalpha (getstringcharacter (bsletter, 0)))
04743             continue;
04744         
04745         // find the count for this page
04746         if (!langsuretablevalue (hindex, bsletter, &hwords)) // each letter table contains word tables
04747             return (false);
04748         
04749         if (!langsuretablevalue (hwords, bsword, &hpages)) // each word table contains addresses
04750             return (false);
04751         
04752         if (!hashtablelookup (hpages, bsaddress, &vcount, &hnode)) // this objects count for this word
04753             initvalue (&vcount, longvaluetype); //first occurence of this word in the page
04754         
04755         if (!coercetolong (&vcount)) //make sure it's a number
04756             return (false);
04757 
04758         ++vcount.data.longvalue; //one more occurrance
04759         
04760         bundle { //do relevancy ranking
04761         
04762             alllower (bsaddress);
04763             alllower (bspage);
04764             alllower (bsparent);
04765             alllower (bstitle);
04766             
04767             if (patternmatch (bsword, bsaddress) > 0)
04768                 if (vcount.data.longvalue < 100)
04769                     vcount.data.longvalue += 100;
04770             
04771             if (patternmatch (bsword, bspage) > 0)
04772                 if (vcount.data.longvalue < 500)
04773                     vcount.data.longvalue += 500;
04774             
04775             if (equalstrings (bsword, bsparent)) {
04776                 
04777                 firstword (bspage, '.', bspage);
04778                 
04779                 if (equalstrings (bspage, str_default) || equalstrings (bspage, str_index))
04780                     if (vcount.data.longvalue < 1000)
04781                         vcount.data.longvalue += 1000;
04782                 }
04783             
04784             if (patternmatch (bsword, bstitle) > 0)
04785                 if (vcount.data.longvalue < 2000)
04786                     vcount.data.longvalue += 2000;
04787             }
04788         
04789         if (!hashtableassign (hpages, bsaddress, vcount))
04790             return (false);
04791         }
04792     
04793     return (true);
04794     } /*indexpage*/
04795 
04796 
04797 static boolean stripmarkupverb (hdltreenode hp1, tyvaluerecord *v) {
04798     
04799     /*
04800     5.1.4 dmb
04801     */
04802     
04803     Handle x;
04804     handlestream s;
04805     
04806     flnextparamislast = true;
04807     
04808     if (!getexempttextvalue (hp1, 1, &x))
04809         return (false);
04810     
04811     openhandlestream (x, &s);
04812     
04813     if (!stripmarkup (&s))
04814         return (false);
04815     
04816     closehandlestream (&s);
04817     
04818     return (setheapvalue (x, stringvaluetype, v));
04819     } /*stripmarkupverb*/
04820 
04821 
04822 static boolean deindexpageverb (hdltreenode hp1, tyvaluerecord *v) {
04823     
04824     /*
04825     5.1.4 dmb
04826     */
04827     
04828     hdlhashtable hindex;
04829     bigstring bsaddress;
04830     
04831     if (!getstringvalue (hp1, 1, bsaddress))
04832         return (false);
04833     
04834     flnextparamislast = true;
04835     
04836     if (!gettablevalue (hp1, 2, &hindex))
04837         return (false);
04838     
04839     if (!deindexpage (hindex, bsaddress))
04840         return (false);
04841     
04842     return (setbooleanvalue (true, v));
04843     } /*deindexpageverb*/
04844 
04845 
04846 static boolean indexpageverb (hdltreenode hp1, tyvaluerecord *v) {
04847     
04848     /*
04849     5.1.4 dmb: kernelization of Brent's indexing code
04850     
04851     «adrPage is the address of the page.
04852     «url is the url of the page on the web.
04853     «title is the title of the page.
04854     «pageText is the unprocessed text of the page.
04855     «adrIndex is the address of the inverted index.
04856     «adrStopWordsTable is a table of stop words.
04857         «The name of each entry is a stop word -- a word *not* to index.
04858         «The values are ignored, they can be nil.
04859     «flMessages -- true to report progress in the About Window.
04860     */
04861     
04862     hdlhashtable hindex, hstopwords;
04863     Handle hpagetext;
04864     bigstring bsurl, bstitle, bsaddress, bspage, bsparent;
04865     handlestream s;
04866     
04867     if (!getstringvalue (hp1, 1, bsaddress))
04868         return (false);
04869     
04870     if (!getstringvalue (hp1, 2, bsurl))
04871         return (false);
04872     
04873     if (!getstringvalue (hp1, 3, bstitle))
04874         return (false);
04875     
04876     if (!getexempttextvalue (hp1, 4, &hpagetext))
04877         return (false);
04878     
04879     if (!gettablevalue (hp1, 5, &hindex))
04880         goto error;
04881     
04882     flnextparamislast = true;
04883     
04884     if (!gettablevalue (hp1, 6, &hstopwords))
04885         goto error;
04886     
04887     parsepageaddress (bsaddress, bspage, bsparent);
04888     
04889     openhandlestream (hpagetext, &s);
04890     
04891     if (!indexpage (bsaddress, bsurl, bstitle, &s, hindex, hstopwords, bsparent, bspage)) // consumes hpagetext
04892         goto error;
04893     
04894     disposehandle (hpagetext);
04895     
04896     return (setbooleanvalue (true, v));
04897     
04898     error:
04899         disposehandle (hpagetext);
04900         
04901         return (false);
04902     } /*indexpageverb*/
04903 
04904 
04905 static boolean cleanindexverb (hdltreenode hp1, tyvaluerecord *v) {
04906     
04907     /*
04908     5.1.4 dmb: clean out stale items from the index
04909     */
04910     
04911     hdlhashtable hindex;
04912     
04913     flnextparamislast = true;
04914     
04915     if (!gettablevalue (hp1, 1, &hindex))
04916         return (false);
04917     
04918     if (!deindexpage (hindex, nil))
04919         return (false);
04920     
04921     return (setbooleanvalue (true, v));
04922     } /*cleanindexverb*/
04923 
04924 
04925 static boolean unionmatchesverb (hdltreenode hp1, tyvaluerecord *v) {
04926     
04927     /*
04928     5.1.4 dmb: merge the results of the list of tables into an output table
04929     
04930     the first parameter is a list of addresses of tables.
04931     
04932     the second parameter is a table, where the results are stored.
04933     */
04934     
04935     tyvaluerecord vtablelist, adrtable, vtable;
04936     hdlhashtable htable, hunion;
04937     hdlhashnode hmatch, hitem;
04938     bigstring bsname;
04939     long ix, ctitems;
04940     tyvaluerecord vmatch, vunion;
04941     hdlhashnode hnode;
04942     
04943     if (!getparamvalue (hp1, 1, &vtablelist) || !coercetolist (&vtablelist, listvaluetype)) 
04944         return (false);
04945     
04946     flnextparamislast = true;
04947     
04948     if (!gettablevalue (hp1, 2, &hunion))
04949         return (false);
04950     
04951     if (!langgetlistsize (&vtablelist, &ctitems))
04952         return (false);
04953     
04954     for (ix = 1; ix <= ctitems; ++ix) {
04955         
04956         if (!langgetlistitem (&vtablelist, ix, nil, &adrtable))
04957             return (false);
04958         
04959         if (!coercetoaddress (&adrtable))
04960             return (false);
04961         
04962         if (!getaddressvalue (adrtable, &htable, bsname))
04963             return (false);
04964         
04965         if (!hashtablelookup (htable, bsname, &vtable, &hnode))
04966             return (false);
04967         
04968         if (langexternalvaltotable (vtable, &htable, hnode)) {
04969             
04970             for (hmatch = (**htable).hfirstsort; hmatch != nil; hmatch = (**hmatch).sortedlink) {
04971             
04972                 gethashkey (hmatch, bsname);
04973                 
04974                 if (!copyvaluerecord ((**hmatch).val, &vmatch) || !coercetolong (&vmatch))
04975                     return (false);
04976                 
04977                 if (hashtablelookupnode (hunion, bsname, &hitem)) {
04978                     
04979                     vmatch.data.longvalue += 4000;
04980                     
04981                     if (!addvalue ((**hitem).val, vmatch, &vunion))
04982                         return (false);
04983                     
04984                     exemptfromtmpstack (&vunion);
04985                     
04986                     (**hitem).val = vunion;
04987                     
04988                     langsymbolchanged (hunion, bsname, hitem, true); /*value changed*/
04989                     }
04990                 else {
04991                 
04992                     if (!hashtableassign (hunion, bsname, vmatch))
04993                         return (false);
04994                     }
04995                 }
04996             }
04997         }
04998     
04999     return (setbooleanvalue (true, v));
05000     } /*unionmatchesverb*/
05001 
05002 
05003 #ifdef MACVERSION
05004 #pragma mark === webserver utility functions ===
05005 #endif
05006 
05007 
05008 static boolean followaddressvalue (tyvaluerecord *v) {
05009 
05010     /*
05011     6.1d4 AR: Reviewed for proper error handling and reporting.
05012     */
05013     hdlhashnode hnode;
05014 
05015     if ((*v).valuetype == addressvaluetype) {
05016         hdlhashtable htable;
05017         bigstring bstemp;
05018 
05019         if (!getaddressvalue ((*v), &htable, bstemp))
05020             return (false);
05021 
05022         if (!langhashtablelookup (htable, bstemp, v, &hnode))
05023             return (false);
05024         }
05025         
05026     return (true);
05027     } /*followaddressvalue*/
05028 
05029 
05030 static boolean followaddress (tyaddress *adr) {
05031 
05032     /*
05033     6.1d4 AR: Reviewed for proper error handling and reporting.
05034     */
05035     
05036     tyvaluerecord val;
05037     hdlhashnode hnode;
05038     
05039     if (!langhashtablelookup ((*adr).ht, (*adr).bs, &val, &hnode))
05040         return (false);
05041     
05042     if (val.valuetype == addressvaluetype
05043             && !getaddressvalue (val, &(*adr).ht, (*adr).bs))
05044         return (false);
05045         
05046     return (true);
05047     } /*followaddress*/
05048 
05049 
05050 static boolean langlookupvaluefollow (hdlhashtable ht, bigstring bs, tyvaluerecord *v, hdlhashnode * hnode) {
05051 
05052     if (!langhashtablelookup (ht, bs, v, hnode))
05053         return (false);
05054     
05055     if ((*v).valuetype == addressvaluetype) {
05056         bigstring bstemp;
05057         hdlhashtable httemp;
05058         
05059         if (!getaddressvalue (*v, &httemp, bstemp))
05060             return (false);
05061             
05062         if (!langhashtablelookup (httemp, bstemp, v, hnode))
05063             return (false);
05064         }
05065 
05066     return (true);
05067     } /*langlookupvaluefollow*/
05068 
05069 
05070 static boolean langcallscriptwithaddress (tyaddress *adrscript, tyaddress *adrparamtable, hdlhashtable hcontext, tyvaluerecord *vreturned) {
05071     
05072     /*
05073     6.1d2 AR: New script based on langrunscript.
05074     Takes an address instead of a script name.
05075     Also assumes a single address as a parameter instead of a parameter list.
05076     */
05077     
05078     boolean flchained = false, fltmpval;
05079     boolean fl = false;
05080     tyvaluerecord val;
05081     hdltreenode hfunctioncall;
05082     hdltreenode hparamlist;
05083     hdltreenode hcode;
05084     hdltreenode hscriptcode;
05085     tyvaluerecord vhandler;
05086     hdlhashnode handlernode;
05087         
05088     if (!hashtablelookupnode ((*adrscript).ht, (*adrscript).bs, &handlernode)) {
05089         
05090         langparamerror (unknownfunctionerror, (*adrscript).bs);
05091         
05092         return (false);
05093         }
05094     
05095     vhandler = (**handlernode).val;
05096     
05097     /* make sure the script we're going to call is compiled */
05098     
05099     if ((**(*adrscript).ht).valueroutine == nil) { /*not a kernel table*/
05100         
05101         if (!langexternalvaltocode (vhandler, &hscriptcode)) {
05102 
05103             langparamerror (notfunctionerror, (*adrscript).bs);
05104 
05105             return (false);
05106             }
05107         
05108         if (hscriptcode == nil) { /*needs compilation*/
05109             
05110             if (!langcompilescript (handlernode, &hscriptcode))
05111                 return (false);
05112             }
05113         }
05114 
05115     /*build a code tree and call the handler, with our error hook in place*/
05116     
05117     if (!setaddressvalue ((*adrscript).ht, (*adrscript).bs, &val))
05118         return (false);
05119     
05120     if (!pushfunctionreference (val, &hfunctioncall))
05121         return (false);
05122     
05123     if (!setaddressvalue ((*adrparamtable).ht, (*adrparamtable).bs, &val)
05124             || !exemptfromtmpstack (&val)
05125             || !newconstnode (val, &hparamlist)) {
05126     
05127         langdisposetree (hfunctioncall);
05128         
05129         return (false);
05130         }
05131 
05132     if (!pushfunctioncall (hfunctioncall, hparamlist, &hcode)) /*consumes input parameters*/
05133         return (false);
05134     
05135     if (hcontext != nil) {
05136         
05137         flchained = (**hcontext).flchained;
05138         
05139         if (flchained)
05140             pushhashtable (hcontext);
05141         else
05142             chainhashtable (hcontext); /*establishes outer local context*/
05143         }
05144     
05145     fl = evaluatelist (hcode, vreturned);
05146     
05147     fltmpval = exemptfromtmpstack (vreturned); /*must survive disposing of local scope chain*/
05148     
05149     if (hcontext != nil) {
05150         
05151         if (flchained)
05152             pophashtable ();
05153         else
05154             unchainhashtable ();
05155         }
05156     
05157     if (fltmpval) /*insert into the next-most-global tmpstack, if one exists*/
05158         pushvalueontmpstack (vreturned);
05159 
05160     langdisposetree (hcode);
05161         
05162     return (fl);
05163     } /*langcallscriptwithaddress*/
05164 
05165 
05166 
05167 static boolean langruntextwithcontext (Handle htext, hdlhashtable hcontext, tyvaluerecord *v) {
05168                 
05169     boolean fl = false;
05170     boolean fltmpval, flchained = false;
05171         
05172     if (hcontext != nil) {
05173         
05174         flchained = (**hcontext).flchained;
05175         
05176         if (flchained)
05177             pushhashtable (hcontext);
05178         else
05179             chainhashtable (hcontext); /*establishes outer local context*/
05180         }
05181     
05182     fl = langrun (htext, v);
05183 
05184     fltmpval = exemptfromtmpstack (v); /*must survive disposing of local scope chain*/
05185     
05186     if (hcontext != nil) {
05187         
05188         if (flchained)
05189             pophashtable ();
05190         else
05191             unchainhashtable ();
05192         }
05193     
05194     if (fltmpval) /*insert into the next-most-global tmpstack, if one exists*/
05195         pushvalueontmpstack (v);
05196         
05197     return (fl);
05198     } /*langruntextwithcontext*/
05199 
05200 
05201 
05202 #ifdef MACVERSION
05203 #pragma mark === webserver.util verbs ===
05204 #endif
05205 
05206 
05207 
05208 static boolean addHeaderToTable (Handle htext, long ix1, long len1, long ix2, long len2, hdlhashtable htable) {
05209 
05210     /*
05211     6.1d1 AR: Utility function for webserverparseheaders.
05212     
05213     6.1d4 AR: Reviewed for proper error handling and reporting.
05214     */
05215 
05216     bigstring bs;
05217     Handle h = nil;
05218     tyvaluerecord vexists;
05219     hdlhashnode hnode;
05220 
05221     texttostring (&((*htext) [ix1]), len1, bs);
05222 
05223     if (!loadfromhandletohandle (htext, &ix2, len2, false, &h))
05224         return (false);
05225 
05226     if (hashtablelookup (htable, bs, &vexists, &hnode)) {  /*a symbol named bs already exists in htable*/
05227 
05228         if (vexists.valuetype == listvaluetype) { /*the existing value is a list: append*/
05229 
05230             if (!langpushlisttext (vexists.data.listvalue, h)) /*consumes h*/
05231                 return (false);
05232 
05233             langsymbolchanged (htable, bs, hnode, true);
05234             }   
05235 
05236         else {  /*the existing value is not a list: create a list from it and append*/
05237 
05238             hdllistrecord hlist;
05239             tyvaluerecord vlist;
05240 
05241             /*create empty list*/
05242 
05243             if (!opnewlist (&hlist, false)) {
05244                 
05245                 disposehandle (h);
05246                 
05247                 return (false);
05248                 }
05249 
05250             /*push vexists and vnew on list and add list to hashtable*/
05251 
05252             if (!langpushlistval (hlist, nil, &vexists)) {
05253                 
05254                 disposehandle (h);
05255                 
05256                 opdisposelist (hlist);
05257                 
05258                 return (false);
05259                 }
05260             
05261             if (!langpushlisttext (hlist, h)) {
05262 
05263                 opdisposelist (hlist);
05264                 
05265                 return (false);
05266                 }
05267 
05268             initvalue (&vlist, listvaluetype);
05269             
05270             vlist.data.listvalue = hlist;
05271             
05272             if (!hashtableassign (htable, bs, vlist)) {
05273                     
05274                 opdisposelist (hlist);
05275                 
05276                 return (false);
05277                 }
05278             }
05279         }
05280     else {  /*a symbol named bs doesn't exist in htable yet*/
05281 
05282         if (!langassigntextvalue (htable, bs, h)) {
05283 
05284             disposehandle (h);
05285 
05286             return (false);
05287             }
05288         }
05289 
05290     return (true);
05291     }/*addHeaderToTable*/
05292 
05293 
05294 static boolean webserverparseheaders (Handle htext, hdlhashtable hheadertable, Handle *hptr) {
05295 
05296     /*
05297     6.1d1 AR: Parse the headers of an HTTP request or response into the given table.
05298     We return the first line of the request/response.
05299     
05300     6.1d4 AR: Reviewed for proper error handling and reporting.
05301     */
05302 
05303     register long pos;
05304     long lenfirstline;
05305     long beginlabel, lenlabel, beginvalue, lenvalue;
05306     long hsize = gethandlesize (htext);
05307     Handle h;
05308 
05309     /*locate end of first line*/
05310 
05311     lenfirstline = textpatternmatch ((byte *)(*htext), hsize, STR_P_CRLF, false);
05312 
05313     if (lenfirstline == -1 || hheadertable == nil)
05314         goto done; /*there's no line terminator, we are done*/
05315 
05316     /*loop thru 2nd to nth line of headers to build the table*/
05317 
05318     pos = beginlabel = lenfirstline + stringlength (STR_P_CRLF);
05319 
05320     lenlabel = beginvalue = lenvalue = -1;
05321 
05322     while (pos < hsize) {
05323 
05324         switch ((*htext) [pos]) {
05325 
05326             case ':':
05327 
05328                 if (lenlabel == -1) { /*only pick up position of first colon on a line*/
05329 
05330                     long i = pos + 1;
05331 
05332                     lenlabel = pos - beginlabel;
05333 
05334                     while ((*htext) [i] == ' ' || (*htext) [i] == '\t')
05335                         i++; /*skip over any whitespace following the colon*/
05336 
05337                     pos = beginvalue = i;
05338 
05339                     continue; /*bypass additional incrementation of pos*/
05340                     }
05341 
05342                 break;
05343 
05344             case '\r':
05345 
05346                 if (beginvalue == -1 || lenlabel <= 0) { /*there was no colon on this line*/
05347 
05348                     lenlabel = pos - beginlabel; /*the label is the full text of the line*/
05349 
05350                     beginvalue = pos; /*the value will be empty*/
05351                     }
05352 
05353                 if (lenlabel == 0)
05354                     goto done;
05355 
05356                 lenvalue = pos - beginvalue;
05357 
05358                 if (!addHeaderToTable (htext, beginlabel, lenlabel, beginvalue, lenvalue, hheadertable))
05359                     return (false);
05360 
05361                 pos++;
05362 
05363                 if ((*htext) [pos] == '\n') /*skip over '\n' following '\r'*/
05364                     pos++;
05365 
05366                 if ((*htext) [pos] == '\r')
05367                     goto done; /*end of headers, leave while-loop*/
05368 
05369                 beginlabel = pos;
05370 
05371                 lenlabel = beginvalue = lenvalue = -1;
05372 
05373                 continue; /*bypass additional incrementation of pos*/           
05374             }
05375 
05376         pos++;
05377         }/*while*/
05378 
05379 done:   /*set up return string: first line of headers*/
05380 
05381     if (lenfirstline <= 0) {
05382 
05383         if (!newemptyhandle (&h))
05384             return (false);
05385         }
05386     else {
05387 
05388         beginlabel = 0;
05389 
05390         if (!loadfromhandletohandle (htext, &beginlabel, lenfirstline, false, &h))
05391             return (false);
05392         }
05393             
05394     *hptr = h;
05395     
05396     return (true);
05397     }/*webserverparseheaders*/
05398 
05399 
05400 static boolean webserverparsecookies (hdlhashtable hparamtable, tyvaluerecord *vreturn) {
05401 
05402     /*
05403     6.1d2 AR: Utility string for parsing the request's Cookie header.
05404     
05405     6.1d4 AR: Reviewed for proper error handling and reporting.
05406     */
05407 
05408     hdlhashtable hheaderstable, hcookiestable;
05409     tyvaluerecord val;
05410     Handle hcookie, hfirstpart;
05411     long ix, len, lencookie;
05412     bigstring bsname;
05413     boolean fl;
05414     hdlhashnode hnode;
05415     
05416     disablelangerror ();
05417     
05418     fl = langhashtablelookup (hparamtable, STR_P_REQUESTHEADERS, &val, &hnode);
05419     
05420     fl = fl && langexternalvaltotable (val, &hheaderstable, hnode);
05421     
05422     fl = fl && langhashtablelookup (hheaderstable, STR_P_COOKIE, &val, &hnode);
05423     
05424     fl = fl && copyvaluerecord (val, &val);
05425     
05426     fl = fl && coercetostring (&val);
05427     
05428     enablelangerror ();
05429     
05430     if (!fl) {
05431 
05432         setbooleanvalue (false, vreturn);   
05433 
05434         return (true);
05435         }
05436 
05437     hcookie = val.data.stringvalue; /*val is on the tmpstack*/
05438 
05439     if (!langassignnewtablevalue (hheaderstable, STR_P_COOKIES, &hcookiestable))
05440         return (false);
05441 
05442     lencookie = gethandlesize (hcookie);
05443 
05444     while (lencookie > 0) {
05445     
05446         tyvaluerecord vcookie;
05447 
05448         if (!textnthword ((ptrbyte)(*hcookie), lencookie, 1, ';', /*flstrict:*/ true, &ix, &len))
05449             break;
05450 
05451         if (!loadfromhandletohandle (hcookie, &ix, len, false, &hfirstpart))
05452             return (false);
05453 
05454         setheapvalue (hfirstpart, stringvaluetype, &vcookie);
05455 
05456         if (len < lencookie) {
05457 
05458             if (!pullfromhandle (hcookie, 0, len + 1, nil))
05459                 return (false);
05460             
05461             handlepopleadingchars (hcookie, ' ');
05462             }
05463         else
05464             sethandlesize (hcookie, 0L);
05465 
05466         if (!textnthword ((ptrbyte)(*hfirstpart), gethandlesize (hfirstpart), 1, '=', /*flstrict:*/ true, &ix, &len))
05467             break;
05468         
05469         if (len > 0) {
05470 
05471             texttostring (*hfirstpart, len, bsname);
05472 
05473             if (len >= gethandlesize (hfirstpart))
05474                 sethandlesize (hfirstpart, 0L);
05475             else
05476                 if (!pullfromhandle (hfirstpart, 0, len + 1, nil))
05477                     return (false);
05478 
05479             if (!hashtableassign (hcookiestable, bsname, vcookie))
05480                 return (false);
05481 
05482             exemptfromtmpstack (&vcookie);
05483             }
05484 
05485         lencookie = gethandlesize (hcookie);
05486         }
05487 
05488     setbooleanvalue (true, vreturn);
05489 
05490     return (true);  
05491     }/*webserverparsecookies*/
05492 
05493 
05494 
05495 static boolean webservergetserverstring (tyvaluerecord *vreturn) {
05496 
05497     /*
05498     6.1d2 AR: Return a string identifying the server software, i.e. UserLand Frontier/6.1d2-WinNT
05499     
05500     6.1d4 AR: Reviewed for proper error handling and reporting.
05501     */
05502 
05503     Handle h = nil;
05504     tyvaluerecord vversion, vos;
05505 
05506     if (!newtexthandle (STR_P_SERVERSTRING, &h))
05507         return (false);
05508 
05509     if (!frontierversion (&vversion))
05510         goto exit;
05511 
05512     if (!sysos (&vos))
05513         goto exit;
05514 
05515     if (!parsedialoghandle (h, vversion.data.stringvalue, vos.data.stringvalue, nil, nil))
05516         goto exit;
05517         
05518     return (setheapvalue (h, stringvaluetype, vreturn));
05519 
05520 exit:
05521 
05522     disposehandle (h);
05523 
05524     return (false);
05525     }/*webservergetserverstring*/
05526 
05527 
05528 static boolean webserverbuilderrorpage (Handle hshort, Handle hlong, Handle *hpage) {
05529 
05530     /*
05531     6.1d2 AR: Build an error page.
05532     
05533     6.1d4 AR: Reviewed for proper error handling and reporting.
05534     */
05535 
05536     Handle h;
05537 
05538     if (!newtexthandle (STR_P_ERRORPAGETEMPLATE, &h))
05539         return (false);
05540 
05541     if (!parsedialoghandle (h, hshort, hlong, nil, nil)) {
05542 
05543         disposehandle (h);
05544 
05545         return (false);
05546         }
05547 
05548     *hpage = h;
05549 
05550     return (true);  
05551 
05552     }/*webserverbuilderrorpage*/
05553 
05554 
05555 static boolean webserverbuilderrorpagefromstrings (bigstring bsshort, bigstring bslong, Handle *hpage) {
05556 
05557     /*
05558     6.1d2 AR: Build an error page.
05559     
05560     6.1d4 AR: Reviewed for proper error handling and reporting.
05561     */
05562 
05563     hdlhashtable hcodestable;
05564     Handle hshort = nil;
05565     Handle hlong = nil;
05566     boolean returnval = false;
05567     boolean fl;
05568     tyvaluerecord val;
05569     hdlhashnode hnode;
05570 
05571     if (!newtexthandle (bsshort, &hshort))
05572         goto exit;
05573 
05574     if (!newtexthandle (bslong, &hlong))
05575         goto exit;
05576     
05577     disablelangerror ();
05578     
05579     fl = langfastaddresstotable (builtinstable, STR_P_WEBSERVERDATARESPONSES, &hcodestable);
05580     
05581     fl = fl && langhashtablelookup (hcodestable, bsshort, &val, &hnode);
05582     
05583     if (fl && val.valuetype != stringvaluetype) {
05584     
05585         fl = fl && copyvaluerecord (val, &val);
05586         
05587         fl = fl && coercevalue (&val, stringvaluetype);
05588         }
05589     
05590     enablelangerror ();
05591     
05592     if (fl) {
05593         
05594         if (!pushtexthandle (STR_P_SPACE, hshort))
05595             goto exit;
05596             
05597         if (!pushhandle (val.data.stringvalue, hshort))
05598             goto exit;
05599         }
05600 
05601     if (!webserverbuilderrorpage (hshort, hlong, hpage))
05602         goto exit;
05603 
05604     returnval = true;
05605 
05606 exit:
05607 
05608     disposehandle (hshort);
05609 
05610     disposehandle (hlong);
05611 
05612     return (returnval); 
05613 
05614     }/*webserverbuilderrorpage*/
05615 
05616 
05617 static boolean writetableitemtostreamvisit (tyvaluerecord val, ptrhandlestream sptr, bigstring bs) {
05618 
05619     /*
05620     6.1d2 AR: Utility function for webserverbuildresponse.
05621     
05622     6.1d4 AR: Reviewed for proper error handling and reporting.
05623     */
05624 
05625     if (val.valuetype == listvaluetype) { /*recurse*/
05626         long ix = 1;
05627         tyvaluerecord vitem;
05628 
05629         while (getnthlistval (val.data.listvalue, ix++, nil, &vitem))
05630             if (!writetableitemtostreamvisit (vitem, sptr, bs))
05631                 return (false);
05632         }
05633     
05634     else {
05635 
05636         if (val.valuetype != stringvaluetype)
05637             if (!copyvaluerecord (val, &val) || !coercetostring (&val))
05638                 return (false);
05639 
05640         if (!writehandlestreamstring (sptr, bs))
05641             return (false);
05642 
05643         if (!writehandlestreamstring (sptr, STR_P_COLON))
05644             return (false);
05645         
05646         if (!writehandlestreamhandle (sptr, val.data.stringvalue))
05647             return (false);
05648         
05649         if (!writehandlestreamstring (sptr, STR_P_CRLF))
05650             return (false);
05651         }
05652 
05653     return (true);
05654     } /*writetableitemtostreamvisit*/
05655     
05656     
05657 /*  system.verbs.builtins.webserver.util.buildResponse */
05658 
05659 static boolean webserverbuildresponse (bigstring bscode, hdlhashtable hheaderstable, Handle hbody, tyvaluerecord *vreturn) {
05660 
05661     /*
05662     6.1d2 AR: Build the HTTP response headers and optionally body.
05663     
05664     6.1d4 AR: Reviewed for proper error handling and reporting.
05665     */
05666 
05667     handlestream s;
05668     hdlhashtable hcodestable;
05669     tyvaluerecord val;
05670     long bodysize = gethandlesize (hbody);
05671     boolean fldisposetable = false;
05672     boolean fl;
05673     long ix;
05674     hdlhashnode hn;
05675     hdlhashnode hnode;
05676 
05677     openhandlestream (nil, &s);
05678 
05679     if (!writehandlestreamstring (&s, STR_P_HTTP11))
05680         goto exit;
05681 
05682     if (!writehandlestreamstring (&s, bscode))
05683         goto exit;
05684     
05685     if (!writehandlestreamchar (&s, chspace))
05686         goto exit;
05687     
05688     disablelangerror ();
05689     
05690     fl = langfastaddresstotable (builtinstable, STR_P_WEBSERVERDATARESPONSES, &hcodestable);
05691     
05692     fl = fl && langhashtablelookup (hcodestable, bscode, &val, &hnode);
05693     
05694     fl = fl && copyvaluerecord (val, &val);
05695     
05696     fl = fl && coercevalue (&val, stringvaluetype);
05697     
05698     enablelangerror ();
05699     
05700     if (fl) {       
05701         if (!writehandlestreamhandle (&s, val.data.stringvalue))
05702             goto exit;
05703         }
05704     else
05705         if (!writehandlestreamstring (&s, STR_P_UNKNOWN))
05706             goto exit;
05707     
05708     if (!writehandlestreamstring (&s, STR_P_CRLF))
05709         goto exit;
05710     
05711     /* create headers table if it doesn't exist yet */
05712 
05713     if (hheaderstable == nil) {
05714     
05715         if (!tablenewtablevalue (&hheaderstable, &val))
05716             goto exit;
05717         
05718         fldisposetable = true;
05719         }
05720     
05721     /* add Connection: close to header table */
05722 
05723     if (!langassignstringvalue (hheaderstable, STR_P_CONNECTION, STR_P_CLOSE))
05724         goto exit;
05725     
05726     /* add Date: Sat, 29 Nov 1997 00:51:47 GMT to header table */
05727     
05728     if (!datenetstandardstring (timenow (), &val))
05729         goto exit;
05730     
05731     if (!hashtableassign (hheaderstable, STR_P_DATE, val))
05732         goto exit;
05733 
05734     exemptfromtmpstack (&val);
05735 
05736     /* add Server: UserLand Frontier/6.1d1-NT to header table */
05737     
05738     if (!webservergetserverstring (&val))
05739         goto exit;
05740     
05741     if (!hashtableassign (hheaderstable, STR_P_SERVER, val))
05742         goto exit;
05743 
05744     exemptfromtmpstack (&val);
05745 
05746     /* add Content-Length header if there's a response body */
05747     
05748     if (bodysize > 0)
05749         if (!langassignlongvalue (hheaderstable, STR_P_CONTENT_LENGTH, bodysize))
05750             goto exit;
05751     
05752     /* loop thru the headers table and append header lines */
05753     
05754     ix = 0;
05755     
05756     /***NEED TO OPTIMIZE*** RAB 1/3/00 */
05757     /* This code using while (hashgetnthnode) is actually going from 0 to n, But because it is using 
05758        hashgetnthnode, it is actually looping through the link list n! times.  The first is just 0,
05759        then 0, 1, then 0,1,2 then 0,1,2,3, etc all the way to 0,1,2,...,n for walking the linked list.
05760        This whould just use the nomad pointer and walk the list itself one time only n not n! !! */
05761     /* AR: It's actually looping (n+1)(n+2)/2 times which is a lot less bad than n! would have been. */
05762     while (hashgetnthnode (hheaderstable, ix++, &hn))
05763         if (!writetableitemtostreamvisit ((**hn).val, &s, (**hn).hashkey))
05764             goto exit;
05765 
05766     /* terminate the response headers with additional CRLF sequence*/
05767     
05768     if (!writehandlestreamstring (&s, STR_P_CRLF))
05769         goto exit;
05770 
05771     /* append response body if specified */
05772     
05773     if (bodysize > 0)
05774         if (!writehandlestreamhandle (&s, hbody))
05775             goto exit;
05776 
05777     /* done, return response */
05778     
05779     if (fldisposetable)
05780         tabledisposetable (hheaderstable, false);
05781 
05782     return (setheapvalue (closehandlestream (&s), stringvaluetype, vreturn));
05783 
05784 exit: /* there was an error! */
05785 
05786     disposehandlestream (&s);
05787 
05788     if (fldisposetable)
05789         tabledisposetable (hheaderstable, false);
05790     
05791     return (false);
05792     } /*webserverbuildresponse*/
05793 
05794 
05795 static boolean webserveraddtoerrorlog (tyaddress *adrmethod, bigstring bstype, bigstring bserror);
05796 
05797 
05798 static boolean webservercallfilters (tyaddress *pta, bigstring bstable, bigstring bserrortype) {
05799 
05800     /*
05801     6.1d2 AR: A utility function for calling all scripts in a given Frontier.root table.
05802     
05803     6.1d4 AR: Reviewed for proper error handling and reporting.
05804 
05805     6.1d7 AR: Modified to walk the table in sorted order.
05806     */
05807 
05808     tyvaluerecord val;
05809     tyaddress adrscript;
05810     hdlhashtable ht;
05811     long i = 0;
05812     hdlhashnode x;
05813     bigstring bserror;
05814     langerrormessagecallback savecallback;
05815     ptrvoid saverefcon;
05816     boolean fl;
05817 
05818     
05819     if (!langfastaddresstotable (roottable, bstable, &ht))
05820         return (false);
05821     
05822     while (hashgetnthnode (ht, i++, &x)) {
05823 
05824         setemptystring (bserror);
05825     
05826         langtraperrors (bserror, &savecallback, &saverefcon);
05827 
05828         gethashkey (x, adrscript.bs);
05829 
05830         adrscript.ht = ht;
05831 
05832         fl = followaddress (&adrscript); /*6.1b5 AR: don't ignore return value of this call*/
05833             
05834         fl = fl && langcallscriptwithaddress (&adrscript, pta, nil, &val);
05835 
05836         languntraperrors (savecallback, saverefcon, !fl);
05837 
05838         fllangerror = false;
05839 
05840         if (!ingoodthread ())
05841             return (false);
05842 
05843         if (!fl && !webserveraddtoerrorlog (&adrscript, bserrortype, bserror))
05844             return (false);
05845         } /*while*/
05846     
05847     return (true);
05848     } /*webservercallfilters*/
05849 
05850 
05851 
05852 #ifdef MACVERSION
05853 #pragma mark === kernelized webserver ===
05854 #endif
05855 
05856 
05857 /*  For reference: HTTP/1.1 status codes
05858 
05859     Informational 1xx
05860         100 Continue
05861         101 Switching Protocols
05862         
05863     Successful 2xx
05864         200 OK
05865         201 Created
05866         202 Accepted
05867         203 Non-Authoritative Information
05868         204 No Content
05869         205 Reset Content
05870         206 Partial Content
05871         
05872     Redirection 3xx
05873         300 Multiple Choices
05874         301 Moved Permanently
05875         302 Moved Temporarily
05876         303 See Other
05877         304 Not Modified
05878         305 Use Proxy
05879         
05880     Client Error 4xx
05881         400 Bad Request
05882         401 Unauthorized
05883         402 Payment Required
05884         403 Forbidden
05885         404 Not Found
05886         405 Method Not Allowed
05887         406 Not Acceptable
05888         407 Proxy Authentication Required
05889         408 Request Timeout
05890         409 Conflict
05891         410 Gone
05892         411 Length Required
05893         412 Precondition Failed
05894         413 Request Entity Too Large
05895         414 Request-URI Too Long
05896         415 Unsupported Media Type
05897         
05898     Server Error 5xx
05899         500 Internal Server Error
05900         501 Not Implemented
05901         502 Bad Gateway
05902         503 Service Unavailable
05903         504 Gateway Timeout
05904         505 HTTP Version Not Supported
05905 */
05906 
05907 #if 0
05908 
05909 static boolean webservergetpref (bigstring bsprefname, tyvaluerecord *vreturn) {
05910     
05911     /*
05912     6.1d2 AR: A utility function for getting a pref from user.webserver.prefs.
05913     If no value is found in that table, we return false in vreturn.
05914     
05915     6.1d4 AR: Reviewed for proper error handling and reporting.
05916     */
05917     
05918     hdlhashtable hprefstable;
05919     tyvaluerecord val;
05920     boolean fl;
05921     hdlhashnode hnode;
05922     
05923     disablelangerror ();
05924     
05925     fl = langfastaddresstotable (roottable, STR_P_USERWEBSERVERPREFS, &hprefstable)
05926          && langhashtablelookup (hprefstable, bsprefname, &val, &hnode);
05927     
05928     enablelangerror ();
05929     
05930     if (!fl)
05931         return (setbooleanvalue (false, vreturn));
05932     
05933     if (!copyvaluerecord (val, vreturn))
05934         return (false);
05935         
05936     if ((*vreturn).valuetype == externalvaluetype)
05937         if (langexternalgettype (*vreturn) == idwordprocessor)
05938             coercetostring (vreturn);
05939     
05940     return (true);
05941     } /*webservergetpref*/
05942 
05943 #endif
05944 
05945 
05946 static boolean webservergetrespondertableaddress (bigstring bsname, tyaddress *adr) {
05947     
05948     /*
05949     6.1d2 AR: Determine the address of the responder table based on the given name.
05950 
05951     6.1d4 AR: Reviewed for proper error handling and reporting.
05952     */
05953 
05954     hdlhashtable hrespondertable;
05955     tyvaluerecord val;
05956     hdlhashnode hnode;
05957     
05958     if (!langfastaddresstotable (roottable, STR_P_USERWEBSERVERRESPONDERS, &hrespondertable))
05959         return (false);
05960     
05961     if (!langhashtablelookup (hrespondertable, bsname, &val, &hnode))
05962         return (false);
05963     
05964     if (val.valuetype == addressvaluetype) { //follow the address
05965         
05966         if (!getaddressvalue (val, &(*adr).ht, (*adr).bs))
05967             return (false);
05968         }
05969     else {
05970         (*adr).ht = hrespondertable;
05971         
05972         copystring (bsname, (*adr).bs);
05973         }
05974     
05975     return (true);
05976     } /*webservergetrespondertableaddress*/
05977 
05978 
05979 static boolean webserverlocaterespondercontextbuilder (hdlhashtable hpt, hdlhashtable *hnew) {
05980 
05981     /*
05982     6.1d2 AR: Build context for responder search.
05983     
05984     6.1d4 AR: Reviewed for proper error handling and reporting.
05985     */
05986         
05987     hdlhashtable ht, hheadertable;
05988     
05989     if (!langfastaddresstotable (hpt, STR_P_REQUESTHEADERS, &hheadertable))
05990         return (false);
05991     
05992     if (!newhashtable (&ht))
05993         return (false);
05994     
05995     (**ht).fllocaltable = true;
05996 
05997     (**ht).lexicalrefcon = 0L;
05998     
05999     //  with adrParamTable^, adrParamTable^.requestHeaders
06000 
06001     if (!langpushwithtable (ht, hpt))
06002         goto error;
06003     
06004     if (!langpushwithtable (ht, hheadertable))
06005         goto error;
06006 
06007     *hnew = ht;
06008 
06009     return (true);
06010 
06011 error:
06012     disposehashtable (ht, false);
06013     
06014     return (false); 
06015     } /*webserverlocaterespondercontextbuilder*/
06016 
06017 
06018 static boolean webserverlocateresponder (hdlhashtable hparamtable, bigstring bs, tyaddress *adrrespondertable) {
06019 
06020     /*
06021     6.1d2 AR: Determine which responder should handle this request.
06022     Return true if we found it.
06023     
06024     6.1d4 AR: Reviewed for proper error handling and reporting.
06025 
06026     7.0.1 PBS: Fixed a bug when a responder is the address of a responder table. If, for instance, there is an address
06027     at user.webserver.responders.foo, and it points to a table named fooResponder, the responder would not get
06028     called because "foo" != "fooResponder".
06029     */
06030     
06031     hdlhashtable ht, hresponderstable, hcontext;
06032     hdlhashnode hnode;
06033     register short i = 0;
06034     boolean flfound = false;
06035     boolean flenabled;
06036     bigstring bstemp, bskey;
06037     tyvaluerecord v, val, vcondition;
06038     tyaddress adrscript, adrparamtable;
06039     hdlhashnode hnode2;
06040     
06041     if (!findinparenttable (hparamtable, &adrparamtable.ht, adrparamtable.bs))
06042         return (false);
06043     
06044     if (!langfastaddresstotable (roottable, STR_P_USERWEBSERVERRESPONDERS, &hresponderstable))
06045         return (false);
06046 
06047     if (!webserverlocaterespondercontextbuilder (hparamtable, &hcontext))
06048         return (false);
06049     
06050     disablelangerror ();
06051     
06052     while (hashgetnthnode (hresponderstable, i++, &hnode)) {
06053 
06054         val = (**hnode).val;
06055 
06056         gethashkey (hnode, bskey); /*7.0.1 PBS: save the key before we resolve addresses.*/
06057                         
06058         if (val.valuetype == addressvaluetype) {
06059 
06060             if (!getaddressvalue (val, &ht, bstemp))
06061                 continue;
06062 
06063             if (!langsymbolreference (ht, bstemp, &val, &hnode))
06064                 continue;
06065             }
06066             
06067         if (!langexternalvaltotable (val, &ht, hnode))
06068             continue;
06069             
06070         if (!langlookupbooleanvalue (ht, STR_P_ENABLED, &flenabled) || !flenabled)
06071             continue;
06072             
06073         if (!hashtablelookup (ht, STR_P_CONDITION, &vcondition, &hnode2))
06074             continue;
06075             
06076         if ((vcondition.valuetype == codevaluetype)
06077                 || (vcondition.valuetype == externalvaluetype && langexternalgettype (vcondition) == idscriptprocessor)) {
06078                 
06079             copystring (STR_P_CONDITION, adrscript.bs);
06080                 
06081             adrscript.ht = ht;
06082             
06083             if (!langcallscriptwithaddress (&adrscript, &adrparamtable, hcontext, &v)) {
06084 
06085                 if (!ingoodthread ()) {/*unwind quickly*/
06086                     enablelangerror ();
06087                     goto exit;
06088                     }
06089 
06090                 continue;
06091                 }
06092             }
06093         else {
06094             Handle htext;
06095                 
06096             if (!copyvaluerecord (vcondition, &val)
06097                     || !coercetostring (&val)
06098                     || !copyhandle (val.data.stringvalue, &htext))
06099                 continue;
06100 
06101             if (!langruntextwithcontext (htext, hcontext, &v)) { /* consumes htext */
06102 
06103                 if (!ingoodthread ()) { /*unwind quickly*/
06104                     enablelangerror ();
06105                     goto exit;
06106                     }
06107 
06108                 continue;
06109                 }
06110             }
06111                 
06112         if (coercetoboolean (&v) && v.data.flvalue) { /* found it */
06113                 
06114             //gethashkey (hnode, bs); /*7.0.1 PBS: commented out. We may have resolved one or more addresses.*/
06115 
06116             copystring (bskey, bs); /*7.0.1 PBS: use the key we got before resolving addresses.*/
06117                 
06118             flfound = true;
06119                 
06120             break;
06121             }   
06122         } /*while*/
06123     
06124     enablelangerror ();
06125 
06126     /* fall back to default responder if neccessary */
06127 
06128     if (!flfound) {
06129         hdlhashtable hprefstable;
06130         
06131         if (langfastaddresstotable (roottable, STR_P_USERWEBSERVERPREFS, &hprefstable)
06132                 && langlookupstringvalue (hprefstable, STR_P_DEFAULTRESPONDER, bs))
06133             flfound = true;
06134         }
06135     
06136     if (flfound)
06137         flfound = webservergetrespondertableaddress (bs, adrrespondertable);
06138 
06139 exit:
06140 
06141     disposehashtable (hcontext, false);
06142 
06143     return (flfound);   
06144     } /*webserverlocateresponder*/
06145 
06146 
06147 static boolean visitmethods (hdlhashnode hnode , Handle *h) {
06148     
06149     /*
06150     6.1d4 AR: Reviewed for proper error handling and reporting.
06151     */
06152 
06153     bigstring bs;
06154     
06155     if (gethandlesize (*h) > 0)
06156         if (!pushtexthandle (BIGSTRING ("\x02" ", "), *h))
06157             return (false);
06158     
06159     gethashkey (hnode, bs);
06160     
06161     if (!pushtexthandle (bs, *h))
06162         return (false);
06163     
06164     return (true);
06165     } /*visitmethods*/
06166 
06167 
06168 static boolean webservermethodnotallowed (hdlhashtable hmethodstable, bigstring bsmethod, tyvaluerecord *vreturn) {
06169     
06170     /*
06171     6.1d4 AR: Reviewed for proper error handling and reporting.
06172 
06173     6.1d15 AR: Fixed crashing bug, we were disposing of h even though it had been assigned to ht.
06174     */
06175 
06176     bigstring bscode, bslong;
06177     Handle h = nil;
06178     Handle hpage = nil;
06179     hdlhashtable ht = nil;
06180     hdlhashnode hn;
06181     long ix = 0;
06182     boolean flresult = false;
06183 
06184     if (!newemptyhandle (&h))
06185         goto done;
06186 
06187     while (hashgetnthnode (hmethodstable, ix++, &hn))
06188         if (!visitmethods (hn, &h))
06189             goto done;
06190 
06191     if (!newhashtable (&ht))
06192         goto done;
06193 
06194     if (!langassigntextvalue (ht, STR_P_ALLOW, h))
06195         goto done;
06196 
06197     h = nil; /*it's now part of ht, make sure we don't dispose it twice*/
06198     
06199     numbertostring (405, bscode);
06200 
06201     parsedialogstring (STR_P_METHOD_NOT_ALLOWED, bsmethod, nil, nil, nil, bslong);
06202 
06203     if (!webserverbuilderrorpagefromstrings (bscode, bslong, &hpage))
06204         goto done;
06205 
06206     if (!webserverbuildresponse (bscode, ht, hpage, vreturn))
06207         goto done;
06208 
06209     flresult = true;
06210 
06211 done:
06212 
06213     disposehandle (h);
06214     
06215     disposehandle (hpage);
06216     
06217     disposehashtable (ht, false);
06218     
06219     return (flresult);
06220     } /*webservermethodnotallowed*/
06221     
06222         
06223 static boolean webservergetmethod (hdlhashtable hpt, tyaddress *adrresponder, tyaddress *adrmethod, tyvaluerecord *vreturn) {
06224 
06225     /*
06226     6.1d2 AR: Determine which responder method should handle this request.
06227     Build an error message if the specified method is not allowed.
06228         
06229     6.1d4 AR: Reviewed for proper error handling and reporting.
06230     */
06231 
06232     hdlhashtable hrespondertable, hmethodstable;
06233     bigstring bsmethod;
06234     tyvaluerecord val;
06235     boolean fl;
06236     hdlhashnode hnode;
06237 
06238     if (!langlookupstringvalue (hpt, STR_P_METHOD, bsmethod))
06239         return (false);
06240     
06241     if (!langhashtablelookup ((*adrresponder).ht, (*adrresponder).bs, &val, &hnode)
06242             || !langexternalvaltotable (val, &hrespondertable, hnode))
06243         return (false);
06244     
06245     if (!langlookupvaluefollow (hrespondertable, STR_P_METHODS, &val, &hnode)
06246             || !langexternalvaltotable (val, &hmethodstable, hnode))
06247         return (false);
06248 
06249     copystring (bsmethod, (*adrmethod).bs);
06250     
06251     (*adrmethod).ht = hmethodstable;
06252 
06253     disablelangerror ();
06254     
06255     fl = followaddress (adrmethod);
06256     
06257     enablelangerror ();
06258 
06259     if (fl)
06260         return (true);
06261 
06262     copystring (STR_P_ANY, (*adrmethod).bs);
06263     
06264     (*adrmethod).ht = hmethodstable;
06265 
06266     disablelangerror ();
06267     
06268     fl = followaddress (adrmethod);
06269 
06270     enablelangerror ();
06271     
06272     if (fl)
06273         return (true);
06274 
06275     return (webservermethodnotallowed (hmethodstable, bsmethod, vreturn));
06276     } /*webservergetmethod*/
06277 
06278 
06279 static boolean webserveraddtoerrorlog (tyaddress *adrmethod, bigstring bstype, bigstring bserror) {
06280 
06281     /*
06282     6.1d16 AR: Report webserver method errors.
06283     */
06284 
06285     Handle htext = nil;
06286     Handle htype = nil;
06287     Handle herror = nil;
06288     Handle haddress = nil;
06289     bigstring bsaddress;
06290     tyvaluerecord val, vparams, vaddress;
06291     hdllistrecord hlist;
06292 
06293     disablelangerror ();
06294 
06295     if (!opnewlist (&hlist, false))
06296         goto exit;
06297 
06298     if (!setheapvalue ((Handle) hlist, listvaluetype, &vparams))
06299         goto exit;
06300 
06301     if (!setaddressvalue ((*adrmethod).ht, (*adrmethod).bs, &vaddress))
06302         goto exit;
06303 
06304     if (!getaddresspath (vaddress, bsaddress))
06305         goto exit;
06306 
06307     if (!newtexthandle (BIGSTRING ("\x0A" "^0: ^1: ^2"), &htext)
06308             || !newtexthandle (bstype, &htype)
06309             || !newtexthandle (bserror, &herror)
06310             || !newtexthandle (bsaddress, &haddress)
06311             || !parsedialoghandle (htext, htype, haddress, herror, nil)) {
06312 
06313         disposehandle (htext);
06314 
06315         disposehandle (htype);
06316 
06317         disposehandle (herror);
06318 
06319         disposehandle (haddress);
06320 
06321         goto exit;
06322         }
06323     
06324     disposehandle (htype);
06325 
06326     disposehandle (herror);
06327 
06328     disposehandle (haddress);
06329 
06330     if (!langpushlisttext (hlist, htext)) /*consumes htext*/
06331         goto exit;
06332 
06333     if (!langpushliststring (hlist, BIGSTRING ("\x09" "webserver")))
06334         goto exit;
06335 
06336     if (!langrunscript (BIGSTRING ("\x1D" "system.verbs.builtins.log.add"), &vparams, nil, &val))
06337         goto exit;
06338 
06339 exit:
06340     enablelangerror ();
06341 
06342     return (true);
06343     } /*webservererrorlog*/
06344 
06345 
06346 static boolean webservercallresponder (tyaddress *pta, tyaddress *adrresponder, tyvaluerecord *vreturn) {
06347     
06348     /*
06349     6.1d2 AR: Call responder, run postfilters, and build response.
06350         
06351     6.1d4 AR: Reviewed for proper error handling and reporting.
06352     
06353     6.2b10 AR: Implemented writing of file to stream.
06354     */
06355 
06356     tyaddress adrscript;
06357     hdlhashtable hparamtable;
06358     bigstring bserror;
06359     langerrormessagecallback savecallback;
06360     ptrvoid saverefcon;
06361     hdlhashnode hnode;
06362 
06363     if (!langsuretablevalue ((*pta).ht, (*pta).bs, &hparamtable))
06364         return (false);
06365 
06366     /* locate method, possibly switch to the ANY method */
06367     
06368     setbooleanvalue (false, vreturn);
06369 
06370     if (!webservergetmethod (hparamtable, adrresponder, &adrscript, vreturn))
06371         return (false);
06372     
06373     if ((*vreturn).valuetype == stringvaluetype) /* webservermethod created an error response */
06374         return (true);
06375 
06376     /* set up our own error reporting */
06377 
06378     setemptystring (bserror);
06379     
06380     langtraperrors (bserror, &savecallback, &saverefcon);
06381 
06382     /* call the responder method */
06383 
06384     if (!langcallscriptwithaddress (&adrscript, pta, nil, vreturn))
06385         goto internal_error;
06386     
06387     /* build response if neccessary */
06388     
06389     if (((*vreturn).valuetype == booleanvaluetype) && ((*vreturn).data.flvalue == true)) {
06390         
06391         tyvaluerecord val;
06392         bigstring bscode;
06393         hdlhashtable hresponseheaderstable;
06394         
06395         /* run post-filters */
06396         
06397         if (!webservercallfilters (pta, STR_P_USERWEBSERVERPOSTFILTERS, STR_P_POSTFILTERERROR))
06398             goto internal_error;
06399 
06400         /* build response */
06401         
06402         if (!langlookupstringvalue (hparamtable, STR_P_CODE, bscode))
06403             goto internal_error;
06404 
06405         if (!langsuretablevalue (hparamtable, STR_P_RESPONSEHEADERS, &hresponseheaderstable))
06406             goto internal_error;
06407 
06408         if (!langhashtablelookup (hparamtable, STR_P_RESPONSEBODY, &val, &hnode))
06409             goto internal_error;
06410 
06411 #if 0       
06412         if (val.valuetype == filespecvaluetype) { /* 6.2b10 AR: write the file itself to the stream */
06413             
06414             tyvaluerecord vserve;
06415             
06416             if (!webservergetpref (BIGSTRING ("\x19" "flEnableDirectFileServing"), &vserve))
06417                 goto internal_error;
06418             
06419             if (!coercetoboolean (&vserve))
06420                 goto internal_error;
06421             
06422             if (vserve.data.flvalue) {
06423                 
06424                 unsigned long stream;
06425                 unsigned long fsize;
06426                 tyvaluerecord vheader;
06427                 tyfilespec fs = **val.data.filespecvalue;
06428                 
06429                 if (!filesize (&fs, &fsize))
06430                     goto internal_error;    
06431 
06432                 if (!langassignlongvalue (hresponseheaderstable, STR_P_CONTENT_LENGTH, fsize))
06433                     goto internal_error;    
06434 
06435                 if (!webserverbuildresponse (bscode, hresponseheaderstable, nil, &vheader))
06436                     goto internal_error;    
06437 
06438                 if (!langlookuplongvalue (hparamtable, STR_P_STREAM, &stream))
06439                     goto internal_error;    
06440                 
06441                 if (!fwsNetEventWriteFileToStream (stream, vheader.data.stringvalue, nil, &fs))
06442                     goto internal_error;    
06443                 
06444                 if (!setbooleanvalue (true, vreturn))
06445                     goto internal_error;
06446                 
06447                 goto done;
06448                 }
06449             }
06450 #endif
06451 
06452         if (!copyvaluerecord (val, &val) || !coercetostring (&val))
06453             goto internal_error;
06454 
06455         if (!webserverbuildresponse (bscode, hresponseheaderstable, val.data.stringvalue, vreturn))
06456             goto internal_error;
06457         }
06458 
06459 /*done:*/
06460 
06461     languntraperrors (savecallback, saverefcon, false);
06462 
06463     fllangerror = false;
06464 
06465     return (true);
06466 
06467 internal_error: {
06468 
06469         Handle hpage;
06470         bigstring bscode;
06471 
06472         /* tear down our error handling */
06473 
06474         languntraperrors (savecallback, saverefcon, true);
06475 
06476         fllangerror = false;
06477 
06478         if (!ingoodthread ()) /*unwind quickly*/
06479             return (false);
06480 
06481         /* log webserver error */
06482 
06483         webserveraddtoerrorlog (&adrscript, STR_P_RESPONDERERROR, bserror);
06484 
06485         /* unlock our semaphores */
06486 
06487         langreleasesemaphores (nil);
06488 
06489         /* webserver.util.buildResponse (500, nil, webserver.util.buildErrorPage ("500 Server Error", tryError)) */
06490 
06491         numbertostring (500L, bscode);
06492     
06493         if (!webserverbuilderrorpagefromstrings (bscode, bserror, &hpage))
06494             return (false);
06495 
06496         if (!webserverbuildresponse (bscode, nil, hpage, vreturn)) {
06497 
06498             disposehandle (hpage);
06499 
06500             return (false);
06501             }
06502 
06503         disposehandle (hpage);
06504         }
06505 
06506     return (true);
06507     } /*webservercallresponder*/
06508 
06509 
06510 static boolean webserverdispatch (tyaddress *pta, tyvaluerecord *vreturn) {
06511 
06512     /*
06513     6.1d2 AR: Dispatch the request to the appropriate responder.
06514         
06515     6.1d4 AR: Reviewed for proper error handling and reporting.
06516     */
06517 
06518     hdlhashtable hparamtable, hresponseheaderstable;
06519     tyaddress adrresponder;
06520     bigstring bsrespondername;
06521     Handle h = nil;
06522     
06523     if (!langsuretablevalue ((*pta).ht, (*pta).bs, &hparamtable))
06524         return (false);
06525     
06526     /* call pre-filters */
06527     
06528     if (!webservercallfilters (pta, STR_P_USERWEBSERVERPREFILTERS, STR_P_PREFILTERERROR))
06529         return (false);
06530     
06531     /* determine name of responder and address of responder table */
06532     
06533     if (!webserverlocateresponder (hparamtable, bsrespondername, &adrresponder))
06534         return (false);
06535     
06536     /* initialize paramtable fields */
06537     
06538     if (!langassignstringvalue (hparamtable, STR_P_RESPONDER, bsrespondername))
06539         return (false);
06540     
06541     if (!langassignaddressvalue (hparamtable, STR_P_RESPONDERTABLEADR, &adrresponder))
06542         return (false);
06543 
06544     if (!langassignlongvalue (hparamtable, STR_P_CODE, 200))
06545         return (false);
06546 
06547     if (!newemptyhandle (&h))
06548         return (false);
06549 
06550     if (!langassigntextvalue (hparamtable, STR_P_RESPONSEBODY, h)) {
06551     
06552         disposehandle (h);
06553         
06554         return (false);
06555         }
06556     
06557     if (!langassignnewtablevalue (hparamtable, STR_P_RESPONSEHEADERS, &hresponseheaderstable))
06558         return (false);
06559 
06560     /* call responder, run postfilters, and build response */       
06561 
06562     return (webservercallresponder (pta, &adrresponder, vreturn));
06563     } /*webserverdispatch*/
06564     
06565 
06566 static boolean webserverreadrequest (hdlhashtable ht, Handle h, long *errorcode, bigstring bserror) {
06567 
06568     /*
06569     6.1d2 AR: Make sure we have the whole request, reading from the stream as neccessary.
06570     We assume that we are allowed to modify h.
06571         
06572     6.1d4 AR: Reviewed for proper error handling and reporting.
06573 
06574     6.1b9 AR: Adapted to fwsNetEventsReadStreamUntil changes.
06575     */
06576 
06577     hdlhashtable hheaderstable;
06578     Handle hrequestbody = nil;
06579     Handle hpattern = nil;
06580     Handle hfirstline = nil;
06581     long contentlength;
06582     long stream = -1;
06583     long timeout = 30;
06584     boolean flresult = false;
06585     boolean fl;
06586 
06587     /* Try to lookup stream and timeout in paramtable */
06588 
06589     disablelangerror (); /* ignoring errors since we have default values */
06590 
06591     langlookuplongvalue (ht, STR_P_STREAM, &stream);
06592 
06593     langlookuplongvalue (ht, STR_P_TIMEOUT, &timeout);
06594 
06595     enablelangerror ();
06596 
06597     /* Make sure we have the request headers */
06598 
06599     if (!newtexthandle (STR_P_CRLFCRLF, &hpattern))
06600         goto exit;
06601 
06602     if (h != nil) { /* Assume we don't own the handle, so make a copy */
06603         if (!copyhandle (h, &h))
06604             goto exit;
06605         }
06606     else { /* Read headers*/
06607 
06608         if (!newemptyhandle (&h))
06609             goto exit;
06610 
06611         if (!fwsNetEventReadStreamUntil (stream, h, hpattern, timeout))
06612             goto exit;
06613         }
06614 
06615     /* For robustness remove leading cr/lf's and spaces from request */
06616     /*{
06617     
06618         long i, hsize = gethandlesize (h);
06619         byte ch;
06620 
06621         for (i = 0; i < hsize; i++) {
06622 
06623             ch = (*(byte **)h) [i];
06624 
06625             if ((ch != '\r') && (ch != '\n') && (ch != chspace))
06626                 break;
06627             }
06628         
06629         if (i > 0)
06630             pullfromhandle (h, 0, i, nil);  
06631         }
06632 */  
06633     /* Create requestHeaders sub-table and parse headers */
06634 
06635     if (!langassignnewtablevalue (ht, STR_P_REQUESTHEADERS, &hheaderstable))
06636         goto exit;
06637 
06638     if (!webserverparseheaders (h, hheaderstable, &hfirstline))
06639         goto exit;
06640 
06641 
06642     /* Check for Expect header -- if expecting a Continue response, send it, otherwise fail*/
06643 
06644     if (hashtablesymbolexists (hheaderstable, STR_P_EXPECT)) { /*PBS 7.0b43: handle before reading body*/
06645 
06646         bigstring bsexpectheader;
06647 
06648         langlookupstringvalue (hheaderstable, STR_P_EXPECT, bsexpectheader);
06649 
06650         if (!equalidentifiers (bsexpectheader, STR_P_100CONTINUE)) { /*DotNet compatibility*/
06651 
06652             //fwsNetEventWriteStream (stream, sizestatuscontinue, STR_STATUSCONTINUE);
06653             //} /*if*/
06654 
06655     //  else { /*Can't fulfill expectation*/
06656 
06657             *errorcode = 417;
06658                     
06659             flresult = true;
06660                     
06661             goto exit;
06662             } /*else*/
06663         } /*if*/
06664 
06665 
06666     /* Now that the request headers are parsed, make sure we have the whole body */
06667 
06668     disablelangerror ();
06669 
06670     fl = langlookuplongvalue (hheaderstable, STR_P_CONTENT_LENGTH, &contentlength);
06671 
06672     enablelangerror ();
06673 
06674     if (fl && (contentlength > 0)) { /* there's a body, read it */
06675 
06676         long ctpattern = gethandlesize (hpattern);
06677         long ctrequest = gethandlesize (h);
06678         long ctheaders = searchhandle (h, hpattern, 0, ctrequest); /* length of headers */
06679         long ctfullrequest;
06680         long ixbodystart;
06681         //long bytestoread;
06682 
06683         //assert (ctheaders != -1);
06684 
06685         ctfullrequest = ctheaders + ctpattern + contentlength;
06686 
06687         //bytestoread = ctfullrequest - ctrequest;
06688 
06689         if (/*bytestoread > 0 &&*/ !fwsNetEventReadStreamBytes (stream, h, ctfullrequest, timeout)) {
06690             
06691             *errorcode = 400;
06692             
06693             copystring (STR_P_BODY_NOT_READ, bserror);
06694             
06695             flresult = true;
06696             
06697             goto exit;
06698             }
06699 
06700         ctrequest = gethandlesize (h);
06701 
06702         if (ctrequest > ctfullrequest && !sethandlesize (h, ctfullrequest)) /* remove trailing junk */
06703             goto exit;
06704         
06705         ixbodystart = ctheaders + ctpattern;
06706 
06707         if (!loadfromhandletohandle (h, &ixbodystart, contentlength, false, &hrequestbody))
06708             goto exit;
06709         }
06710 
06711     /* Check for Expect header -- we may not be able to live up to the client's expectations */
06712 
06713 /*  if (hashtablesymbolexists (hheaderstable, STR_P_EXPECT)) { /% PBS 7.0b43: handle before reading body %/
06714     
06715         *errorcode = 417;
06716         
06717         flresult = true;
06718         
06719         goto exit;
06720         }*/
06721 
06722     /* Add cells to paramtable: firstLine, request, requestBody, host */
06723     
06724     disablelangerror ();
06725     
06726     langtablecopyvalue (hheaderstable, ht, STR_P_HOST);
06727     
06728     enablelangerror ();
06729     
06730     if(!langassigntextvalue (ht, STR_P_FIRSTLINE, hfirstline))
06731         goto exit;
06732     
06733     hfirstline = nil; /*make sure it won't be nuked anymore*/
06734 
06735     if (hrequestbody == nil)
06736         if (!newemptyhandle (&hrequestbody))
06737             goto exit;
06738 
06739     if(!langassigntextvalue (ht, STR_P_REQUESTBODY, hrequestbody))
06740         goto exit;
06741 
06742     hrequestbody = nil; /*make sure it won't be nuked anymore*/
06743 
06744     if(!langassigntextvalue (ht, STR_P_REQUEST, h))
06745         goto exit;
06746 
06747     h = nil; /*make sure it won't be nuked anymore*/
06748 
06749     flresult = true;
06750 
06751 exit:
06752 
06753     disposehandle (hpattern);
06754 
06755     disposehandle (hfirstline);
06756     
06757     disposehandle (hrequestbody);
06758 
06759     disposehandle (h);
06760     
06761     return (flresult);
06762     } /*webserverreadrequest*/
06763 
06764 
06765 static boolean webserverprocessfirstline (hdlhashtable ht, long *errorcode, bigstring bserror) {
06766 
06767     /*
06768     6.1d1 AR: Process the first line of a HTTP request.
06769     The request is already in the paramtable.
06770         
06771     6.1d4 AR: Reviewed for proper error handling and reporting.
06772     
06773     6.2a9 AR: Future-style request lines of the form "POST http://localhost/RPC2 HTTP/1.0"
06774     weren't handled gracefully. hpath would end up being "localhost/RPC2" instead of "/RPC2".
06775     */
06776     
06777     Handle h = nil;
06778     Handle hmethod = nil;
06779     Handle hpath = nil;
06780     Handle huri = nil;
06781     Handle hversion = nil;  
06782     Handle hsearchargs = nil;   
06783     Handle hpathargs = nil; 
06784     long hsize;
06785     long wdstart, wdlength;
06786     boolean flresult = false;
06787     tyvaluerecord val;
06788     hdlhashnode hnode;
06789     
06790     /* Look up the request line in the paramtable */
06791     
06792     if (!langhashtablelookup (ht, STR_P_FIRSTLINE, &val, &hnode))
06793         goto exit;
06794     
06795     if (val.valuetype != stringvaluetype)
06796         if (!copyvaluerecord (val, &val) || !coercetostring (&val))
06797             goto exit;
06798     
06799     h = val.data.stringvalue; /*still on tmpstack or in table*/
06800     
06801     /* Scan the request line and set up hmethod, hpath, and hversion:
06802        GET /index.html HTTP/1.1  */
06803 
06804     hsize = gethandlesize (h);
06805     
06806     if (!textnthword ((ptrbyte)(*h), hsize, 1, chspace, false, &wdstart, &wdlength)) {
06807         
06808         *errorcode = 400;
06809         
06810         copystring (STR_P_INVALID_REQUEST_LINE, bserror);
06811         
06812         flresult = true;
06813         
06814         goto exit;
06815         }
06816 
06817     if (!loadfromhandletohandle (h, &wdstart, wdlength, false, &hmethod))
06818         goto exit;
06819 
06820     if (!textnthword ((ptrbyte)(*h), hsize, 2, chspace, false, &wdstart, &wdlength)) {
06821         
06822         *errorcode = 400;
06823         
06824         copystring (STR_P_INVALID_REQUEST_LINE, bserror);
06825         
06826         flresult = true;
06827         
06828         goto exit;
06829         }
06830     
06831     if (!loadfromhandletohandle (h, &wdstart, wdlength, false, &hpath))
06832         goto exit;
06833 
06834     if (!textnthword ((ptrbyte)(*h), hsize, 3, chspace, false, &wdstart, &wdlength)) {
06835         
06836         *errorcode = 400;
06837         
06838         copystring (STR_P_INVALID_REQUEST_LINE, bserror);
06839         
06840         flresult = true;
06841         
06842         goto exit;
06843         }
06844     
06845     if (!loadfromhandletohandle (h, &wdstart, wdlength, false, &hversion))
06846         goto exit;
06847 
06848     /* Extract HTTP version number: "HTTP/1.1" --> "1.1" */
06849     
06850     if (!textnthword ((ptrbyte)(*hversion), gethandlesize (hversion), 2, '/', false, &wdstart, &wdlength)) {
06851         
06852         *errorcode = 400;
06853         
06854         copystring (STR_P_INVALID_REQUEST_LINE, bserror);
06855         
06856         flresult = true;
06857         
06858         goto exit;
06859         }
06860 
06861     if (!pullfromhandle (hversion, 0, wdstart, nil))
06862         goto exit;
06863     
06864     /* Check HTTP version */
06865     
06866     if (memcmp (*hversion, "1.1", 3) == 0) { /* Host header is obligatory in HTTP/1.1 */
06867 
06868         if (!hashtablesymbolexists (ht, STR_P_HOST)) {
06869         
06870             *errorcode = 400;
06871             
06872             copystring (STR_P_MISSING_HOST_HEADER, bserror);
06873             
06874             flresult = true;
06875 
06876             goto exit;          
06877             }
06878         }
06879     else { /* We don't know how to handle HTTP versions greater than 1.x */
06880 
06881         bigstring bsmainversion;
06882         long mainversion = 1;
06883     
06884         if (!textnthword ((ptrbyte)(*hversion), gethandlesize (hversion), 1, '.', false, &wdstart, &wdlength)) {
06885             
06886             *errorcode = 400;
06887             
06888             copystring (STR_P_INVALID_REQUEST_LINE, bserror);
06889             
06890             flresult = true;
06891             
06892             goto exit;
06893             }
06894         
06895         texttostring (*hversion, wdlength, bsmainversion);
06896         
06897         stringtonumber (bsmainversion, &mainversion);
06898         
06899         if (mainversion > 1) {
06900             bigstring bsversion;
06901 
06902             *errorcode = 505;
06903             
06904             texthandletostring (hversion, bsversion);
06905             
06906             parsedialogstring (STR_P_UNSUPPORTED_VERSION, bsversion, nil, nil, nil, bserror);
06907             
06908             flresult = true;
06909 
06910             goto exit;          
06911             }
06912         }
06913     
06914     /* Check path */
06915     
06916     if ((getlower((*hpath)[0]) == 'h')
06917         && (getlower((*hpath)[1]) == 't')
06918         && (getlower((*hpath)[2]) == 't')
06919         && (getlower((*hpath)[3]) == 'p')
06920         && (getlower((*hpath)[4]) == ':')) { /* It's a future-style full URL, remove scheme and host name */
06921         
06922         if (!textnthword ((ptrbyte)(*hpath), gethandlesize (hpath), 3, '/', true, &wdstart, &wdlength)
06923                 || !pullfromhandle (hpath, 0, wdstart + wdlength, nil)) {
06924             
06925             *errorcode = 400;
06926             
06927             copystring (STR_P_INVALID_URI, bserror);
06928             
06929             flresult = true;
06930             
06931             goto exit;
06932             }
06933         }
06934     else if (((*hpath)[0] != '/') && (((*hpath)[0] != '*') || (gethandlesize (hpath) != 1))) {
06935         
06936         *errorcode = 400;
06937         
06938         copystring (STR_P_INVALID_URI, bserror);
06939         
06940         flresult = true;
06941         
06942         goto exit;
06943         }
06944 
06945     /* Extract searchArgs from path -- the stuff after the '?' */
06946 
06947     if (textnthword ((ptrbyte)(*hpath), gethandlesize (hpath), 2, '\?', true, &wdstart, &wdlength))
06948         if (!loadfromhandletohandle (hpath, &wdstart, wdlength, false, &hsearchargs))
06949             goto exit;
06950 
06951     if (textnthword ((ptrbyte)(*hpath), gethandlesize (hpath), 1, '\?', true, &wdstart, &wdlength))
06952         sethandlesize (hpath, wdlength); /* keep only stuff before the question mark */
06953 
06954     /* Extract pathArgs from path -- the stuff after the '$' */
06955 
06956     wdstart = textpatternmatch ((byte *)(*hpath), gethandlesize (hpath), STR_P_DOLLAR, false);
06957 
06958     if (wdstart == -1) { /*fix for proxies which encode "$" as "%24"*/
06959 
06960         wdstart = textpatternmatch ((byte *)(*hpath), gethandlesize (hpath), STR_P_DOLLAR_ENCODED, false);
06961 
06962         if (wdstart != -1)
06963             pullfromhandle (hpath, wdstart + 1, 2, nil);
06964         }
06965 
06966     if (wdstart != -1) {
06967 
06968         long temp = wdstart + 1;
06969 
06970         if (!loadfromhandletohandle (hpath, &temp, gethandlesize (hpath) - temp, false, &hpathargs))
06971             goto exit;
06972 
06973         sethandlesize (hpath, wdstart);
06974         }
06975 
06976     /* Add paramtable field: method */
06977 
06978     if(!langassigntextvalue (ht, STR_P_METHOD, hmethod))
06979         goto exit;
06980 
06981     hmethod = nil;
06982     
06983     /* Add paramtable fields: path, URI */
06984 
06985     if (!copyhandle (hpath, &huri))
06986         goto exit;
06987 
06988     if(!langassigntextvalue (ht, STR_P_PATH, hpath))
06989         goto exit;
06990 
06991     hpath = nil;
06992 
06993     if(!langassigntextvalue (ht, STR_P_URI, huri))
06994         goto exit;
06995 
06996     huri = nil;
06997     
06998     /* Add paramtable fields: searchArgs, pathArgs */
06999 
07000     if (hsearchargs == nil)
07001         if (!newemptyhandle (&hsearchargs))
07002             goto exit;
07003     
07004     if(!langassigntextvalue (ht, STR_P_SEARCHARGS, hsearchargs))
07005         goto exit;
07006 
07007     hsearchargs = nil;
07008 
07009     if (hpathargs == nil)
07010         if (!newemptyhandle (&hpathargs))
07011             goto exit;
07012 
07013     if(!langassigntextvalue (ht, STR_P_PATHARGS, hpathargs))
07014         goto exit;
07015 
07016     hpathargs = nil;
07017     
07018     flresult = true;
07019 
07020 exit: /* clean up memory */
07021 
07022     disposehandle (hmethod);
07023     
07024     disposehandle (hpath);
07025 
07026     disposehandle (huri);
07027     
07028     disposehandle (hversion);
07029     
07030     disposehandle (hsearchargs);
07031     
07032     disposehandle (hpathargs);
07033     
07034     return (flresult);
07035     } /*webserverprocessfirstline*/
07036 
07037 
07038 static boolean webservermaintainstats (void) {
07039 
07040     /*
07041     6.1d1 AR: called from webserverserver to maintain
07042     the stats in the user.webserver.stats table.
07043         
07044     6.1d4 AR: Reviewed for proper error handling and reporting.
07045     */
07046 
07047     hdlhashtable hstatstable;
07048     
07049     disablelangerror (); /*ignore all errors*/
07050     
07051     if (langfastaddresstotable (roottable, STR_P_USERWEBSERVERSTATS, &hstatstable)) {
07052 
07053         long hits, maxconn, maxbytes, minbytes;
07054         long ctthreads = processthreadcount ();
07055         long ctbytes = FreeMem ();
07056         
07057         /*  if threads > user.webserver.stats.maxConnections {
07058                 user.webserver.stats.maxConnections = threads}; */
07059 
07060         if (langlookuplongvalue (hstatstable, STR_P_MAXCONNECTIONS, &maxconn) && (ctthreads > maxconn))
07061             langassignlongvalue (hstatstable, STR_P_MAXCONNECTIONS, ctthreads);
07062 
07063         /* if memavail > user.webserver.stats.maxmemavail {
07064                 user.webserver.stats.maxmemavail = memavail}; */
07065 
07066         if (langlookuplongvalue (hstatstable, STR_P_MAXMEMAVAIL, &maxbytes) && (ctbytes > maxbytes))
07067             langassignlongvalue (hstatstable, STR_P_MAXMEMAVAIL, ctbytes);
07068 
07069         /* if memavail < user.webserver.stats.minmemavail {
07070                 user.webserver.stats.minmemavail = memavail}; */
07071                 
07072         if (langlookuplongvalue (hstatstable, STR_P_MINMEMAVAIL, &minbytes) && (ctbytes < minbytes))
07073             langassignlongvalue (hstatstable, STR_P_MINMEMAVAIL, ctbytes);
07074 
07075         /* user.webserver.stats.hits++ */
07076         
07077         if (langlookuplongvalue (hstatstable, STR_P_HITS, &hits))
07078             langassignlongvalue (hstatstable, STR_P_HITS, ++hits);
07079         }
07080     
07081     enablelangerror ();
07082     
07083     return (true);
07084     } /*webservermaintainstats*/
07085 
07086 
07087 static boolean webserverserver (tyaddress *pta, Handle hrequest, tyvaluerecord *vreturn) {
07088 
07089     /*
07090     6.1d1 AR: Kernelized system.verbs.builtins.webserver.server.
07091     Pass in nil for hrequest if you want us to read the request from the stream.
07092             
07093     6.1d4 AR: Reviewed for proper error handling and reporting.
07094     */
07095 
07096     hdlhashtable hstatstable, hparamtable;
07097     tyvaluerecord val;
07098     long statuscode = 200;
07099     bigstring bsexplanation;
07100 
07101     setemptystring (bsexplanation);
07102     
07103     /* Wait here until Frontier has finished starting up */
07104 
07105     while (langgetuserflag (idfrontierstartup, false)) {
07106 
07107         if (!langgetuserflag (idflwaitduringstartup, true))
07108             break;
07109 
07110         processsleep (getcurrentthread (), 5 * 60);
07111         }
07112 
07113     /* Make sure we have a valid param table */
07114     
07115     if (!langsuretablevalue ((*pta).ht, (*pta).bs, &hparamtable))
07116         goto exit;
07117 
07118     /* Create stats sub-table in param table and record current tick count */
07119 
07120     if (!langassignnewtablevalue (hparamtable, STR_P_STATS, &hstatstable))
07121         goto exit;
07122 
07123     if (!langassignlongvalue (hstatstable, STR_P_PROCESSING_STARTED, gettickcount ()))
07124         goto exit;
07125 
07126     /* Read the full request and parse the headers, write everything to paramtable */
07127 
07128     if (!webserverreadrequest (hparamtable, hrequest, &statuscode, bsexplanation))
07129         goto exit;
07130     
07131     if (statuscode != 200)
07132         goto internal_error;
07133 
07134     /* Parse the first line of the request and add the following cells to the param table:
07135        method, path, URI, pathArgs, searchArgs */
07136 
07137     if (!webserverprocessfirstline (hparamtable, &statuscode, bsexplanation))
07138         goto exit;
07139     
07140     if (statuscode != 200)
07141         goto internal_error;
07142 
07143     /* Parse Cookie header line and add Cookies sub-table to param table */
07144 
07145     if (!webserverparsecookies (hparamtable, &val))
07146         goto exit;
07147 
07148     /* Dispatch the request */
07149 
07150     if (!webserverdispatch (pta, vreturn))
07151         goto exit;
07152 
07153     /* Maintain stats, if the feature is turned on (our default is off) */
07154     
07155     if (langgetuserflag (idwebserverstats, false))
07156         if (!webservermaintainstats ()) {
07157 
07158             assert (false);
07159     
07160             goto exit;
07161             }
07162     
07163     return (true);
07164 
07165 internal_error: {
07166         
07167         bigstring bscode;
07168         Handle hbody = nil;
07169         boolean fl = true;
07170         
07171         numbertostring (statuscode, bscode);
07172         
07173         if (!isemptystring (bsexplanation))
07174             if (!webserverbuilderrorpagefromstrings (bscode, bsexplanation, &hbody))
07175                 return (false);
07176         
07177         fl = webserverbuildresponse (bscode, nil, hbody, vreturn);
07178         
07179         disposehandle (hbody);
07180 
07181         return (fl);
07182         }
07183 
07184 exit:
07185 
07186     return (false);
07187     }/*webserverserver*/
07188 
07189 
07190 static unsigned char * bswhatwerewedoing [] = {
07191     BIGSTRING ("\x21" "Checking user.inetd.shutdown flag"),
07192     BIGSTRING ("\x13" "Creating paramtable"),
07193     BIGSTRING ("\x1e" "Adding client IP to paramtable"),
07194     BIGSTRING ("\x17" "Initializing paramtable"),
07195     BIGSTRING ("\x29" "Looking up timeout and chunksize settings"),
07196     BIGSTRING ("\x10" "Waiting for data"),
07197     BIGSTRING ("\x12" "Calling the daemon"),
07198     BIGSTRING ("\x24" "Returning the response to the client"),
07199     BIGSTRING ("\x0B" "Cleaning up")
07200     };
07201 
07202 static boolean inetdaddtoerrorlog (long code, bigstring bserror, hdlhashtable hparamtable) {
07203 
07204     /*
07205     6.1d1 AR: Record errors in the WebserverErrors sub-table of the daily log GDB.
07206             
07207     6.1d4 AR: Reviewed for proper error handling and reporting.
07208     */
07209 
07210     hdlhashtable hparenttable, hlogtable;
07211     tyvaluerecord vlogtable;
07212     bigstring bstablename;
07213     Handle hscript = nil;
07214     
07215     if (hparamtable == nil) /*crash prevention*/
07216         return (true);
07217 
07218     /* get address of new table in log GDB */
07219 
07220     if (!newtexthandle (BIGSTRING ("\x33""log.addToGuestDatabase (\"inetd\", flHourlyRoll:true)"), &hscript))
07221         return (false);
07222 
07223     if (!langrun (hscript, &vlogtable) || !coercetoaddress (&vlogtable)) /*htext is always consumed*/
07224         return (false);
07225     
07226     if (!getaddressvalue (vlogtable, &hparenttable, bstablename))
07227         return (false);
07228     
07229     if (!langsuretablevalue (hparenttable, bstablename, &hlogtable))
07230         return (false);
07231 
07232     /* fill log table */
07233     
07234     disablelangerror ();
07235 
07236     langassignstringvalue (hlogtable, STR_P_WHATWEREWEDOING, bswhatwerewedoing[code]);
07237 
07238     langassignstringvalue (hlogtable, STR_P_WHATWENTWRONG, bserror);
07239 
07240     langassignlongvalue (hlogtable, STR_P_THREAD, getthreadid (getcurrentthread ()));
07241 
07242     langtablecopyvalue (hparamtable, hlogtable, STR_P_STREAM);
07243     
07244     langtablecopyvalue (hparamtable, hlogtable, STR_P_PORT);
07245 
07246     langtablecopyvalue (hparamtable, hlogtable, STR_P_CLIENT);
07247     
07248     enablelangerror ();
07249 
07250     return (true);
07251     } /*inetdaddtoerrorlog*/
07252 
07253 
07254 static boolean inetdsupervisor (long stream, long refcon, tyvaluerecord * vreturn) {
07255 
07256     /*
07257     6.1d1 AR: The entry point for the kernelized webserver.
07258     
07259     6.1d4 AR: Reviewed for proper error handling and reporting.
07260     */
07261     
07262     hdlhashtable hparamtable = nil;
07263     hdlhashtable hconfigtable;
07264     Handle hrequest = nil;
07265     tyaddress adrparamtable;
07266     tyvaluerecord vparamtable, vreturndata;
07267     bigstring bspeeraddress, bserror;
07268     langerrormessagecallback savecallback;
07269     ptrvoid saverefcon;
07270     unsigned long peeraddress, peerport;
07271     long timeout = 30; /*seconds*/
07272     long whatarewedoing = 0;
07273     long chunksize = 8192; /*bytes*/
07274     hdlhashtable ht, hprefstable;
07275     bigstring bsrefcon;
07276     tyvaluerecord vconfigtable;
07277     tyaddress adrconfigtable;
07278     tyaddress adrscript;
07279     boolean flGotTimeout, flGotChunksize;
07280     boolean flNoWait = false;
07281     hdlhashnode hnode;
07282 
07283     /* check for valid stream id */
07284 
07285     if (stream < 0) //return immediately, can't close listen from here
07286         return (true);
07287     
07288     /* set up error trapping so that we can log any errors */
07289 
07290     setemptystring (bserror);
07291     
07292     langtraperrors (bserror, &savecallback, &saverefcon);
07293 
07294     /* check user.inetd.shutdown flag */
07295 
07296     if (langgetuserflag (idinetdshutdown, false)) {
07297 
07298         bigstring bsresult;
07299     
07300         if (!fwsNetEventCloseStream (stream))
07301             goto exit;
07302 
07303         if (!langrunstring (BIGSTRING ("\x0C" "inetd.stop()"), bsresult))
07304             goto exit;
07305 
07306         goto done;
07307         }
07308 
07309     /* new (tableType, @paramTable) */
07310     /* local (adrparamtable = @paramtable) */
07311 
07312     whatarewedoing++;
07313 
07314     if (!tablenewtablevalue (&hparamtable, &vparamtable))
07315         goto exit;
07316 
07317     if (!langsetsymbolval (STR_P_PARAMTABLE, vparamtable)) { 
07318 
07319         tabledisposetable (hparamtable, false);
07320 
07321         goto exit;
07322         }
07323 
07324     findinparenttable (hparamtable, &adrparamtable.ht, adrparamtable.bs);
07325 
07326     /* get the client IP and add it to the paramtable */
07327     
07328     whatarewedoing++;
07329 
07330     if (!fwsNetEventGetPeerAddress (stream, &peeraddress, &peerport))
07331         goto exit;
07332 
07333     if (!fwsNetEventAddressDecode (peeraddress, bspeeraddress))
07334         goto exit;
07335 
07336     if (!langassignstringvalue (hparamtable, STR_P_CLIENT, bspeeraddress))
07337         goto exit;
07338 
07339     /* init more paramtable values: ready, refcon, stream */
07340     
07341     whatarewedoing++;
07342 
07343     if (!langassignbooleanvalue (hparamtable, STR_P_READY, true))
07344         goto exit;
07345 
07346     if (!langassignlongvalue (hparamtable, STR_P_REFCON, refcon))
07347         goto exit;
07348 
07349     if (!langassignlongvalue (hparamtable, STR_P_STREAM, stream))
07350         goto exit;
07351 
07352     /*  init more paramtable values: inetdConfigTableAdr */
07353 
07354     if (!langfastaddresstotable (roottable, STR_P_USERINETDLISTENS, &ht))
07355         goto exit;
07356     
07357     numbertostring (refcon, bsrefcon);
07358 
07359     if (!langhashtablelookup (ht, bsrefcon, &vconfigtable, &hnode)
07360         || !langexternalvaltotable (vconfigtable, &ht, hnode))
07361         goto exit;
07362 
07363     if (!langlookupaddressvalue (ht, STR_P_ADRTABLE, &adrconfigtable))
07364         goto exit;
07365 
07366     if (!langassignaddressvalue (hparamtable, STR_P_INETDCONFIGTABLEADR, &adrconfigtable))
07367         goto exit;
07368 
07369     /*  init more paramtable values: port */
07370 
07371     if (!langhashtablelookup (adrconfigtable.ht, adrconfigtable.bs, &vconfigtable, &hnode)
07372         || !langexternalvaltotable (vconfigtable, &hconfigtable, hnode))
07373         goto exit;
07374     
07375     if (!langtablecopyvalue (hconfigtable, hparamtable, STR_P_PORT))
07376         goto exit;
07377 
07378     /* get timeout and chunksize from daemon config table */
07379 
07380     whatarewedoing++;
07381     
07382     disablelangerror ();
07383 
07384     flGotTimeout = langlookuplongvalue (hconfigtable, STR_P_TIMEOUT, &timeout);
07385 
07386     flGotChunksize = langlookuplongvalue (hconfigtable, STR_P_CHUNKSIZE, &chunksize);
07387 
07388     enablelangerror ();
07389 
07390     if (!flGotTimeout || !flGotChunksize) {
07391 
07392         if (!langfastaddresstotable (roottable, STR_P_USERINETDPREFS, &hprefstable))
07393             goto exit;
07394 
07395         if (!flGotTimeout)
07396             if (!langlookuplongvalue (hprefstable, STR_P_DEFAULTTIMEOUTSECS, &timeout))
07397                 goto exit;
07398 
07399         if (!flGotChunksize)
07400             if (!langlookuplongvalue (hprefstable, STR_P_RETURNCHUNKSIZE, &chunksize))
07401                 goto exit;
07402         }
07403 
07404 
07405     if (!langassignlongvalue (hparamtable, STR_P_TIMEOUT, timeout))
07406         goto exit;
07407 
07408     /*  waiting for data & init more paramtable values: request */
07409 
07410     whatarewedoing++;
07411     
07412     if (!newemptyhandle (&hrequest))
07413         goto exit;
07414 
07415     if (!langassigntextvalue (hparamtable, STR_P_REQUEST, hrequest)) {
07416 
07417         disposehandle (hrequest);
07418 
07419         goto exit;
07420         }
07421 
07422 //#if 0  //6.1b2 AR: last-minute addition for full backward compatibility with the obsolete script
07423 
07424     disablelangerror ();
07425 
07426     langlookupbooleanvalue (hconfigtable, STR_P_NOWAIT, &flNoWait);
07427 
07428     enablelangerror ();
07429 
07430     if (!flNoWait) {
07431 
07432         if (!fwsNetEventInetdRead (stream, hrequest, timeout))
07433             goto exit;
07434 
07435         langsymbolchanged (hparamtable, STR_P_REQUEST, nil, true);
07436         }
07437 
07438 //#endif
07439     
07440     /* call the daemon */
07441     
07442     whatarewedoing++;
07443 
07444     adrscript.ht = hconfigtable;
07445 
07446     copystring (STR_P_DAEMON, adrscript.bs);
07447 
07448     if (!followaddress (&adrscript))
07449         goto exit;
07450 
07451     if (!langcallscriptwithaddress (&adrscript, &adrparamtable, nil, &vreturndata))
07452         goto exit;
07453 
07454     /* write response */
07455     
07456     whatarewedoing++;
07457 
07458     /* return a string */
07459 
07460     if (vreturndata.valuetype == stringvaluetype)
07461         if (!fwsNetEventWriteHandleToStream (stream, vreturndata.data.stringvalue, chunksize, timeout))
07462             goto exit;
07463 
07464     /* it's the end of the world as we know it */
07465     
07466     whatarewedoing++;
07467 
07468     if (!fwsNetEventCloseStream (stream))
07469         goto exit; /* On Mac OS: error -51 (bad refnum) -- WHY??? */
07470 
07471 done:
07472 
07473     languntraperrors (savecallback, saverefcon, false);
07474 
07475     setbooleanvalue (true, vreturn);
07476     
07477     return (true);
07478 
07479 exit:
07480 
07481     languntraperrors (savecallback, saverefcon, true);
07482 
07483     fllangerror = false; /*6.1b12 AR*/
07484 
07485     if (!ingoodthread ()) /*unwind quickly*/
07486         return (false);
07487 
07488     //disablelangerror ();  //can't do that because langerrordisable
07489                             //is not preserved when a context switch occurs
07490 
07491     fwsNetEventAbortStream (stream);
07492 
07493     inetdaddtoerrorlog (whatarewedoing, bserror, hparamtable);
07494 
07495     //enablelangerror ();
07496 
07497     return (false);
07498 
07499 }/*inetdsupervisor*/
07500 
07501 
07502 
07503 #ifdef MACVERSION
07504 #pragma mark === mainResponder: Calendar ===
07505 #endif
07506 
07507 /*
07508 on getAddressDay (adr) {
07509     local (alist = string.parseaddress (adr));
07510     local (sizelist = sizeof (alist));
07511     local (day = number (alist [sizelist]));
07512     local (month = number (alist [sizelist - 1]));
07513     local (year = number (alist [sizelist - 2]));
07514     return (date.set (day, month, year, 0, 0, 0))}
07515 */
07516 
07517 extern boolean parseaddress (Handle htext, tyvaluerecord *v); /*stringverbs.c*/
07518 
07519 static boolean mrcalendargetaddressday (Handle htext, unsigned long *date) {
07520     
07521     tyvaluerecord vlist, val;
07522     long size, day, month, year;
07523     
07524     if (!parseaddress (htext, &vlist))
07525         return (false);
07526 
07527     assert (vlist.valuetype == listvaluetype);
07528     
07529     if (!langgetlistsize (&vlist, &size))
07530         return (false);
07531 
07532     if (!langgetlistitem (&vlist, size - 2, nil, &val))
07533         return (false);
07534     
07535     if (!coercetolong (&val))
07536         return (false);
07537     
07538     year = val.data.longvalue;
07539 
07540     if (!langgetlistitem (&vlist, size - 1, nil, &val))
07541         return (false);
07542     
07543     if (!coercetolong (&val))
07544         return (false);
07545     
07546     month = val.data.longvalue;
07547     
07548     if (!langgetlistitem (&vlist, size, nil, &val))
07549         return (false);
07550     
07551     if (!coercetolong (&val))
07552         return (false);
07553     
07554     day = val.data.longvalue;
07555     
07556     *date = datetimetoseconds (day, month, year, 0, 0, 0);
07557             
07558     return (true);
07559     }/*mrcalendargetaddressday*/
07560 
07561 
07562 static boolean mrcalendargetaddressdayverb (hdltreenode hp1, tyvaluerecord *v) {
07563     
07564     Handle htext;
07565     unsigned long date;
07566     
07567     flnextparamislast = true;
07568     
07569     if(!getexempttextvalue (hp1, 1, &htext))
07570         return (false);
07571                 
07572     if (!mrcalendargetaddressday (htext, &date))
07573         return (false);
07574 
07575     return (setdatevalue (date, v));
07576     }/*mrcalendargetaddressdayverb*/
07577 
07578 
07579 /*
07580 on getDayAddress (adrcalendar, d, flcreate=true, objtype=tabletype) { //turn a date into an address
07581     «this is the bottleneck
07582     
07583     local (day, month, year, hour, minute, second);
07584     date.get (d, @day, @month, @year, @hour, @minute, @second);
07585     year = string (year);
07586     month = string.padwithzeros (month, 2);
07587     day = string.padwithzeros (day, 2);
07588     
07589     local (adr = adrcalendar);
07590     on diveinto (name, type=tabletype) {
07591         adr = @adr^.[name];
07592         if (not defined (adr^)) and flcreate and (type != nil) {
07593             new (type, adr)}};
07594     diveinto (year);
07595     diveinto (month);
07596     diveinto (day, objtype);
07597     return (adr)}
07598 */
07599 
07600 static boolean mrcalendargetdayaddressdive (short num, tyaddress *adr, boolean flcreate, OSType idtype) {
07601     
07602     tyvaluerecord val;
07603     hdlhashnode hnode;
07604     
07605     if (!langhashtablelookup ((*adr).ht, (*adr).bs, &val, &hnode))
07606         return (false);
07607 
07608     if (!langexternalvaltotable (val, &(*adr).ht, hnode))
07609         return (false);
07610     
07611     shorttostring (num, (*adr).bs);
07612     
07613     while (stringlength ((*adr).bs) < 2)
07614         insertchar ('0', (*adr).bs);
07615     
07616     if (!langtablelookup ((*adr).ht, (*adr).bs, &(*adr).ht) && flcreate && idtype != nil) {
07617 
07618         tyvaluetype type = langgetvaluetype (idtype);
07619         
07620         if ((type >= outlinevaluetype) && (type <= pictvaluetype)) {
07621             
07622             hdlhashtable newtable;
07623             
07624             if (!langexternalnewvalue ((tyexternalid) (type - outlinevaluetype), nil, &val))
07625                 return (false);
07626 
07627             if ((type == tablevaluetype) && langexternalvaltotable (val, &newtable, HNoNode))
07628                 (**newtable).fllocaltable = (**(*adr).ht).fllocaltable;
07629             }
07630         else {
07631             initvalue (&val, novaluetype); /*nil all data*/
07632             
07633             if (!coercevalue (&val, type)) /*should only fail on low-mem*/
07634                 return (false);
07635             }
07636         
07637         if (!langsetsymboltableval ((*adr).ht, (*adr).bs, val)) {
07638             disposevaluerecord (val, true);         
07639             return (false);
07640             }
07641         else
07642             exemptfromtmpstack (&val);
07643         }
07644     
07645     return (true);
07646     }/*mrcalendargetdayaddressdive*/
07647     
07648 
07649 static boolean mrcalendargetdayaddress (tyaddress adrcalendar, unsigned long secs, boolean flcreate, OSType idtype, tyaddress *adr) {
07650     
07651     short day, month, year, hour, minute, second;
07652     
07653     *adr = adrcalendar;
07654     
07655     secondstodatetime (secs, &day, &month, &year, &hour, &minute, &second);
07656     
07657     if (!mrcalendargetdayaddressdive (year, adr, flcreate, 'tabl'))
07658         return (false);
07659 
07660     if (!mrcalendargetdayaddressdive (month, adr, flcreate, 'tabl'))
07661         return (false);
07662     
07663     if (!mrcalendargetdayaddressdive (day, adr, flcreate, idtype))
07664         return (false);
07665     
07666     return (true);
07667     }/*mrcalendargetdayaddress*/
07668 
07669 
07670 static boolean mrcalendargetdayaddressverb (hdltreenode hp1, tyvaluerecord *v) {
07671     
07672     short ctconsumed = 2, ctpositional = 2;
07673     unsigned long secs;
07674     tyvaluerecord val;
07675     tyaddress adrcalendar, adr; 
07676     boolean flcreate;
07677     OSType idtype;
07678     
07679     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
07680         return (false);
07681     
07682     if (!getdatevalue (hp1, 2, &secs))
07683         return (false);
07684         
07685     setbooleanvalue (true, &val);
07686     
07687     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x08""flcreate"), &val))
07688         return (false);
07689     
07690     flcreate = val.data.flvalue;
07691 
07692     setostypevalue ('tabl', &val);
07693     
07694     flnextparamislast = true;
07695     
07696     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x07""objtype"), &val))
07697         return (false);
07698     
07699     idtype = val.data.ostypevalue;
07700     
07701     if (!mrcalendargetdayaddress (adrcalendar, secs, flcreate, idtype, &adr))
07702         return (false);
07703     
07704     return (setaddressvalue (adr.ht, adr.bs, v));
07705     }/*mrcalendargetdayaddressverb*/
07706 
07707 
07708 /*
07709 on getFirstAddress (adrcalendar) {
07710     local (adryear, adrmonth, adrday, ix);
07711     on diveinto (parent, adrsibling) {
07712         for ix = 1 to sizeof (parent^) {
07713             try {
07714                 number (nameOf (parent^[ix]));
07715                 adrsibling^ = @parent^[ix];
07716                 return (true)}};
07717         return (false)};
07718     if diveinto (adrcalendar, @adryear) {
07719         if diveinto (adryear, @adrmonth) {
07720             if diveinto (adrmonth, @adrday) {
07721                 return (adrday)}}};
07722     scripterror ("Can't get the first address in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure.")}
07723 */
07724 
07725 
07726 static boolean findfirstnumericnodevisit (bigstring bsname, hdlhashnode nomad, tyvaluerecord val, ptrvoid refcon) {
07727 #pragma unused(val)
07728 
07729     if (isallnumeric (bsname)) {
07730 
07731         hdlhashnode *hnode = (hdlhashnode *) refcon;
07732         
07733         *hnode = nomad;
07734         
07735         return (true);
07736         }
07737 
07738     return (false);
07739     }/*findfirstnumericnodevisit*/
07740         
07741 
07742 static boolean mrcalendargetfirstaddressverb (hdltreenode hp1, tyvaluerecord *v) {
07743     
07744     hdlhashtable ht;
07745     bigstring bs;
07746     hdlhashnode hn;
07747 
07748     flnextparamislast = true;
07749                 
07750     if (!gettablevalue (hp1, 1, &ht))
07751         return (false);
07752 
07753     disablelangerror ();
07754     
07755     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn)) /*year*/
07756         goto error;
07757         
07758     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07759         goto error;
07760     
07761     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn)) /*month*/
07762         goto error;
07763         
07764     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07765         goto error;
07766     
07767     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn)) /*day*/
07768         goto error;
07769 
07770     enablelangerror ();
07771     
07772     gethashkey (hn, bs);
07773         
07774     return (setaddressvalue (ht, bs, v));
07775 
07776 error:
07777 
07778     enablelangerror ();
07779 
07780     langerrormessage (BIGSTRING ("\x76""Can't get the first address in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure."));
07781 
07782     return (false);
07783     }/*mrcalendargetfirstaddressverb*/
07784 
07785 /*
07786 on getFirstDay (adrcalendar) {
07787     local (adrday);
07788     try {
07789         adrday = mainResponder.calendar.getFirstAddress (adrcalendar);
07790         return (mainResponder.calendar.getAddressDay (adrday))}
07791     else {
07792         scripterror ("Can't get the first day in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure.")}}
07793 */
07794 
07795 static boolean mrcalendargetfirstday (hdlhashtable ht, tyvaluerecord *v) {
07796 
07797     hdlhashnode hn;
07798     short year, month, day;
07799 
07800     disablelangerror ();
07801     
07802     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn))
07803         goto error;
07804     
07805     stringtoshort ((**hn).hashkey, &year);
07806     
07807     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07808         goto error;
07809 
07810     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn))
07811         goto error;
07812     
07813     stringtoshort ((**hn).hashkey, &month);
07814     
07815     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07816         goto error;
07817 
07818     if (!hashsortedinversesearch (ht, &findfirstnumericnodevisit, &hn))
07819         goto error;
07820     
07821     stringtoshort ((**hn).hashkey, &day);
07822 
07823     enablelangerror ();
07824         
07825     return (setdatevalue (datetimetoseconds (day, month, year, 0, 0, 0), v));
07826 
07827 error:
07828 
07829     enablelangerror ();
07830 
07831     langerrormessage (BIGSTRING ("\x72""Can't get the first day in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure."));
07832 
07833     return (false);
07834     }/*mrcalendargetfirstday*/
07835 
07836 
07837 static boolean mrcalendargetfirstdayverb (hdltreenode hp1, tyvaluerecord *v) {
07838 
07839     hdlhashtable ht;
07840 
07841     flnextparamislast = true;
07842                 
07843     if (!gettablevalue (hp1, 1, &ht))
07844         return (false);
07845 
07846     return (mrcalendargetfirstday (ht, v));
07847     }/*mrcalendargetfirstdayverb*/
07848 
07849 /*
07850 on getLastAddress (adrcalendar) {
07851     local (adryear, adrmonth, adrday, ix);
07852     on diveinto (parent, adrsibling) {
07853         for ix = sizeof (parent^) downto 1 {
07854             try {
07855                 number (nameOf (parent^[ix]));
07856                 adrsibling^ = @parent^[ix];
07857                 return (true)}};
07858         return (false)};
07859     if diveinto (adrcalendar, @adryear) {
07860         if diveinto (adryear, @adrmonth) {
07861             if diveinto (adrmonth, @adrday) {
07862                 return (adrday)}}};
07863     scripterror ("Can't get the last address in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure.")}
07864 */
07865 
07866 static boolean findlastnumericnodevisit (bigstring bsname, hdlhashnode nomad, tyvaluerecord val, ptrvoid refcon) {
07867 #pragma unused(val)
07868 
07869     if (isallnumeric (bsname)) {
07870     
07871         hdlhashnode *hnode = (hdlhashnode *) refcon;
07872     
07873         *hnode = nomad;
07874         }
07875         
07876     return (false);
07877     }/*findlastnumericnodevisit*/
07878         
07879 
07880 static boolean mrcalendargetlastaddressverb (hdltreenode hp1, tyvaluerecord *v) {
07881     
07882     hdlhashtable ht;
07883     bigstring bs;
07884     hdlhashnode hn;
07885 
07886     flnextparamislast = true;
07887                 
07888     if (!gettablevalue (hp1, 1, &ht))
07889         return (false);
07890 
07891     disablelangerror ();
07892     
07893     hn = nil;
07894     
07895     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*year*/
07896 
07897     if (hn == nil)
07898         goto error;
07899     
07900     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07901         goto error;
07902     
07903     hn = nil;
07904     
07905     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*month*/
07906 
07907     if (hn == nil)
07908         goto error;
07909         
07910     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07911         goto error;
07912     
07913     hn = nil;
07914         
07915     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*day*/
07916 
07917     if (hn == nil)
07918         goto error;
07919 
07920     enablelangerror ();
07921     
07922     gethashkey (hn, bs);
07923         
07924     return (setaddressvalue (ht, bs, v));
07925 
07926 error:
07927 
07928     enablelangerror ();
07929 
07930     langerrormessage (BIGSTRING ("\x75""Can't get the last address in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure."));
07931 
07932     return (false);
07933     }/*mrcalendargetlastaddressverb*/
07934 
07935 /*
07936 on getLastDay (adrcalendar) {
07937     local (adrday);
07938     try {
07939         adrday = mainResponder.calendar.getLastAddress (adrcalendar);
07940         return (mainResponder.calendar.getAddressDay (adrday))}
07941     else {
07942         scripterror ("Can't get the last day in the calendar because adrcalendar does not point to a valid non-empty calendar structure.")}}
07943 */
07944 
07945 
07946 static boolean mrcalendargetlastday (hdlhashtable ht, tyvaluerecord *v) {
07947 
07948     hdlhashnode hn;
07949     short year, month, day;
07950 
07951     disablelangerror ();
07952     
07953     hn = nil;
07954     
07955     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*year*/
07956 
07957     if (hn == nil)
07958         goto error;
07959     
07960     stringtoshort ((**hn).hashkey, &year);
07961     
07962     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07963         goto error;
07964     
07965     hn = nil;
07966 
07967     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*month*/
07968 
07969     if (hn == nil)
07970         goto error;
07971     
07972     stringtoshort ((**hn).hashkey, &month);
07973     
07974     if (!langexternalvaltotable ((**hn).val, &ht, hn))
07975         goto error;
07976     
07977     hn = nil;
07978 
07979     hashsortedinversesearch (ht, &findlastnumericnodevisit, &hn); /*day*/
07980 
07981     if (hn == nil)
07982         goto error;
07983     
07984     stringtoshort ((**hn).hashkey, &day);
07985 
07986     enablelangerror ();
07987         
07988     return (setdatevalue (datetimetoseconds (day, month, year, 0, 0, 0), v));
07989 
07990 error:
07991 
07992     enablelangerror ();
07993 
07994     langerrormessage (BIGSTRING ("\x71""Can't get the last day in the calendar because adrcalendar doesn't point to a valid non-empty calendar structure."));
07995 
07996     return (false);
07997     }/*mrcalendargetlastday*/
07998 
07999 
08000 static boolean mrcalendargetlastdayverb (hdltreenode hp1, tyvaluerecord *v) {
08001 
08002     hdlhashtable ht;
08003 
08004     flnextparamislast = true;
08005                 
08006     if (!gettablevalue (hp1, 1, &ht))
08007         return (false);
08008 
08009     return (mrcalendargetlastday (ht, v));
08010     }/*mrcalendargetlastdayverb*/
08011 
08012 /*
08013 on getMostRecentAddress (adrCalendar, d=clock.now ()) {
08014     local (adrDay, dmin = mainResponder.calendar.getFirstDay (adrCalendar));
08015     while (dmin <= d) {
08016         try {
08017             adrDay = mainResponder.calendar.getDayAddress (adrCalendar, d, flcreate:false);
08018             if defined (adrDay^) {
08019                 return (adrDay)}};
08020         d = date.yesterday (d)};
08021     scripterror ("Can't get the most recent address in the calendar because the calendar doesn't contain any elements before " + d + ".")}
08022 */
08023 
08024 static boolean mrcalendargetmostrecentaddress (tyaddress adrcalendar, unsigned long date, tyvaluerecord *v) {
08025 
08026     tyaddress adr;
08027     tyvaluerecord val;
08028     unsigned long mindate;
08029     const unsigned long oneday = 24 * 60 * 60;
08030     hdlhashnode hnode;
08031     hdlhashtable ht;
08032     bigstring bserror, bsdate;
08033         
08034     if (!langhashtablelookup (adrcalendar.ht, adrcalendar.bs, &val, &hnode) || !langexternalvaltotable (val, &ht, hnode) || !mrcalendargetfirstday (ht, &val))
08035         return (false);
08036     
08037     mindate = val.data.datevalue;
08038         
08039     disablelangerror ();
08040     
08041     while (mindate <= date) {
08042     
08043         if (mrcalendargetdayaddress (adrcalendar, date, false, 'tabl', &adr) && hashtablesymbolexists (adr.ht, adr.bs)) {
08044             
08045             enablelangerror ();
08046             
08047             return (setaddressvalue (adr.ht, adr.bs, v));
08048             }
08049     
08050         date -= oneday;
08051         }/*while*/
08052 
08053     enablelangerror ();
08054 
08055     /*
08056     scripterror ("Can't get the most recent address in the calendar because the calendar doesn't contain any elements before " + d + ".")}
08057     */
08058     
08059     timedatestring (date, bsdate);
08060     
08061     parsedialogstring (BIGSTRING ("\x6e""Can't get the most recent address in the calendar because the calendar doesn't contain any elements before ^0."), bsdate, nil, nil, nil, bserror);
08062     
08063     langerrormessage (bserror);
08064     
08065     return (false);
08066     }/*mrcalendargetmostrecentaddress*/
08067     
08068 
08069 static boolean mrcalendargetmostrecentaddressverb (hdltreenode hp1, tyvaluerecord *v) {
08070 
08071     tyaddress adrcalendar;
08072     tyvaluerecord val;
08073     short ctconsumed = 1;
08074     short ctpositional = 1;
08075     unsigned long date;
08076     
08077     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
08078         return (false);
08079     
08080     flnextparamislast = true;
08081         
08082     setdatevalue (timenow (), &val);
08083 
08084     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x01""d"), &val))
08085         return (false);
08086 
08087     date = val.data.datevalue;
08088         
08089     return (mrcalendargetmostrecentaddress (adrcalendar, date, v));
08090     }/*mrcalendargetmostrecentaddressverb*/
08091 
08092 /*
08093 on getMostRecentDay (adrCalendar, d=clock.now ()) {
08094     local (adrDay, dmin = mainResponder.calendar.getFirstDay (adrCalendar));
08095     while (dmin <= d) {
08096         try {
08097             adrDay = mainResponder.calendar.getDayAddress (adrCalendar, d, flcreate:false);
08098             if defined (adrDay^) {
08099                 local (day, month, year, hour, minute, second);
08100                 date.get (d, @day, @month, @year, @hour, @minute, @second);
08101                 d = date.set (day, month, year, 0, 0, 0);
08102                 return (d)}};
08103         d = date.yesterday (d)};
08104     scripterror ("Can't get the most recent day in the calendar because the calendar doesn't contain any elements before " + d + ".")}
08105 */
08106 
08107 static boolean mrcalendargetmostrecentdayverb (hdltreenode hp1, tyvaluerecord *v) {
08108     
08109     tyaddress adrcalendar, adr;
08110     tyvaluerecord val;
08111     short ctconsumed = 1;
08112     short ctpositional = 1;
08113     unsigned long mindate, date;
08114     const unsigned long oneday = 24 * 60 * 60;
08115     hdlhashnode hnode;
08116     hdlhashtable ht;
08117     bigstring bserror, bsdate;
08118     
08119     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
08120         return (false);
08121     
08122     flnextparamislast = true;
08123         
08124     setdatevalue (timenow (), &val);
08125 
08126     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x01""d"), &val))
08127         return (false);
08128 
08129     date = val.data.datevalue;
08130     
08131     if (!langhashtablelookup (adrcalendar.ht, adrcalendar.bs, &val, &hnode) || !langexternalvaltotable (val, &ht, hnode) || !mrcalendargetfirstday (ht, &val))
08132         return (false);
08133     
08134     mindate = val.data.datevalue;
08135         
08136     disablelangerror ();
08137     
08138     while (mindate <= date) {
08139     
08140         if (mrcalendargetdayaddress (adrcalendar, date, false, 'tabl', &adr) && hashtablesymbolexists (adr.ht, adr.bs)) {
08141             
08142             short day, month, year, hour, minute, second;
08143             
08144             enablelangerror ();
08145 
08146             secondstodatetime (date, &day, &month, &year, &hour, &minute, &second);
08147             
08148             return (setdatevalue (datetimetoseconds (day, month, year, 0, 0, 0), v));
08149             }
08150     
08151         date -= oneday;
08152         }/*while*/
08153 
08154     enablelangerror ();
08155 
08156     /*
08157     scripterror ("Can't get the most recent day in the calendar because the calendar doesn't contain any elements before " + d + ".")}
08158     */
08159     
08160     timedatestring (date, bsdate);
08161     
08162     parsedialogstring (BIGSTRING ("\x6a""Can't get the most recent day in the calendar because the calendar doesn't contain any elements before ^0."), bsdate, nil, nil, nil, bserror);
08163     
08164     langerrormessage (bserror);
08165     
08166     return (false);
08167     }/*mrcalendargetmostrecentdayverb*/
08168 
08169 /*
08170 on getNextAddress (adrCalendar, d=clock.now ()) {
08171     local (adrDay, dmax = mainResponder.calendar.getLastDay (adrCalendar));
08172     dmax = date (date.tomorrow (dmax) - 1);
08173     while (d <= dmax) {
08174         try {
08175             adrDay = mainResponder.calendar.getDayAddress (adrCalendar, d, flcreate:false);
08176             if defined (adrDay^) {
08177                 return (adrDay)}};
08178         d = date.tomorrow (d)};
08179     scripterror ("Can't get the next address in the calendar because the calendar doesn't contain any elements after " + d + ".")}
08180 */
08181 
08182 static boolean mrcalendargetnextaddress (tyaddress adrcalendar, unsigned long date, tyvaluerecord *v) {
08183 
08184     tyaddress adr;
08185     tyvaluerecord val;
08186     unsigned long maxdate;
08187     const unsigned long oneday = 24 * 60 * 60;
08188     hdlhashnode hnode;
08189     hdlhashtable ht;
08190     bigstring bserror, bsdate;
08191     
08192     if (!langhashtablelookup (adrcalendar.ht, adrcalendar.bs, &val, &hnode) || !langexternalvaltotable (val, &ht, hnode) || !mrcalendargetlastday (ht, &val))
08193         return (false);
08194     
08195     maxdate = val.data.datevalue + oneday - 1;
08196         
08197     disablelangerror ();
08198     
08199     while (date <= maxdate) {
08200     
08201         if (mrcalendargetdayaddress (adrcalendar, date, false, 'tabl', &adr) && hashtablesymbolexists (adr.ht, adr.bs)) {
08202             
08203             enablelangerror ();
08204             
08205             return (setaddressvalue (adr.ht, adr.bs, v));
08206             }
08207     
08208         date += oneday;
08209         }/*while*/
08210 
08211     enablelangerror ();
08212 
08213     /*
08214     scripterror ("Can't get the next address in the calendar because the calendar doesn't contain any elements after " + d + ".")}
08215     */
08216     
08217     timedatestring (date, bsdate);
08218     
08219     parsedialogstring (BIGSTRING ("\x66""Can't get the next address in the calendar because the calendar doesn't contain any elements after ^0."), bsdate, nil, nil, nil, bserror);
08220     
08221     langerrormessage (bserror);
08222     
08223     return (false);
08224     }/*mrcalendargetnextaddress*/
08225 
08226 
08227 static boolean mrcalendargetnextaddressverb (hdltreenode hp1, tyvaluerecord *v) {
08228 
08229     tyaddress adrcalendar;
08230     tyvaluerecord val;
08231     short ctconsumed = 1;
08232     short ctpositional = 1;
08233     unsigned long date;
08234     
08235     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
08236         return (false);
08237     
08238     flnextparamislast = true;
08239         
08240     setdatevalue (timenow (), &val);
08241 
08242     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x01""d"), &val))
08243         return (false);
08244 
08245     date = val.data.datevalue;
08246         
08247     return (mrcalendargetnextaddress (adrcalendar, date, v));
08248     }/*mrcalendargetnextaddressverb*/
08249 
08250 /*
08251 on getNextDay (adrCalendar, d=clock.now ()) {
08252     local (adrDay, dmax = mainResponder.calendar.getLastDay (adrCalendar));
08253     dmax = date (date.tomorrow (dmax) - 1);
08254     while (d <= dmax) {
08255         try {
08256             adrDay = mainResponder.calendar.getDayAddress (adrCalendar, d, flcreate:false);
08257             if defined (adrDay^) {
08258                 local (day, month, year, hour, minute, second);
08259                 date.get (d, @day, @month, @year, @hour, @minute, @second);
08260                 d = date.set (day, month, year, 0, 0, 0);
08261                 return (d)}};
08262         d = date.tomorrow (d)};
08263     scripterror ("Can't get the next day in the calendar because the calendar doesn't contain any elements after " + d + ".")}
08264 */
08265 
08266 static boolean mrcalendargetnextdayverb (hdltreenode hp1, tyvaluerecord *v) {
08267 
08268     tyaddress adrcalendar, adr;
08269     tyvaluerecord val;
08270     short ctconsumed = 1;
08271     short ctpositional = 1;
08272     unsigned long maxdate, date;
08273     const unsigned long oneday = 24 * 60 * 60;
08274     hdlhashnode hnode;
08275     hdlhashtable ht;
08276     bigstring bserror, bsdate;
08277     
08278     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
08279         return (false);
08280     
08281     flnextparamislast = true;
08282         
08283     setdatevalue (timenow (), &val);
08284 
08285     if (!getoptionalparamvalue (hp1, &ctconsumed, &ctpositional, BIGSTRING ("\x01""d"), &val))
08286         return (false);
08287 
08288     date = val.data.datevalue;
08289     
08290     if (!langhashtablelookup (adrcalendar.ht, adrcalendar.bs, &val, &hnode) || !langexternalvaltotable (val, &ht, hnode) || !mrcalendargetlastday (ht, &val))
08291         return (false);
08292     
08293     maxdate = val.data.datevalue + oneday - 1;
08294         
08295     disablelangerror ();
08296     
08297     while (date <= maxdate) {
08298     
08299         if (mrcalendargetdayaddress (adrcalendar, date, false, 'tabl', &adr) && hashtablesymbolexists (adr.ht, adr.bs)) {
08300             
08301             short day, month, year, hour, minute, second;
08302             
08303             enablelangerror ();
08304 
08305             secondstodatetime (date, &day, &month, &year, &hour, &minute, &second);
08306             
08307             return (setdatevalue (datetimetoseconds (day, month, year, 0, 0, 0), v));
08308             }
08309     
08310         date += oneday;
08311         }/*while*/
08312 
08313     enablelangerror ();
08314 
08315     /*
08316     scripterror ("Can't get the next address in the calendar because the calendar doesn't contain any elements after " + d + ".")}
08317     */
08318     
08319     timedatestring (date, bsdate);
08320     
08321     parsedialogstring (BIGSTRING ("\x62""Can't get the next day in the calendar because the calendar doesn't contain any elements after ^0."), bsdate, nil, nil, nil, bserror);
08322     
08323     langerrormessage (bserror);
08324     
08325     return (false);
08326     }/*mrcalendargetnextdayverb*/
08327 
08328 
08329 /*
08330 on navigate (adrCalendar, adrDay, flForwardInTime) {
08331     if not defined (adrDay^) { //consistency check
08332         scriptError ("Can't navigate the calendar structure because adrDay does not point to an existing calendar element.")};
08333     local (d = mainResponder.calendar.getAddressDay (adrDay));
08334     if flForwardInTime {
08335         return (mainResponder.calendar.getNextAddress (adrCalendar,date.tomorrow (d)))}
08336     else {
08337         return (mainResponder.calendar.getMostRecentAddress (adrCalendar, date.yesterday (d)))}}
08338 */
08339 
08340 static boolean mrcalendarnavigateverb (hdltreenode hp1, tyvaluerecord *v) {
08341     
08342     tyvaluerecord val, valday;
08343     tyaddress adrcalendar, adrday;
08344     boolean flforwardintime;
08345     unsigned long date;
08346     const unsigned long oneday = 24 * 60 * 60;
08347     bigstring bs;
08348     Handle htext;
08349 
08350     if (!getaddressparam (hp1, 1, &val) || !getaddressvalue (val, &adrcalendar.ht, adrcalendar.bs))
08351         return (false);
08352 
08353     if (!getaddressparam (hp1, 2, &valday))
08354         return (false);
08355 
08356     flnextparamislast = true;
08357     
08358     if (!getbooleanvalue (hp1, 3, &flforwardintime))
08359         return (false);
08360     
08361     if (!getaddressvalue (valday, &adrday.ht, adrday.bs) || !hashtablesymbolexists (adrday.ht, adrday.bs)) { /*consistency check*/
08362         
08363         langerrormessage (BIGSTRING ("\x64""Can't navigate the calendar structure because adrDay does not point to an existing calendar element."));
08364         
08365         return (false);
08366         }
08367     
08368     if (!getaddresspath (valday, bs) || !newtexthandle (bs, &htext) || !mrcalendargetaddressday (htext, &date))
08369         return (false);
08370     
08371     if (flforwardintime)
08372         return (mrcalendargetnextaddress (adrcalendar, date + oneday, v));
08373     else
08374         return (mrcalendargetmostrecentaddress (adrcalendar, date - oneday, v));
08375     }/*mrcalendarnavigateverb*/
08376     
08377     
08378 /*
08379 
08380 on draw ( adrcalendar=nil, urlprefix="", colwidth=32, rowheight=22, tableborder=0, bgcolor=nil, monthYearTemplate="<font size=\"+2\" face=\"helvetica,arial\" color=\"black\"><b><center>***</center></b></font>", dayNameTemplate="<font size=\"-1\" color=\"gray\"><center>***</center></font>", dayTemplate="<font size=\"+0\" color=\"black\"><center><b>***</b></center></font>", curdate=clock.now ()) {
08381 
08382     local (bgcolorstring = "");
08383     if bgcolor != nil {
08384         bgcolorstring = "bgcolor=\"" + bgcolor + "\""};
08385 
08386     local (htmltext = "", indentlevel = 0);
08387     on add (s) {
08388         htmltext = htmltext + string.filledString ("\t", indentlevel) + s + "\r"};
08389 
08390     local (day, month, year, hour, minute, second);
08391     date.get (curdate, @day, @month, @year, @hour, @minute, @second);
08392     local (monthyear = date.monthToString (month) + " " + year);
08393     add ("<table cellspacing=\"0\" border=\"" + tableborder + "\">"); indentlevel++;
08394     add ("<tr>"); indentlevel++;
08395     add ("<td " + bgcolorstring + " colspan=\"7\">"); indentlevel++;
08396     add (string.replaceAll (string (monthYearTemplate), "***", monthyear));
08397     add ("</td>"); indentlevel--;
08398     add ("</tr>"); indentlevel--;
08399     bundle { //add days of week
08400         local (i);
08401         add ("<tr height=\"" + rowheight + "\">"); indentlevel++;
08402         for i = 1 to 7 {
08403             local (dayname = string.mid (date.dayOfWeekToString (i), 1, 3));
08404             add ("<td " + bgcolorstring + " width=\"" + colwidth + "\">"); indentlevel++;
08405             add (string.replaceAll (string (dayNameTemplate), "***", dayname));
08406             add ("</td>"); indentlevel--};
08407         add ("</tr>"); indentlevel--};
08408 
08409     bundle { //add the days of the month, 1, 2, 3, etc.
08410         on addday (daynum) {
08411             local (link = daynum);
08412             if daynum == day {
08413                 link = "<b>" + daynum + "</b>"}
08414             else {
08415                 try {
08416                     local (yearstring = string (year));
08417                     local (monthstring = string.padwithzeros (month, 2));
08418                     local (daystring = string.padwithzeros (daynum, 2));
08419                     local (adritem = @adrcalendar^.[yearstring].[monthstring].[daystring]);
08420                     if defined (adritem^) {
08421                         local (url = yearstring + "/" + monthstring + "/" + daystring);
08422                         link = "<a href=\"" + urlprefix + url + "\">" + daynum + "</a>"}}};
08423             add ("<td " + bgcolorstring + " height=\"" + rowheight + "\">" + string.replaceAll (string (dayTemplate), "***", link) + "</td>")};
08424         local (startday = date.dayOfWeek (date.set (1, month, year, 0, 0, 0)), i, daynum = 1);
08425         local (daysinmonth = date.daysInMonth (curdate));
08426         add ("<tr>"); indentlevel++;
08427         if startday > 1 {
08428             add ("<td " + bgcolorstring + " colspan=\"" + (startday - 1) + "\">&nbsp;</td>")};
08429         for i = startday to 7 {
08430             addday (daynum++)};
08431         add ("</tr>"); indentlevel--;
08432         while daynum <= daysinmonth {
08433             add ("<tr>"); indentlevel++;
08434             for i = 1 to 7 {
08435                 if daynum > daysinmonth {
08436                     add ("<td " + bgcolorstring + " colspan=\"" + (8 - i) + "\">&nbsp;</td>");
08437                     break};
08438                 addday (daynum++)};
08439             add ("</tr>"); indentlevel--}};
08440     add ("</table>"); indentlevel--;
08441     return (htmltext)}
08442 
08443 */
08444 
08445 #define str_calendartdwithinfo BIGSTRING ("\x1f""\t\t<td^0 width=\"^1\" height=\"^2\">")
08446 #define str_calendartdspacer BIGSTRING ("\x21""\t\t<td^0 colspan=\"^1\">&nbsp;</td>\r")
08447 
08448 #define str_calendartodaytemplate BIGSTRING ("\x09""<b>^0</b>")
08449 
08450 #define dayofweektocolumn(i,f) ((((i) - ((f) - 1) + 6) % 7) + 1)
08451 #define columntodayofweek(i,f) ((((i) + ((f) - 1) - 1) % 7) + 1)
08452 
08453 #if 0
08454 
08455 static boolean addhandle (handlestream *s, Handle h, short cttabs) {
08456 
08457     if (cttabs > 0) { /*indent*/
08458 
08459         bigstring bs;
08460 
08461         filledstring ('\t', cttabs, bs);
08462 
08463         if (!writehandlestreamstring (s, bs))
08464             return (false);
08465         }
08466 
08467     if (!writehandlestreamhandle (s, h))
08468         return (false);
08469 
08470     return (writehandlestreamchar (s, '\r'));
08471     }/*addhandle*/
08472 
08473 
08474 static boolean addstring (handlestream *s, bigstring bstring, short cttabs) {
08475 
08476     if (cttabs > 0) { /*indent*/
08477 
08478         bigstring bs;
08479 
08480         filledstring ('\t', cttabs, bs);
08481 
08482         if (!writehandlestreamstring (s, bs))
08483             return (false);
08484         }
08485 
08486     if (!writehandlestreamstring (s, bstring))
08487         return (false);
08488 
08489     return (writehandlestreamchar (s, '\r'));
08490     }/*addstring*/
08491 
08492 #endif
08493 
08494 
08495 static boolean addclassattribute (handlestream *s, Handle hcssprefix, bigstring bsname) {
08496 
08497     if (hcssprefix == nil)
08498         return (true);
08499             
08500     if (!writehandlestreamstring (s, BIGSTRING ("\x08" " class=\"")))
08501         return (false);
08502     
08503     if (!writehandlestreamhandle (s, hcssprefix))
08504         return (false);
08505 
08506     if (!writehandlestreamstring (s, bsname))
08507         return (false);
08508 
08509     if (!writehandlestreamchar (s, '"'))
08510         return (false);
08511 
08512     return (true);
08513     }/*addclassattribute*/
08514 
08515 
08516 static boolean fillintemplate (Handle h, Handle hinsert) {
08517     
08518     Handle hplaceholder;
08519     boolean fl;
08520     
08521     if (h == nil || hinsert == nil)
08522         return (true);
08523     
08524     if (!newtexthandle (BIGSTRING ("\x03""***"), &hplaceholder))
08525         return (false);
08526         
08527     fl = textfindreplace (hplaceholder, hinsert, h, true, false);   
08528     
08529     disposehandle (hplaceholder);
08530     
08531     return (fl);
08532     }/*fillintemplate*/
08533 
08534 /*
08535 on addday (daynum) {
08536     local (link = daynum);
08537     if daynum == day {
08538         link = "<b>" + daynum + "</b>"}
08539     else {
08540         try {
08541             local (yearstring = string (year));
08542             local (monthstring = string.padwithzeros (month, 2));
08543             local (daystring = string.padwithzeros (daynum, 2));
08544             local (adritem = @adrcalendar^.[yearstring].[monthstring].[daystring]);
08545             if defined (adritem^) {
08546                 local (url = yearstring + "/" + monthstring + "/" + daystring);
08547                 link = "<a href=\"" + urlprefix + url + "\">" + daynum + "</a>"}}};
08548     add ("<td " + bgcolorstring + " height=\"" + rowheight + "\">" + string.replaceAll (string (dayTemplate), "***", link) + "</td>")};
08549 */
08550 
08551 static boolean
08552 addday (
08553         handlestream *s,
08554         long daynum,
08555         boolean fltoday,
08556         hdlhashtable hmonthtable,
08557         Handle hurlprefix,
08558         Handle hbgcolorattrib,
08559         Handle hcolwidth,
08560         Handle hrowheight,
08561         Handle hday,
08562         Handle hcssprefix,
08563         boolean flfirstcolumn) {
08564 #pragma unused (hcolwidth)
08565 
08566     Handle hlink= nil;
08567     bigstring daystring;
08568     bigstring paddeddaystring;
08569     boolean fldaylinked;
08570 
08571     numbertostring (daynum, daystring);
08572 
08573     copystring (daystring, paddeddaystring);
08574 
08575     while (stringlength (paddeddaystring) < 2)
08576         insertchar ('0', paddeddaystring);
08577     
08578     fldaylinked = (hmonthtable != nil) && hashtablesymbolexists (hmonthtable, paddeddaystring);
08579 
08580     if (!writehandlestreamstring (s, BIGSTRING ("\x05""\t\t<td")))
08581         return (false);
08582     
08583     if (fltoday) {
08584         if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x12" "CalendarDayCurrent")))
08585             return (false);
08586         }
08587     else {      
08588         if (fldaylinked) {
08589             if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x11" "CalendarDayLinked")))
08590                 return (false);
08591             }
08592         else {
08593             if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x0b" "CalendarDay")))
08594                 return (false);
08595             }
08596         }
08597 
08598     if (0 < gethandlesize (hbgcolorattrib)) {
08599 
08600         if (!writehandlestreamhandle (s, hbgcolorattrib))
08601             return (false);
08602         }
08603 
08604     if (flfirstcolumn) {
08605 
08606         if (!writehandlestreamstring (s, BIGSTRING ("\x09"" height=\"")))
08607             return (false);
08608 
08609         if (!writehandlestreamhandle (s, hrowheight))
08610             return (false);
08611 
08612         if (!writehandlestreamchar (s, '\"'))
08613             return (false); 
08614         }
08615 
08616     if (!writehandlestreamchar (s, '>'))
08617         return (false); 
08618 
08619     if (fltoday) {
08620 
08621         bigstring daylink;
08622 
08623         copystring (str_calendartodaytemplate, daylink);
08624 
08625         parsedialogstring (daylink, daystring, nil, nil, nil, daylink);
08626 
08627         if (!newtexthandle (daylink, &hlink))
08628             goto error;
08629         }
08630 
08631     else {
08632 
08633         if (fldaylinked) {
08634 
08635             if (!newtexthandle (BIGSTRING ("\x09""<a href=\""), &hlink))
08636                 goto error;
08637 
08638             if (!pushhandle (hurlprefix, hlink))
08639                 goto error;
08640 
08641             if (!pushtexthandle (paddeddaystring, hlink))
08642                 goto error;
08643 
08644             if (!pushtexthandle (BIGSTRING ("\x02""\">"), hlink))
08645                 goto error;
08646 
08647             if (!pushtexthandle (daystring, hlink))
08648                 goto error;
08649 
08650             if (!pushtexthandle (BIGSTRING ("\x04""</a>"), hlink))
08651                 goto error;
08652             }
08653 
08654         else if (!newtexthandle (daystring, &hlink))
08655             goto error;
08656         }
08657 
08658     if (!copyhandle (hday, &hday)) /*don't modify original*/
08659         goto error;
08660     
08661     if (!fillintemplate (hday, hlink))
08662         goto error;
08663 
08664     if (!writehandlestreamhandle (s, hday))
08665         goto error;
08666 
08667     if (!writehandlestreamstring (s, BIGSTRING ("\x05""</td>")))
08668         goto error;
08669 
08670     if (!writehandlestreamchar (s, '\r'))
08671         goto error;
08672 
08673     disposehandle (hday);
08674 
08675     disposehandle (hlink);
08676 
08677     return (true);
08678 
08679 error:
08680 
08681     disposehandle (hday);
08682 
08683     disposehandle (hlink);
08684 
08685     return (false);
08686     }
08687 
08688 static boolean getmonthstring (long ix, tyvaluerecord vlist, Handle *h) {
08689 
08690     tyvaluerecord val;
08691 
08692     if (vlist.valuetype == novaluetype) {
08693         if (!datemonthtostring (ix, &val))
08694             return (false);
08695         }
08696     else {
08697         if (!langgetlistitem (&vlist, ix, nil, &val))
08698             return (false);
08699         }
08700 
08701     if (!coercetostring (&val))
08702         return (false);
08703 
08704     if (!copyhandle (val.data.stringvalue, h))
08705         return (false);
08706 
08707     return (true);
08708     }/*getmonthstring*/
08709 
08710 
08711 static boolean getdayofweekstring (long ix, tyvaluerecord vlist, Handle *h) {
08712 
08713     tyvaluerecord val;
08714 
08715     if (vlist.valuetype == novaluetype) {
08716 
08717         if (!datedayofweektostring (ix, &val))
08718             return (false);
08719 
08720         if (!sethandlesize (val.data.stringvalue, 3))
08721             return (false);
08722         }
08723     else {
08724         if (!langgetlistitem (&vlist, ix, nil, &val))
08725             return (false);
08726 
08727         if (!coercetostring (&val))
08728             return (false);
08729         }
08730 
08731     if (!copyhandle (val.data.stringvalue, h))
08732         return (false);
08733 
08734     return (true);
08735     }/*getdayofweekstring*/
08736 
08737 
08738 static boolean getmonthurl (unsigned long curdate, Handle hurldelimiter, Handle hurlprefix, hdlhashtable hcalendartable, hdlhashtable *hmonthtable) {
08739 
08740     /*other variable definitions*/
08741     hdlhashtable ht;
08742     short day, month, year, hour, minute, second;
08743     bigstring bsmonth, bsyear;
08744 
08745     /*try to locate monthly table in calendar*/
08746     /*it's not an error, if we can't find it - we'll just display a calendar without links*/
08747 
08748     secondstodatetime (curdate, &day, &month, &year, &hour, &minute, &second);
08749 
08750     numbertostring ((long) year, bsyear);
08751 
08752     numbertostring ((long) month, bsmonth);
08753 
08754     while (stringlength (bsmonth) < 2)
08755         insertchar ('0', bsmonth);
08756 
08757     *hmonthtable = nil;
08758     
08759     if (findnamedtable (hcalendartable, bsyear, &ht))
08760         findnamedtable (ht, bsmonth, hmonthtable);
08761 
08762     /*build urlprefix for this month:  "http://news.userland.com/" + "1999" + "/" + "06" + "/" */
08763 
08764     if (!pushtexthandle (bsyear, hurlprefix))
08765         return (false);
08766 
08767     if (!pushhandle (hurldelimiter, hurlprefix))
08768         return (false);
08769 
08770     if (!pushtexthandle (bsmonth, hurlprefix))
08771         return (false);
08772 
08773     if (!pushhandle (hurldelimiter, hurlprefix))
08774         return (false);
08775     
08776     return (true);
08777     }/*getmonthurl*/
08778 
08779 
08780 static boolean openoutertable (handlestream *s, Handle hcssprefix, Handle htableborder) {
08781 
08782     if (!writehandlestreamstring (s, BIGSTRING ("\x06" "<table")))
08783         return (false);
08784     
08785     if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x0d" "CalendarTable")))
08786         return (false);
08787 
08788     if (!writehandlestreamstring (s, BIGSTRING ("\x19" " cellspacing=\"0\" border=\"")))
08789         return (false);
08790 
08791     if (!writehandlestreamhandle (s, htableborder))
08792         return (false);
08793 
08794     if (!writehandlestreamstring (s, BIGSTRING ("\x03" "\">\r")))
08795         return (false);
08796 
08797     return (true);
08798     }/*addoutertable*/
08799 
08800 
08801 static boolean addmonthyearrow (handlestream *s, tyvaluerecord vmonthlist, unsigned long curdate, Handle hcssprefix, Handle hbgcolor, Handle hmonthyeartemplate) {
08802 
08803     short day, month, year, hour, minute, second;
08804     bigstring bsyear;
08805     Handle h;
08806     boolean fl;
08807     
08808     secondstodatetime (curdate, &day, &month, &year, &hour, &minute, &second);
08809     
08810     numbertostring ((long) year, bsyear);
08811     
08812     if (!getmonthstring ((long) month, vmonthlist, &h))
08813         return (false);
08814  
08815     fl = enlargehandle (h, sizeof(char), " ") && pushtexthandle (bsyear, h) && fillintemplate (hmonthyeartemplate, h);
08816     
08817     disposehandle (h);
08818     
08819     if (!fl)
08820         return (false);
08821     
08822     if (!writehandlestreamstring (s, BIGSTRING ("\x04" "\t<tr")))
08823         return (false);
08824 
08825     if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x14" "CalendarMonthYearRow")))
08826         return (false);
08827 
08828     if (!writehandlestreamstring (s, BIGSTRING ("\x04" "><td")))
08829         return (false);
08830 
08831     if (!writehandlestreamhandle (s, hbgcolor))
08832         return (false);
08833 
08834     if (!writehandlestreamstring (s, BIGSTRING ("\x0d" " colspan=\"7\">")))
08835         return (false);
08836 
08837     if (!writehandlestreamhandle (s, hmonthyeartemplate))
08838         return (false);
08839 
08840     if (!writehandlestreamstring (s, BIGSTRING ("\x0b" "</td></tr>\r")))
08841         return (false);
08842     
08843     return (true);
08844     }/*addmonthyearrow*/
08845 
08846 /*
08847 <tr class="[hcssprefix]CalendarDayNameRow">
08848     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Sun")]</td>
08849     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Mon")]</td>
08850     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Tue")]</td>
08851     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Wed")]</td>
08852     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Thu")]</td>
08853     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Fri")]</td>
08854     <td[hbgcolor] width="[hcolwidth]" height="[hrowheight]">[string.replaceAll (string (dayNameTemplate), "***", "Sat")]</td>
08855     </tr>
08856 */
08857 
08858 static boolean adddaynamesrow (handlestream *s, Handle hcssprefix, Handle hbgcolor, Handle hcolwidth, Handle hrowheight, Handle hdaynametemplate, long firstdayofweek, tyvaluerecord vdayofweeklist) {
08859 
08860     Handle htd = nil;
08861     long i;
08862 
08863     if (!writehandlestreamstring (s, BIGSTRING ("\x04" "\t<tr")))
08864         goto error;
08865 
08866     if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x12" "CalendarDayNameRow")))
08867         goto error;
08868 
08869     if (!writehandlestreamstring (s, BIGSTRING ("\x02" ">\r")))
08870         goto error;
08871 
08872     if (!newtexthandle (str_calendartdwithinfo, &htd))
08873         goto error;
08874 
08875     if (!parsedialoghandle (htd, hbgcolor, hcolwidth, hrowheight, nil))
08876         goto error;
08877 
08878     for (i = 1; i <= 7; i++) {
08879 
08880         Handle hdayname = nil;
08881         Handle hday = nil;
08882         boolean fl = true;
08883         
08884         if (!writehandlestreamhandle (s, htd))
08885             goto error;
08886 
08887         fl = fl && copyhandle (hdaynametemplate, &hday);
08888 
08889         fl = fl && getdayofweekstring (columntodayofweek (i, firstdayofweek), vdayofweeklist, &hdayname);
08890 
08891         fl = fl && fillintemplate (hday, hdayname);
08892         
08893         fl = fl && writehandlestreamhandle (s, hday);
08894 
08895         disposehandle (hdayname);
08896         
08897         disposehandle (hday);
08898 
08899         if (!fl)
08900             goto error;
08901             
08902         if (!writehandlestreamstring (s, BIGSTRING ("\x06" "</td>\r")))
08903             goto error;
08904         }/*for*/    
08905 
08906     if (!writehandlestreamstring (s, BIGSTRING ("\x08" "\t\t</tr>\r")))
08907         goto error;
08908     
08909     disposehandle (htd);
08910 
08911     return (true);
08912 
08913 error:
08914     disposehandle (htd);
08915     
08916     return (false);
08917     }/*adddaynamesrow*/
08918 
08919 /*
08920 <tr class="[hcssprefix]CalendarDayRow">
08921     <td colspan="1">&nbsp;</td>
08922     <td class="[hcssprefix]CalendarDayLinked" height="[hrowheight]">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/01">1</a>")]</td>
08923     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "2")]</td>
08924     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/03">3</a>")]</td>
08925     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/04">4</a>")]</td>
08926     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "5")]</td>
08927     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "6")]</td>
08928     </tr>
08929 <tr class="[hcssprefix]CalendarDayRow">
08930     <td class="[hcssprefix]CalendarDay" height="[hrowheight]">[string.replaceAll (daytemplate, "***", "7")]</td>
08931     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/08">8</a>")]</td>
08932     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/09">9</a>")]</td>
08933     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "10")]</td>
08934     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "11")]</td>
08935     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "12")]</td>
08936     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/13">13</a>")]</td>
08937     </tr>
08938 <tr class="[hcssprefix]CalendarDayRow">
08939     <td class="[hcssprefix]CalendarDayLinked" height="[hrowheight]">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/14">14</a>")]</td>
08940     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/15">15</a>")]</td>
08941     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/16">16</a>")]</td>
08942     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/17">17</a>")]</td>
08943     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/18">18</a>")]</td>
08944     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "19")]</td>
08945     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/20">20</a>")]</td>
08946     </tr>
08947 <tr class="[hcssprefix]CalendarDayRow">
08948     <td class="[hcssprefix]CalendarDayLinked" height="[hrowheight]">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/21">21</a>")]</td>
08949     <td class="[hcssprefix]CalendarDayLinked">[string.replaceAll (daytemplate, "***", "<a href="[hurlprefix]2000/05/22">22</a>")]</td>
08950     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "23")]</td>
08951     <td class="[hcssprefix]CalendarDayCurrent">[string.replaceAll (daytemplate, "***", "24")]</td>
08952     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "25")]</td>
08953     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "26")]</td>
08954     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "27")]</td>
08955     </tr>
08956 <tr class="[hcssprefix]CalendarDayRow">
08957     <td class="[hcssprefix]CalendarDay" height="[hrowheight]">[string.replaceAll (daytemplate, "***", "28")]</td>
08958     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "29")]</td>
08959     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "30")]</td>
08960     <td class="[hcssprefix]CalendarDay">[string.replaceAll (daytemplate, "***", "31")]</td>
08961     <td colspan="3">&nbsp;</td>
08962     </tr>
08963 */
08964 
08965 static boolean adddayrows (handlestream *s, unsigned long curdate, long firstdayofweek, hdlhashtable hmonthtable,
08966                             Handle hurlprefix,  Handle hbgcolor, Handle hcolwidth, Handle hrowheight, Handle hdaytemplate, Handle hcssprefix) {
08967 
08968     short day, month, year, hour, minute, second;
08969     short dayofweek, maxdays, firstcolumn;
08970     long daynum = 1;
08971     short i;
08972 
08973     /*compute dayofweek, firstcolumn, and maxdays*/
08974     
08975     secondstodatetime (curdate, &day, &month, &year, &hour, &minute, &second);
08976 
08977     secondstodayofweek (datetimetoseconds (1, month, year, 0, 0, 0), &dayofweek);
08978 
08979     firstcolumn = dayofweektocolumn (dayofweek, firstdayofweek);
08980 
08981     maxdays = daysInMonth (month, year);
08982     
08983     /*begin writing table*/
08984 
08985     if (!writehandlestreamstring (s, BIGSTRING ("\x04" "\t<tr")))
08986         return (false);
08987 
08988     if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x0e" "CalendarDayRow")))
08989         return (false);
08990 
08991     if (!writehandlestreamstring (s, BIGSTRING ("\x02" ">\r")))
08992         return (false);
08993 
08994     if (firstcolumn > 1) {
08995 
08996         bigstring bscolspan;
08997         Handle hcolspan = nil;
08998         Handle hspacer = nil;
08999         boolean fl = true;
09000 
09001         numbertostring (firstcolumn - 1, bscolspan);
09002 
09003         fl = fl && newtexthandle (str_calendartdspacer, &hspacer);
09004 
09005         fl = fl && newtexthandle (bscolspan, &hcolspan);
09006 
09007         fl = fl && parsedialoghandle (hspacer, hbgcolor, hcolspan, nil, nil);
09008         
09009         fl = fl && writehandlestreamhandle (s, hspacer);
09010 
09011         disposehandle (hcolspan);
09012         
09013         disposehandle (hspacer);
09014         
09015         if (!fl)
09016             return (false);
09017         }
09018 
09019     for (i = firstcolumn; i <= 7; i++) {
09020 
09021         if (!addday (s, daynum, daynum == day, hmonthtable, hurlprefix,
09022                     hbgcolor, hcolwidth, hrowheight, hdaytemplate, hcssprefix, (i == firstcolumn)))
09023             return (false);
09024 
09025         daynum++;
09026         }
09027 
09028     if (!writehandlestreamstring (s, BIGSTRING ("\x08" "\t\t</tr>\r")))
09029         return (false);
09030 
09031     /*
09032     while daynum <= daysinmonth {
09033         add ("<tr>"); indentlevel++;
09034         for i = 1 to 7 {
09035             if daynum > daysinmonth {
09036                 add ("<td " + bgcolorstring + " colspan=\"" + (8 - i) + "\">&nbsp;</td>");
09037                 break};
09038             addday (daynum++)};
09039         add ("</tr>"); indentlevel--};
09040     */  
09041 
09042     while (daynum <= maxdays) {
09043 
09044         if (!writehandlestreamstring (s, BIGSTRING ("\x04" "\t<tr")))
09045             return (false);
09046 
09047         if (!addclassattribute (s, hcssprefix, BIGSTRING ("\x0e" "CalendarDayRow")))
09048             return (false);
09049 
09050         if (!writehandlestreamstring (s, BIGSTRING ("\x02" ">\r")))
09051             return (false);
09052 
09053         for (i = 1; i <= 7; i++) {
09054 
09055             if (daynum > maxdays) {
09056 
09057                 bigstring bscolspan;
09058                 Handle hcolspan = nil;
09059                 Handle hspacer = nil;
09060                 boolean fl = true;
09061 
09062                 numbertostring (8 - i, bscolspan);
09063 
09064                 fl = fl && newtexthandle (str_calendartdspacer, &hspacer);
09065 
09066                 fl = fl && newtexthandle (bscolspan, &hcolspan);
09067 
09068                 fl = fl && parsedialoghandle (hspacer, hbgcolor, hcolspan, nil, nil);
09069                 
09070                 fl = fl && writehandlestreamhandle (s, hspacer);
09071 
09072                 disposehandle (hcolspan);
09073                 
09074                 disposehandle (hspacer);
09075                 
09076                 if (!fl)
09077                     return (false);
09078 
09079                 break;
09080                 }
09081 
09082             if (!addday (s, daynum, daynum == day, hmonthtable, hurlprefix,
09083                             hbgcolor, hcolwidth, hrowheight, hdaytemplate, hcssprefix, (i == 1)))
09084                 return (false);
09085 
09086             daynum++;
09087             }
09088 
09089         if (!writehandlestreamstring (s, BIGSTRING ("\x08" "\t\t</tr>\r")))
09090             return (false);
09091         }
09092     
09093     return (true);
09094     }/*adddayrows*/
09095 
09096 
09097 static boolean htmlcalendardraw (handlestream *s,
09098                                     hdlhashtable hcalendartable,
09099                                     Handle hurlprefix,
09100                                     Handle hcolwidth,
09101                                     Handle hrowheight,
09102                                     Handle htableborder,
09103                                     Handle hbgcolor,
09104                                     Handle hmonthyeartemplate,
09105                                     Handle hdaynametemplate,
09106                                     Handle hdaytemplate,
09107                                     unsigned long curdate,
09108                                     Handle hurldelimiter,
09109                                     long firstdayofweek,
09110                                     tyvaluerecord vmonthlist,
09111                                     tyvaluerecord vdayofweeklist,
09112                                     Handle hcssprefix) {
09113 
09114     /*other variable definitions*/
09115     hdlhashtable hmonthtable;
09116 
09117     /*try to locate monthly table in calendar*/
09118     /*it's not an error, if we can't find it - we'll just display a calendar without links*/
09119     
09120     if (!getmonthurl (curdate, hurldelimiter, hurlprefix, hcalendartable, &hmonthtable))
09121         return (