1a2011f25ac0bd4ce02ed0eb093ff169724f9047
angie
  Mon May 2 16:39:23 2016 -0700
Added trackDb setting "tableBrowser noGenome", which allows tables to appear in TB and DI menus,
unlike "tableBrowser off", but does not allow genome-wide queries on the tables.  The new "noGenome"
setting is used for OMIM tracks -- OMIM gave the OK for non-genome-wide queries on their tables.

See #4458#note-53 for details of UI & functional changes to the TB and DI for noGenome tracks.

hg/lib/cartTrackDb.c handles access control based on trackDb tableBrowser settings (and the
seldom-used tableAccessControl database tables that restrict tables to be viewed only by
certain hosts).  As before, 'tableBrowser off' means that the track is removed from the
trackList so the TB & DI don't even know that it exists.  'tableBrowser noGenome' tracks are
included in the trackList, but the tracks and any tables listed after 'noGenome' are hashed
for later use by cartTrackDbIsNoGenome().  The hash used to contain permitted-host lists,
but now contains structs that combine the permitted-host lists with a noGenome flag.

hgIntegrator.c now includes a 'noGenome' flag in the JSONified groupedTrackDb, which the JS
code uses to identify noGenome tracks.  When executing a query, if the query region is genome
but hgi_querySpec has noGenome related tables left over from past position-only queries, those
related table settings are removed from the parsed JSON querySpec->config->relatedTables that is
passed down into annoStreamDb.  If one of the dataSources is noGenome but the region is genome
(should be possible only by URL-tweaking), the region is forced to position.

In hgTables, when region is genome, correlation and intersection track menu options for
noGenome tracks are disabled.  On the main page, JS code controls whether options are disabled
depending on the current region.  When listing related tables for filtering or selected fields
output, if region is genome then the checkboxes for noGenome tables are disabled.  If the cart
has noGenome related table or intersection settings left over from past searches, they are
ignored.  If a noGenome query URL is tweaked to have hgta_region=genome, the TB errors out.

hgIntegratorModel.js handles disabling of menu options and related table field settings for
noGenome tracks when region is genome.

refs #4458

diff --git src/lib/cheapcgi.c src/lib/cheapcgi.c
index 6256532..4e08f3d 100644
--- src/lib/cheapcgi.c
+++ src/lib/cheapcgi.c
@@ -1,2397 +1,2403 @@
 /* Routines for getting variables passed in from web page
  * forms via CGI.
  *
  * This file is copyright 2002 Jim Kent, but license is hereby
  * granted for all use - public, private or commercial. */
 
 #include "common.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "portable.h"
 #include "linefile.h"
 #include "errAbort.h"
 #include "filePath.h"
 #ifndef GBROWSE
 #include "mime.h"
 #endif /* GBROWSE */
 #include <signal.h>
 
 
 /* These three variables hold the parsed version of cgi variables. */
 static char *inputString = NULL;
 static unsigned long inputSize;
 static struct hash *inputHash = NULL;
 static struct cgiVar *inputList = NULL;
 
 static boolean haveCookiesHash = FALSE;
 static struct hash *cookieHash = NULL;
 static struct cgiVar *cookieList = NULL;
 
 /* should cheapcgi use temp files to store uploaded files */
 static boolean doUseTempFile = FALSE;
 
 void dumpCookieList()
 /* Print out the cookie list. */
 {
 struct cgiVar *v;
 for (v=cookieList; v != NULL; v = v->next)
     printf("%s=%s (%d)\n", v->name, v->val, v->saved);
 }
 
 void useTempFile()
 /* tell cheapcgi to use temp files */
 {
 doUseTempFile = TRUE;
 }
 
 boolean cgiIsOnWeb()
 /* Return TRUE if looks like we're being run as a CGI. */
 {
 return getenv("REQUEST_METHOD") != NULL;
 }
 
 char *cgiRequestMethod()
 /* Return CGI REQUEST_METHOD (such as 'GET/POST/PUT/DELETE/HEAD') */
 {
 return getenv("REQUEST_METHOD");
 }
 
 char *cgiRequestUri()
 /* Return CGI REQUEST_URI */
 {
 return getenv("REQUEST_URI");
 }
 
 char *cgiRequestContentLength()
 /* Return HTTP REQUEST CONTENT_LENGTH if available*/
 {
 return getenv("CONTENT_LENGTH");
 }
 
 char *cgiScriptName()
 /* Return name of script so libs can do context-sensitive stuff. */
 {
 char *scriptName = getenv("SCRIPT_NAME");
 if (scriptName == NULL)
     scriptName = "cgiSpoofedScript";
 return scriptName;
 }
 
 char *cgiScriptDirUrl()
 /* Return the absolute cgi-bin directory on this webserver.
  * This is not the local directory but the <path> part after the server
  * in external URLs to this webserver.
  * e.g. if CGI is called http://localhost/subdir/cgi-bin/cgiTest
  * then returned value is /subdir/
  * return value must be free'd. */
 {
 char* cgiPath = cloneString(cgiScriptName());
 char dir[PATH_LEN];
 splitPath(cgiPath, dir, NULL, NULL);
 char* dirStr = needMem(PATH_LEN);
 safecpy(dirStr, PATH_LEN, dir);
 return dirStr;
 }
 
 char *cgiServerName()
 /* Return name of server, better to use cgiServerNamePort() for
    actual URL construction */
 {
 return getenv("SERVER_NAME");
 }
 
 char *cgiServerPort()
 /* Return port number of server, default 80 if not found */
 {
 char *port = getenv("SERVER_PORT");
 if (port)
     return port;
 else
     return "80";
 }
 
 boolean cgiServerHttpsIsOn()
 /* Return true if HTTPS is on */
 {
 char *httpsIsOn = getenv("HTTPS");
 if (httpsIsOn)
     return sameString(httpsIsOn, "on");
 else
     return FALSE;
 }
 
 char *cgiAppendSForHttps()
 /* if running on https, add the letter s to the url protocol */
 {
 if (cgiServerHttpsIsOn())
     return "s";
 return "";
 }
 
 
 char *cgiServerNamePort()
 /* Return name of server with port if different than 80 */
 {
 char *port = cgiServerPort();
 char *name = cgiServerName();
 struct dyString *result = newDyString(256);
 char *defaultPort = "80";
 if (cgiServerHttpsIsOn())
     defaultPort = "443";
 
 if (name)
     {
     dyStringPrintf(result,"%s",name);
     if (differentString(port, defaultPort))
 	dyStringPrintf(result,":%s",port);
     return dyStringCannibalize(&result);
     }
 else
     return NULL;
 }
 
 char *cgiRemoteAddr()
 /* Return IP address of client (or "unknown"). */
 {
 static char *dunno = "unknown";
 char *remoteAddr = getenv("REMOTE_ADDR");
 if (remoteAddr == NULL)
     remoteAddr = dunno;
 return remoteAddr;
 }
 
 char *cgiUserAgent()
 /* Return remote user agent (HTTP_USER_AGENT) or NULL if remote user agent is not known */
 {
 return getenv("HTTP_USER_AGENT");
 }
 
 enum browserType cgiClientBrowser(char **browserQualifier, enum osType *clientOs,
                                   char **clientOsQualifier)
 /* Return client browser type determined from (HTTP_USER_AGENT)
    Optionally requuest the additional info about the client */
 {
 // WARNING: The specifics of the HTTP_USER_AGENT vary widely.
 //          This has only been tested on a few cases.
 static enum browserType clientBrowser = btUnknown;
 static enum browserType clientOsType  = (enum browserType)osUnknown;
 static char *clientBrowserExtra       = NULL;
 static char *clientOsExtra            = NULL;
 
 if (clientBrowser == btUnknown)
     {
     char *userAgent = cgiUserAgent();
     if (userAgent != NULL)
         {
         //warn(userAgent);  // Use this to investigate other cases
         char *ptr=NULL;
 
         // Determine the browser
         if ((ptr = stringIn("Opera",userAgent)) != NULL) // Must be before IE
             {
             clientBrowser = btOpera;
             }
         else if ((ptr = stringIn("MSIE ",userAgent)) != NULL)
             {
             clientBrowser = btIE;
             ptr += strlen("MSIE ");
             clientBrowserExtra = cloneFirstWordByDelimiter(ptr,';');
             }
         else if ((ptr = stringIn("Firefox",userAgent)) != NULL)
             {
             clientBrowser = btFF;
             ptr += strlen("(Firefox/");
             clientBrowserExtra = cloneFirstWordByDelimiter(ptr,' ');
             }
         else if ((ptr = stringIn("Chrome",userAgent)) != NULL)  // Must be before Safari
             {
             clientBrowser = btChrome;
             ptr += strlen("Chrome/");
             clientBrowserExtra = cloneFirstWordByDelimiter(ptr,' ');
             }
         else if ((ptr = stringIn("Safari",userAgent)) != NULL)
             {
             clientBrowser = btSafari;
             ptr += strlen("Safari/");
             clientBrowserExtra = cloneFirstWordByDelimiter(ptr,' ');
             }
         else
             {
             clientBrowser = btOther;
             }
 
         // Determine the OS
         if ((ptr = stringIn("Windows",userAgent)) != NULL)
             {
             clientOsType = (enum browserType)osWindows;
             ptr += strlen("Windows ");
             clientOsExtra = cloneFirstWordByDelimiter(ptr,';');
             }
         else if ((ptr = stringIn("Linux",userAgent)) != NULL)
             {
             clientOsType = (enum browserType)osLinux;
             ptr += strlen("Linux ");
             clientOsExtra = cloneFirstWordByDelimiter(ptr,';');
             }
         else if ((ptr = stringIn("Mac ",userAgent)) != NULL)
             {
             clientOsType = (enum browserType)osMac;
             ptr += strlen("Mac ");
             clientOsExtra = cloneFirstWordByDelimiter(ptr,';');
             }
         else
             {
             clientOsType = (enum browserType)osOther;
             }
         }
     }
 if (browserQualifier != NULL)
     {
     if (clientBrowserExtra != NULL)
         *browserQualifier = cloneString(clientBrowserExtra);
     else
         *browserQualifier = NULL;
     }
 if (clientOs != NULL)
     *clientOs = (enum osType)clientOsType;
 if (clientOsQualifier != NULL)
     {
     if (clientOsExtra != NULL)
         *clientOsQualifier = cloneString(clientOsExtra);
     else
         *clientOsQualifier = NULL;
     }
 
 return clientBrowser;
 }
 
 char *_cgiRawInput()
 /* For debugging get the unprocessed input. */
 {
 return inputString;
 }
 
 static void getQueryInputExt(boolean abortOnErr)
 /* Get query string from environment if they've used GET method. */
 {
 inputString = getenv("QUERY_STRING");
 if (inputString == NULL)
     {
     if (abortOnErr)
 	errAbort("No QUERY_STRING in environment.");
     inputString = cloneString("");
     return;
     }
 inputString = cloneString(inputString);
 }
 
 static void getQueryInput()
 /* Get query string from environment if they've used GET method. */
 {
 getQueryInputExt(TRUE);
 }
 
 static void getPostInput()
 /* Get input from file if they've used POST method.
  * Grab any GET QUERY_STRING input first. */
 {
 char *s;
 long i;
 int r;
 
 getQueryInputExt(FALSE);
 int getSize = strlen(inputString);
 
 s = getenv("CONTENT_LENGTH");
 if (s == NULL)
     errAbort("No CONTENT_LENGTH in environment.");
 if (sscanf(s, "%lu", &inputSize) != 1)
     errAbort("CONTENT_LENGTH isn't a number.");
 s = getenv("CONTENT_TYPE");
 if (s != NULL && startsWith("multipart/form-data", s))
     {
     /* use MIME parse on input stream instead, can handle large uploads */
     /* inputString must not be NULL so it knows it was set */
     return;
     }
 int len = getSize + inputSize;
 if (getSize > 0)
     ++len;
 char *temp = needMem((size_t)len+1);
 for (i=0; i<inputSize; ++i)
     {
     r = getc(stdin);
     if (r == EOF)
 	errAbort("Short POST input.");
     temp[i] = r;
     }
 if (getSize > 0)
   temp[i++] = '&';
 strncpy(temp+i, inputString, getSize);
 temp[len] = 0;
 freeMem(inputString);
 inputString = temp;
 }
 
 #define memmem(hay, haySize, needle, needleSize) \
     memMatch(needle, needleSize, hay, haySize)
 
 #ifndef GBROWSE
 static void cgiParseMultipart(struct hash **retHash, struct cgiVar **retList)
 /* process a multipart form */
 {
 char h[1024];  /* hold mime header line */
 char *s = NULL, *ct = NULL;
 struct dyString *dy = newDyString(256);
 struct mimeBuf *mb = NULL;
 struct mimePart *mp = NULL;
 char **env = NULL;
 struct hash *hash = newHash(6);
 struct cgiVar *list = NULL, *el;
 extern char **environ;
 
 
 //debug
 //fprintf(stderr,"GALT: top of cgiParseMultipart()\n");
 //fflush(stderr);
 
 /* find the CONTENT_ environment strings, use to make Alternate Header string for MIME */
 for(env=environ; *env; env++)
     if (startsWith("CONTENT_",*env))
 	{
 	//debug
         //fprintf(stderr,"%s\n",*env);  //debug
 	safef(h,sizeof(h),"%s",*env);
 	s = strchr(h,'_');    /* change env syntax to MIME style header, from _= to -: */
 	if (!s)
 	    errAbort("expecting '_' parsing env var %s for MIME alt header", *env);
 	*s = '-';
 	s = strchr(h,'=');
 	if (!s)
 	    errAbort("expecting '=' parsing env var %s for MIME alt header", *env);
 	*s = ':';
 	dyStringPrintf(dy,"%s\r\n",h);
 	}
 dyStringAppend(dy,"\r\n");  /* blank line at end means end of headers */
 
 //debug
 //fprintf(stderr,"Alternate Header Text:\n%s",dy->string);
 //fflush(stderr);
 mb = initMimeBuf(STDIN_FILENO);
 //debug
 //fprintf(stderr,"got past initMimeBuf(STDIN_FILENO)\n");
 //fflush(stderr);
 mp = parseMultiParts(mb, cloneString(dy->string)); /* The Alternate Header will get freed */
 freeDyString(&dy);
 if(!mp->multi) /* expecting multipart child parts */
     errAbort("Malformatted multipart-form.");
 
 //debug
 //fprintf(stderr,"GALT: Wow got past parse of MIME!\n");
 //fflush(stderr);
 
 ct = hashFindVal(mp->hdr,"content-type");
 //debug
 //fprintf(stderr,"GALT: main content-type: %s\n",ct);
 //fflush(stderr);
 if (!startsWith("multipart/form-data",ct))
     errAbort("main content-type expected starts with [multipart/form-data], found [%s]",ct);
 
 for(mp=mp->multi;mp;mp=mp->next)
     {
     char *cd = NULL, *cdMain = NULL, *cdName = NULL, *cdFileName = NULL;
     cd = hashFindVal(mp->hdr,"content-disposition");
     //debug
     // char *ct = hashFindVal(mp->hdr,"content-type");
     //fprintf(stderr,"GALT: content-disposition: %s\n",cd);
     //fprintf(stderr,"GALT: content-type: %s\n",ct);
     //fflush(stderr);
     cdMain=getMimeHeaderMainVal(cd);
     cdName=getMimeHeaderFieldVal(cd,"name");
     cdFileName=getMimeHeaderFieldVal(cd,"filename");
     //debug
     //fprintf(stderr,"cgiParseMultipart: main:[%s], name:[%s], filename:[%s]\n",cdMain,cdName,cdFileName);
     //fflush(stderr);
     if (!sameString(cdMain,"form-data"))
 	errAbort("main content-type expected [form-data], found [%s]",cdMain);
 
     //debug
     //fprintf(stderr,"GALT: mp->size[%llu], mp->binary=[%d], mp->fileName=[%s], mp=>data:[%s]\n",
         //(unsigned long long) mp->size, mp->binary, mp->fileName,
         //mp->binary && mp->data ? "<binary data not safe to print>" : mp->data);
     //fflush(stderr);
 
     /* filename if there is one */
     /* Internet Explorer on Windows is sending full path names, strip
      * directory name from those.  Using \ and / and : as potential
      * path separator characters, e.g.:
      *	 C:\Documents and Settings\tmp\file.txt.gz
      */
     if (cdFileName)
 	{
 	char *lastPathSep = strrchr(cdFileName, (int) '\\');
 	if (!lastPathSep)
 		lastPathSep = strrchr(cdFileName, (int) '/');
 	if (!lastPathSep)
 		lastPathSep = strrchr(cdFileName, (int) ':');
 	char varNameFilename[256];
 	safef(varNameFilename, sizeof(varNameFilename), "%s__filename", cdName);
 	AllocVar(el);
 	if (lastPathSep)
 	    el->val = cloneString(lastPathSep+1);
 	else
 	    el->val = cloneString(cdFileName);
         slAddHead(&list, el);
         hashAddSaveName(hash, varNameFilename, el, &el->name);
         }
 
     if (mp->data)
         {
         if (mp->binary)
 	    {
 	    char varNameBinary[256];
 	    char addrSizeBuf[40];
 	    safef(varNameBinary,sizeof(varNameBinary),"%s__binary",cdName);
             safef(addrSizeBuf,sizeof(addrSizeBuf),"%lu %llu",
 		(unsigned long)mp->data,
 		(unsigned long long)mp->size);
 	    AllocVar(el);
 	    el->val = cloneString(addrSizeBuf);
 	    slAddHead(&list, el);
 	    hashAddSaveName(hash, varNameBinary, el, &el->name);
 	    }
 	else  /* normal variable, not too big, does not contain zeros */
 	    {
 	    AllocVar(el);
 	    el->val = mp->data;
 	    slAddHead(&list, el);
 	    hashAddSaveName(hash, cdName, el, &el->name);
 	    }
         }
     else if (mp->fileName)
 	{
 	char varNameData[256];
 	safef(varNameData, sizeof(varNameData), "%s__data", cdName);
 	AllocVar(el);
         el->val = mp->fileName;
         slAddHead(&list, el);
         hashAddSaveName(hash, varNameData, el, &el->name);
         //debug
         //fprintf(stderr,"GALT special: saved varNameData:[%s], mp=>fileName:[%s]\n",el->name,el->val);
         //fflush(stderr);
         }
     else if (mp->multi)
 	{
 	warn("unexpected nested MIME structures");
 	}
     else
 	{
 	errAbort("mp-> type not data,fileName, or multi - unexpected MIME structure");
 	}
 
     freez(&cdMain);
     freez(&cdName);
     freez(&cdFileName);
     }
 
 slReverse(&list);
 *retList = list;
 *retHash = hash;
 }
 #endif /* GBROWSE */
 
 
 
 static void parseCookies(struct hash **retHash, struct cgiVar **retList)
 /* parses any cookies and puts them into the given hash and list */
 {
 char* str;
 char *namePt, *dataPt, *nextNamePt;
 struct hash *hash;
 struct cgiVar *list = NULL, *el;
 
 /* don't build the hash table again */
 if(haveCookiesHash == TRUE)
 	return;
 
 str = cloneString(getenv("HTTP_COOKIE"));
 if(str == NULL) /* don't have a cookie */
 	return;
 
 hash = newHash(6);
 
 namePt = str;
 while (isNotEmpty(namePt))
     {
     dataPt = strchr(namePt, '=');
     if (dataPt == NULL)
 	errAbort("Mangled Cookie input string: no = in '%s' (offset %d in complete cookie string: '%s')",
 		 namePt, (int)(namePt - str), getenv("HTTP_COOKIE"));
     *dataPt++ = 0;
     nextNamePt = strchr(dataPt, ';');
     if (nextNamePt != NULL)
 	{
          *nextNamePt++ = 0;
 	 if (*nextNamePt == ' ')
 	     nextNamePt++;
 	}
     cgiDecode(dataPt,dataPt,strlen(dataPt));
     AllocVar(el);
     el->val = dataPt;
     slAddHead(&list, el);
     hashAddSaveName(hash, namePt, el, &el->name);
     namePt = nextNamePt;
     }
 
 haveCookiesHash = TRUE;
 
 slReverse(&list);
 *retList = list;
 *retHash = hash;
 }
 
 char *findCookieData(char *varName)
 /* Get the string associated with varName from the cookie string. */
 {
 struct hashEl *hel;
 char *firstResult;
 
 /* make sure that the cookie hash table has been created */
 parseCookies(&cookieHash, &cookieList);
 if (cookieHash == NULL)
     return NULL;
 /* Watch out for multiple cookies with the same name (hel is a list) --
  * warn if we find them. */
 hel = hashLookup(cookieHash, varName);
 if (hel == NULL)
     return NULL;
 else
     firstResult = ((struct cgiVar *)hel->val)->val;
 hel = hel->next;
 while (hel != NULL)
     {
     char *val = ((struct cgiVar *)(hel->val))->val;
     if (sameString(varName, hel->name) && !sameString(firstResult, val))
 	{
 	/* This is too early to call warn -- it will mess up html output. */
 	fprintf(stderr,
 		"findCookieData: Duplicate cookie value from IP=%s: "
 		"%s has both %s and %s\n",
 		cgiRemoteAddr(),
 		varName, firstResult, val);
 	}
     hel = hel->next;
     }
 return firstResult;
 }
 
 static char *cgiInputSource(char *s)
 /* For NULL sources make a guess as to real source. */
 {
 char *qs;
 if (s != NULL)
     return s;
 qs = getenv("QUERY_STRING");
 if (qs == NULL)
     return "POST";
 char *cl = getenv("CONTENT_LENGTH");
 if (cl != NULL && atoi(cl) > 0)
     return "POST";
 return "QUERY";
 }
 
 static void _cgiFindInput(char *method)
 /* Get raw CGI input into inputString.  Method can be "POST", "QUERY", "GET" or NULL
  * for unknown. */
 {
 if (inputString == NULL)
     {
     method = cgiInputSource(method);
     if (sameWord(method, "POST"))
         getPostInput();
     else if (sameWord(method, "QUERY") || sameWord(method, "GET"))
         getQueryInput();
     else
         errAbort("Unknown form method");
     }
 }
 
 struct cgiDictionary *cgiDictionaryFromEncodedString(char *encodedString)
 /* Giving a this=that&this=that string,  return cgiDictionary parsed out from it. 
  * This does *not* destroy input like the lower level cgiParse functions do. */
 {
 struct cgiDictionary *d;
 AllocVar(d);
 d->stringData = cloneString(encodedString);
 cgiParseInputAbort(d->stringData, &d->hash, &d->list);
 return d;
 }
 
 void cgiDictionaryFree(struct cgiDictionary **pD)
 /* Free up resources associated with dictionary. */
 {
 struct cgiDictionary *d = *pD;
 if (d != NULL)
     {
     slFreeList(&d->list);
     hashFree(&d->hash);
     freez(&d->stringData);
     freez(pD);
     }
 }
 
 void cgiDictionaryFreeList(struct cgiDictionary **pList)
 /* Free up a whole list of cgiDictionaries */
 {
 struct cgiDictionary *el, *next;
 
 for (el = *pList; el != NULL; el = next)
     {
     next = el->next;
     cgiDictionaryFree(&el);
     }
 *pList = NULL;
 }
 
 boolean cgiParseNext(char **pInput, char **retVar, char **retVal)
 /* Parse out next var/val in a var=val&var=val... cgi formatted string 
  * This will insert zeroes and other things into string. 
  * Usage:
  *     char *pt = cgiStringStart;
  *     char *var, *val
  *     while (cgiParseNext(&pt, &var, &val))
  *          printf("%s\t%s\n", var, val); */
 {
 char *var = *pInput;
 if (var == NULL || var[0] == 0)
     return FALSE;
 char *val = strchr(var, '=');
 if (val == NULL)
     errAbort("Mangled CGI input string %s", var);
 *val++ = 0;
 char *end = strchr(val, '&');
 if (end == NULL)
     end = strchr(val, ';');  // For DAS
 if (end == NULL)
     {
     end = val + strlen(val);
     *pInput = NULL;
     }
 else
     {
     *pInput = end+1;
     *end = 0;
     }
 *retVar = var;
 *retVal = val;
 cgiDecode(val,val,end-val);
 return TRUE;
 }
 
 void cgiParseInputAbort(char *input, struct hash **retHash,
         struct cgiVar **retList)
 /* Parse cgi-style input into a hash table and list.  This will alter
  * the input data.  The hash table will contain references back
  * into input, so please don't free input until you're done with
  * the hash. Prints message aborts if there's an error.
  * To clean up - slFreeList, hashFree, and only then free input. */
 {
 char *namePt, *dataPt, *nextNamePt;
 struct hash *hash = *retHash;
 struct cgiVar *list = *retList, *el;
 
 if (!hash)
   hash = newHash(6);
 slReverse(&list);
 
 namePt = input;
 while (namePt != NULL && namePt[0] != 0)
     {
     dataPt = strchr(namePt, '=');
     if (dataPt == NULL)
 	{
 	errAbort("Mangled CGI input string %s", namePt);
 	}
     *dataPt++ = 0;
     nextNamePt = strchr(dataPt, '&');
     if (nextNamePt == NULL)
 	nextNamePt = strchr(dataPt, ';');	/* Accomodate DAS. */
     if (nextNamePt != NULL)
          *nextNamePt++ = 0;
     cgiDecode(namePt,namePt,strlen(namePt));	/* for unusual ct names */
     cgiDecode(dataPt,dataPt,strlen(dataPt));
     AllocVar(el);
     el->val = dataPt;
     slAddHead(&list, el);
     hashAddSaveName(hash, namePt, el, &el->name);
     namePt = nextNamePt;
     }
 slReverse(&list);
 *retList = list;
 *retHash = hash;
 }
 
 static jmp_buf cgiParseRecover;
 
 static void cgiParseAbort()
 /* Abort cgi parsing. */
 {
 longjmp(cgiParseRecover, -1);
 }
 
 boolean cgiParseInput(char *input, struct hash **retHash,
         struct cgiVar **retList)
 /* Parse cgi-style input into a hash table and list.  This will alter
  * the input data.  The hash table will contain references back
  * into input, so please don't free input until you're done with
  * the hash. Prints message and returns FALSE if there's an error.
  * To clean up - slFreeList, hashFree, and only then free input. */
 {
 boolean ok = TRUE;
 int status = setjmp(cgiParseRecover);
 if (status == 0)    /* Always true except after long jump. */
     {
     pushAbortHandler(cgiParseAbort);
     cgiParseInputAbort(input, retHash, retList);
     }
 else    /* They long jumped here because of an error. */
     {
     ok = FALSE;
     }
 popAbortHandler();
 return ok;
 }
 
 char *cgiStringNewValForVar(char *cgiIn, char *varName, char *newVal)
 /* Return a cgi-encoded string with newVal in place of what was oldVal.
  * It is an error for var not to exist.   Do a freeMem of this string
  * when you are through. */
 {
 struct dyString *dy = dyStringNew(strlen(cgiIn) + strlen(newVal));
 struct cgiParsedVars *cpv = cgiParsedVarsNew(cgiIn);
 struct cgiVar *var;
 boolean doneSub = FALSE;
 for (var = cpv->list; var != NULL; var = var->next)
     {
     char *val = var->val;
     if (sameString(var->name, varName))
 	{
         val = newVal;
 	doneSub = TRUE;
 	}
     cgiEncodeIntoDy(var->name, val, dy);
     }
 if (!doneSub)
     errAbort("Couldn't find %s in %s", varName, cgiIn);
 cgiParsedVarsFree(&cpv);
 return dyStringCannibalize(&dy);
 }
 
 
 static boolean dumpStackOnSignal = FALSE;  // should a stack dump be generated?
 
 static void catchSignal(int sigNum)
 /* handler for various terminal signals for logging purposes */
 {
 char *sig = NULL;
 switch (sigNum)
     {
     case SIGTERM:  // after timing out for not producing any stdout or stderr output for too long,
 		   //  apache gives you 3 seconds to clean up and exit or it then sends SIGKILL.
       sig = "SIGTERM";
       break;
     case SIGHUP:   // apache sends this when it is doing a graceful restart or a log rotate.
       sig = "SIGHUP";
       break;
     case SIGABRT:
       sig = "SIGABRT";
       break;
     case SIGSEGV:
       sig = "SIGSEGV";
       break;
     case SIGFPE:
       sig = "SIGFPE";
       break;
     case SIGBUS:
       sig = "SIGBUS";
       break;
     }
     logCgiToStderr();
 
     // apache closes STDERR on the CGI before it sends the SIGTERM and SIGKILL signals
     //  which means that stderr output after that point will not be visible in error_log.
     fprintf(stderr, "Received signal %s\n", sig);
     if (dumpStackOnSignal)
         dumpStack("Stack for signal %s\n", sig);
 
 if (sigNum == SIGTERM || sigNum == SIGHUP) 
     exit(1);   // so that atexit cleanup get called
 
 raise(SIGKILL);
 }
 
 void initSigHandlers(boolean dumpStack)
 /* set handler for various terminal signals for logging purposes.
  * if dumpStack is TRUE, attempt to dump the stack. */
 {
 if (cgiIsOnWeb())
     {
     // SIGKILL is not trappable or ignorable
     signal(SIGTERM, catchSignal);
     signal(SIGHUP, catchSignal);
     signal(SIGABRT, catchSignal);
     signal(SIGSEGV, catchSignal);
     signal(SIGFPE, catchSignal);
     signal(SIGBUS, catchSignal);
     dumpStackOnSignal = dumpStack;
     }
 }
 
 
 static void initCgiInput()
 /* Initialize CGI input stuff.  After this CGI vars are
  * stored in an internal hash/list regardless of how they
  * were passed to the program. */
 {
 char* s;
 
 if (inputString != NULL)
     return;
 
 _cgiFindInput(NULL);
 
 #ifndef GBROWSE
 /* check to see if the input is a multipart form */
 s = getenv("CONTENT_TYPE");
 if (s != NULL && startsWith("multipart/form-data", s))
     {
     cgiParseMultipart(&inputHash, &inputList);
     }
 #endif /* GBROWSE */
 
 cgiParseInputAbort(inputString, &inputHash, &inputList);
 
 /* now parse the cookies */
 parseCookies(&cookieHash, &cookieList);
 
 /* Set enviroment variables CGIs to enable sql tracing and/or profiling */
 s = cgiOptionalString("JKSQL_TRACE");
 if (s != NULL)
     envUpdate("JKSQL_TRACE", s);
 s = cgiOptionalString("JKSQL_PROF");
 if (s != NULL)
     envUpdate("JKSQL_PROF", s);
 
 }
 
 struct cgiVar *cgiVarList()
 /* return the list of cgiVar's */
 {
 initCgiInput();
 return inputList;
 }
 
 static char *findVarData(char *varName)
 /* Get the string associated with varName from the query string. */
 {
 struct cgiVar *var;
 
 initCgiInput();
 if ((var = hashFindVal(inputHash, varName)) == NULL)
     return NULL;
 return var->val;
 }
 
 void cgiBadVar(char *varName)
 /* Complain about a variable that's not there. */
 {
 if (varName == NULL) varName = "";
 errAbort("Sorry, didn't find CGI input variable '%s'", varName);
 }
 
 static char *mustFindVarData(char *varName)
 /* Find variable and associated data or die trying. */
 {
 char *res = findVarData(varName);
 if (res == NULL)
     cgiBadVar(varName);
 return res;
 }
 
 char *javaScriptLiteralEncode(char *inString)
 /* Use backslash escaping on newline
  * and quote chars, backslash and others.
  * Intended that the encoded string will be
  * put between quotes at a higher level and
  * then interpreted by Javascript. */
 {
 char c;
 int outSize = 0;
 char *outString, *out, *in;
 
 if (inString == NULL)
     return(cloneString(""));
 
 /* Count up how long it will be */
 in = inString;
 while ((c = *in++) != 0)
     {
     if (c == '\''
      || c == '\"'
      || c == '&'
      || c == '\\'
      || c == '\n'
      || c == '\r'
      || c == '\t'
      || c == '\b'
      || c == '\f'
 	)
         outSize += 2;
     else
         outSize += 1;
     }
 outString = needMem(outSize+1);
 
 /* Encode string */
 in = inString;
 out = outString;
 while ((c = *in++) != 0)
     {
     if (c == '\''
      || c == '\"'
      || c == '&'
      || c == '\\'
      || c == '\n'
      || c == '\r'
      || c == '\t'
      || c == '\b'
      || c == '\f'
 	)
         *out++ = '\\';
     *out++ = c;
     }
 *out++ = 0;
 return outString;
 
 }
 
 
 /* NOTE: Where in the URL to use which of these functions:
  *
  * Parts of a URL:
  *   protocol://user:password@server.com:port/path/filename?var1=val1&var2=val2
  *
  * Note that a space should only be encoded to a plus and decoded from a plus
  * when dealing with http URLs in the query part of the string,
  * which is the part after the ? above.
  * It should not be used in the rest of the URL.  
  * So in the query string part of a URL, do use cgiEncode/cgiDecode. 
  * And in the rest of the URL, use cgiEncodeFUll/cgiDecodeFull 
  * which do not code space as plus.
  * Since FTP does not use URLs with query parameters, use the Full version.
  */
 
 void cgiDecode(char *in, char *out, int inLength)
 /* Decode from cgi pluses-for-spaces format to normal.
  * Out will be a little shorter than in typically, and
  * can be the same buffer. */
 {
 char c;
 int i;
 for (i=0; i<inLength;++i)
     {
     c = *in++;
     if (c == '+')
 	*out++ = ' ';
     else if (c == '%')
 	{
 	int code;
         if (sscanf(in, "%2x", &code) != 1)
 	    code = '?';
 	in += 2;
 	i += 2;
 	*out++ = code;
 	}
     else
 	*out++ = c;
     }
 *out++ = 0;
 }
 
 void cgiDecodeFull(char *in, char *out, int inLength)
 /* Out will be a cgi-decoded version of in (no space from plus!).
  * Out will be a little shorter than in typically, and
  * can be the same buffer. */
 {
 char c;
 int i;
 for (i=0; i<inLength;++i)
     {
     c = *in++;
     if (c == '%')
 	{
 	int code;
         if (sscanf(in, "%2x", &code) != 1)
 	    code = '?';
 	in += 2;
 	i += 2;
 	*out++ = code;
 	}
     else
 	*out++ = c;
     }
 *out++ = 0;
 }
 
 char *cgiEncode(char *inString)
 /* Return a cgi-encoded version of inString.
  * Alphanumerics kept as is, space translated to plus,
  * and all other characters translated to %hexVal. */
 {
 char c;
 int outSize = 0;
 char *outString, *out, *in;
 
 if (inString == NULL)
     return(cloneString(""));
 
 /* Count up how long it will be */
 in = inString;
 while ((c = *in++) != 0)
     {
     if (isalnum(c) || c == ' ' || c == '.' || c == '_')
         outSize += 1;
     else
         outSize += 3;
     }
 outString = needMem(outSize+1);
 
 /* Encode string */
 in = inString;
 out = outString;
 while ((c = *in++) != 0)
     {
     if (isalnum(c) || c == '.' || c == '_')
         *out++ = c;
     else if (c == ' ')
         *out++ = '+';
     else
         {
         unsigned char uc = c;
         char buf[4];
         *out++ = '%';
         safef(buf, sizeof(buf), "%02X", uc);
         *out++ = buf[0];
         *out++ = buf[1];
         }
     }
 *out++ = 0;
 return outString;
 }
 
 char *cgiEncodeFull(char *inString)
 /* Return a cgi-encoded version of inString (no + for space!).
  * Alphanumerics/./_ kept as is and all other characters translated to
  * %hexVal. */
 {
 char c;
 int outSize = 0;
 char *outString, *out, *in;
 
 if (inString == NULL)
     return(cloneString(""));
 
 /* Count up how long it will be */
 in = inString;
 while ((c = *in++) != 0)
     {
     if (isalnum(c) || c == '.' || c == '_')
         outSize += 1;
     else
         outSize += 3;
     }
 outString = needMem(outSize+1);
 
 /* Encode string */
 in = inString;
 out = outString;
 while ((c = *in++) != 0)
     {
     if (isalnum(c) || c == '.' || c == '_')
         *out++ = c;
     else
         {
         unsigned char uc = c;
         char buf[4];
         *out++ = '%';
         safef(buf, sizeof(buf), "%02X", uc);
         *out++ = buf[0];
         *out++ = buf[1];
         }
     }
 *out++ = 0;
 return outString;
 }
 
 char *cgiOptionalString(char *varName)
 /* Return value of string if it exists in cgi environment, else NULL */
 {
 return findVarData(varName);
 }
 
 
 char *cgiString(char *varName)
 /* Return string value of cgi variable. */
 {
 return mustFindVarData(varName);
 }
 
 char *cgiUsualString(char *varName, char *usual)
 /* Return value of string if it exists in cgi environment.
  * Otherwise return 'usual' */
 {
 char *pt;
 pt = findVarData(varName);
 if (pt == NULL)
     pt = usual;
 return pt;
 }
 
 struct slName *cgiStringList(char *varName)
 /* Find list of cgi variables with given name.  This
  * may be empty.  Free result with slFreeList(). */
 {
 struct hashEl *hel;
 struct slName *stringList = NULL, *string;
 
 initCgiInput();
 for (hel = hashLookup(inputHash, varName); hel != NULL; hel = hel->next)
     {
     if (sameString(hel->name, varName))
         {
 	struct cgiVar *var = hel->val;
 	string = newSlName(var->val);
 	slAddHead(&stringList, string);
 	}
     }
 return stringList;
 }
 
 
 int cgiInt(char *varName)
 /* Return int value of cgi variable. */
 {
 char *data;
 char c;
 
 data = mustFindVarData(varName);
 data = skipLeadingSpaces(data);
 c = data[0];
 if (!(isdigit(c) || (c == '-' && isdigit(data[1]))))
      errAbort("Expecting number in %s, got \"%s\"\n", varName, data);
 return atoi(data);
 }
 
 int cgiIntExp(char *varName)
 /* Evaluate an integer expression in varName and
  * return value. */
 {
 return intExp(cgiString(varName));
 }
 
 int cgiOptionalInt(char *varName, int defaultVal)
 /* This returns integer value of varName if it exists in cgi environment
  * and it's not just the empty string otherwise it returns defaultVal. */
 {
 char *s = cgiOptionalString(varName);
 s = skipLeadingSpaces(s);
 if (isEmpty(s))
     return defaultVal;
 return cgiInt(varName);
 }
 
 double cgiDouble(char *varName)
 /* Returns double value. */
 {
 char *data;
 double x;
 
 data = mustFindVarData(varName);
 if (sscanf(data, "%lf", &x)<1)
      errAbort("Expecting real number in %s, got \"%s\"\n", varName, data);
 return x;
 }
 
 double cgiOptionalDouble(char *varName, double defaultVal)
 /* Returns double value. */
 {
 if (!cgiVarExists(varName))
     return defaultVal;
 return cgiDouble(varName);
 }
 
 boolean cgiVarExists(char *varName)
 /* Returns TRUE if the variable was passed in. */
 {
 initCgiInput();
 return hashLookup(inputHash, varName) != NULL;
 }
 
 boolean cgiBoolean(char *varName)
 {
 return cgiVarExists(varName);
 }
 
 int cgiOneChoice(char *varName, struct cgiChoice *choices, int choiceSize)
 /* Returns value associated with string variable in choice table. */
 {
 char *key = cgiString(varName);
 int i;
 int val = -1;
 
 for (i=0; i<choiceSize; ++i)
     {
     if (sameWord(choices[i].name, key))
         {
         val =  choices[i].value;
         return val;
         }
     }
 errAbort("Unknown key %s for variable %s\n", key, varName);
 return val;
 }
 
 void cgiMakeSubmitButton()
 /* Make 'submit' type button. */
 {
 cgiMakeButton("Submit", "Submit");
 }
 
 void cgiMakeResetButton()
 /* Make 'reset' type button. */
 {
 printf("<INPUT TYPE=RESET NAME=\"Reset\" VALUE=\" Reset \">");
 }
 
 void cgiMakeClearButton(char *form, char *field)
 /* Make button to clear a text field. */
 {
 char javascript[1024];
 
 safef(javascript, sizeof(javascript),
     "document.%s.%s.value = ''; document.%s.submit();", form, field, form);
 cgiMakeOnClickButton(javascript, " Clear  ");
 }
 
 void cgiMakeButtonWithMsg(char *name, char *value, char *msg)
 /* Make 'submit' type button. Display msg on mouseover, if present*/
 {
 printf("<INPUT TYPE=SUBMIT NAME=\"%s\" VALUE=\"%s\" %s%s%s>",
         name, value,
         (msg ? " TITLE=\"" : ""), (msg ? msg : ""), (msg ? "\"" : "" ));
 }
 
 void cgiMakeButtonWithOnClick(char *name, char *value, char *msg, char *onClick)
 /* Make 'submit' type button, with onclick javascript */
 {
 printf("<input type=\"submit\" name=\"%s\" value=\"%s\" onclick=\"%s\" %s%s%s>",
         name, value, onClick,
         (msg ? " TITLE=\"" : ""), (msg ? msg : ""), (msg ? "\"" : "" ));
 }
 
 void cgiMakeButton(char *name, char *value)
 /* Make 'submit' type button */
 {
 cgiMakeButtonWithMsg(name, value, NULL);
 }
 
 void cgiMakeOnClickButton(char *command, char *value)
 /* Make 'push' type button with client side onClick (java)script. */
 {
 printf("<INPUT TYPE=\"button\" VALUE=\"%s\" onClick=\"%s\">", value, command);
 }
 
 void cgiMakeOnClickSubmitButton(char *command, char *name, char *value)
 /* Make submit button with both variable name and value with client side
  * onClick (java)script. */
 {
 printf("<INPUT TYPE=SUBMIT NAME=\"%s\" VALUE=\"%s\" onClick=\"%s\">",
        name, value, command);
 }
 
 void cgiMakeOptionalButton(char *name, char *value, boolean disabled)
 /* Make 'submit' type button that can be disabled. */
 {
 printf("<INPUT TYPE=SUBMIT NAME=\"%s\" VALUE=\"%s\"", name, value);
 if (disabled)
     printf(" DISABLED");
 printf(">");
 }
 
 void cgiMakeFileEntry(char *name)
 /* Make file entry box/browser */
 {
     printf("<INPUT TYPE=FILE NAME=\"%s\">", name);
 }
 
 void cgiSimpleTableStart()
 /* start HTML table  -- no customization. Leaves room
  * for a fancier implementation */
 {
 printf("<TABLE>\n");
 }
 
 void cgiTableEnd()
 /* end HTML table */
 {
 printf("</TABLE>\n");
 }
 
 void cgiSimpleTableRowStart()
 /* Start table row */
 {
 printf("<TR>\n");
 }
 
 void cgiTableRowEnd()
 /* End table row */
 {
 printf("</TR>\n");
 }
 
 void cgiSimpleTableFieldStart()
 /* Start table field */
 {
 printf("<TD>");
 }
 
 void cgiTableFieldStartAlignRight()
 /* Start table field and align right*/
 {
 printf("<TD ALIGN = RIGHT>");
 }
 
 void cgiTableFieldEnd()
 /* End table field */
 {
 printf("</TD>\n");
 }
 
 void cgiTableField(char *text)
 /* Make table field entry */
 {
 printf("<TD> %s </TD>\n", text);
 }
 
 void cgiTableFieldWithMsg(char *text, char *msg)
 /* Make table field entry with mouseover */
 {
 printf("<TD %s%s%s> %s </TD>\n",
         (msg ? " TITLE=\"" : ""), (msg ? msg : ""), (msg ? "\"" : "" ),
         text);
 }
 
 void cgiParagraph(char *text)
 /* Make text paragraph */
 {
 printf("<P> %s\n", text);
 }
 
 void cgiMakeRadioButton(char *name, char *value, boolean checked)
 /* Make radio type button.  A group of radio buttons should have the
  * same name but different values.   The default selection should be
  * sent with checked on. */
 {
 printf("<INPUT TYPE=RADIO NAME=\"%s\" VALUE=\"%s\" %s>", name, value,
    (checked ? "CHECKED" : ""));
 }
 
 void cgiMakeOnClickRadioButton(char *name, char *value, boolean checked,
                                         char *command)
 /* Make radio type button with onClick command.
  *  A group of radio buttons should have the
  * same name but different values.   The default selection should be
  * sent with checked on. */
 {
 printf("<INPUT TYPE=RADIO NAME=\"%s\" VALUE=\"%s\" %s %s>",
         name, value, command, (checked ? "CHECKED" : ""));
 }
 
 char *cgiBooleanShadowPrefix()
 /* Prefix for shadow variable set with boolean variables. */
 {
 return "boolshad.";
 }
 #define BOOLSHAD_EXTRA "class='cbShadow'"
 
 boolean cgiBooleanDefined(char *name)
 /* Return TRUE if boolean variable is defined (by
  * checking for shadow. */
 {
 char buf[256];
 safef(buf, sizeof(buf), "%s%s", cgiBooleanShadowPrefix(), name);
 return cgiVarExists(buf);
 }
 
 static void cgiMakeCheckBox2Bool(char *name, boolean checked, boolean enabled,
                                  char *id, char *moreHtml)
 /* Make check box - designed to be called by the variously overloaded
  * cgiMakeCheckBox functions, and should NOT be called directly.
  * moreHtml: optional additional html like javascript call or mouseover msg (may be NULL)
  * id: button id (may be NULL)
  * Also make a shadow hidden variable and support 2 boolean states:
  *    checked/unchecked and enabled/disabled. */
 {
 char buf[256], idBuf[256];
 
 if(id)
     safef(idBuf, sizeof(idBuf), " id=\"%s\"", id);
 else
     idBuf[0] = 0;
 
 printf("<INPUT TYPE=CHECKBOX NAME=\"%s\"%s VALUE=on %s%s%s>", name, idBuf,
         (moreHtml ? moreHtml : ""),
         (checked ? " CHECKED" : ""),
         (enabled ? "" : " DISABLED"));
 safef(buf, sizeof(buf), "%s%s", cgiBooleanShadowPrefix(), name);
 cgiMakeHiddenVarWithExtra(buf, ( enabled ? "0" : (checked ? "-1" : "-2")),BOOLSHAD_EXTRA);
 }
 
 void cgiMakeCheckBoxUtil(char *name, boolean checked, char *msg, char *id)
 /* Make check box - can be called directly, though it was originally meant
  * as the common code for all lower level checkbox routines.
  * However, it's util functionality has been taken over by
  * cgiMakeCheckBoxWithIdAndOptionalHtml() */
 {
 char buf[256];
 
 if (msg)
     safef(buf, sizeof(buf), "TITLE=\"%s\"", msg);
 else
     buf[0] = 0;
 
 cgiMakeCheckBox2Bool(name, checked, TRUE, id, buf);
 }
 
 void cgiMakeCheckBoxWithMsg(char *name, boolean checked, char *msg)
 {
 char buf[512];
 safef(buf, sizeof(buf), "title='%s'", msg);
 cgiMakeCheckBox2Bool(name, checked, TRUE, NULL, buf);
 }
 
 void cgiMakeCheckBoxWithId(char *name, boolean checked, char *id)
 /* Make check box, which includes an ID. */
 {
 cgiMakeCheckBox2Bool(name, checked, TRUE, id, NULL);
 }
 
 void cgiMakeCheckBox(char *name, boolean checked)
 /* Make check box. */
 {
 cgiMakeCheckBox2Bool(name, checked, TRUE, NULL, NULL);
 }
 
+void cgiMakeCheckBoxEnabled(char *name, boolean checked, boolean enabled)
+/* Make check box, optionally enabled/disabled. */
+{
+cgiMakeCheckBox2Bool(name, checked, enabled, NULL, NULL);
+}
+
 void cgiMakeCheckBoxJS(char *name, boolean checked, char *javascript)
 /* Make check box with javascript. */
 {
 cgiMakeCheckBox2Bool(name,checked,TRUE,NULL,javascript);
 }
 
 void cgiMakeCheckBoxIdAndJS(char *name, boolean checked, char *id, char *javascript)
 /* Make check box with ID and javascript. */
 {
 cgiMakeCheckBox2Bool(name,checked,TRUE,id,javascript);
 }
 
 void cgiMakeCheckBoxFourWay(char *name, boolean checked, boolean enabled, char *id,
                             char *classes, char *moreHtml)
 /* Make check box - with fourWay functionality (checked/unchecked by enabled/disabled)
  * Also makes a shadow hidden variable that supports the 2 boolean states. */
 {
 char shadName[256];
 
 printf("<INPUT TYPE=CHECKBOX NAME='%s'", name);
 if (id)
     printf(" id='%s'", id);
 if (checked)
     printf(" CHECKED");
 if (!enabled)
     {
     if (findWordByDelimiter("disabled",' ', classes) == NULL) // fauxDisabled ?
         printf(" DISABLED");
     }
 if (classes)
     printf(" class='%s'",classes);
 if (moreHtml)
     printf(" %s",moreHtml);
 printf(">");
 
 // The hidden var needs to hold the 4way state
 safef(shadName, sizeof(shadName), "%s%s", cgiBooleanShadowPrefix(), name);
 cgiMakeHiddenVarWithExtra(shadName, ( enabled ? "0" : (checked ? "-1" : "-2")),BOOLSHAD_EXTRA);
 }
 
 
 void cgiMakeHiddenBoolean(char *name, boolean on)
 /* Make hidden boolean variable. Also make a shadow hidden variable so we
  * can distinguish between variable not present and
  * variable set to false. */
 {
 char buf[256];
 cgiMakeHiddenVar(name, on ? "on" : "off");
 safef(buf, sizeof(buf), "%s%s", cgiBooleanShadowPrefix(), name);
 cgiMakeHiddenVarWithExtra(buf, "1",BOOLSHAD_EXTRA);
 }
 
 void cgiMakeTextArea(char *varName, char *initialVal, int rowCount, int columnCount)
 /* Make a text area with area rowCount X columnCount and with text: intialVal */
 {
 cgiMakeTextAreaDisableable(varName, initialVal, rowCount, columnCount, FALSE);
 }
 
 void cgiMakeTextAreaDisableable(char *varName, char *initialVal, int rowCount, int columnCount, boolean disabled)
 /* Make a text area that can be disabled. The rea has rowCount X
  * columnCount and with text: intialVal */
 {
 printf("<TEXTAREA NAME=\"%s\" ROWS=%d COLS=%d %s>%s</TEXTAREA>", varName,
        rowCount, columnCount, disabled ? "DISABLED" : "",
        (initialVal != NULL ? initialVal : ""));
 }
 
 void cgiMakeOnKeypressTextVar(char *varName, char *initialVal, int charSize,
 			      char *script)
 /* Make a text control filled with initial value, with a (java)script
  * to execute every time a key is pressed.  If charSize is zero it's
  * calculated from initialVal size. */
 {
 if (initialVal == NULL)
     initialVal = "";
 if (charSize == 0) charSize = strlen(initialVal);
 if (charSize == 0) charSize = 8;
 
 printf("<INPUT TYPE=TEXT NAME=\"%s\" SIZE=%d VALUE=\"%s\"", varName,
         charSize, initialVal);
 if (isNotEmpty(script))
     printf(" onkeypress=\"%s\"", script);
 printf(">\n");
 }
 
 void cgiMakeTextVar(char *varName, char *initialVal, int charSize)
 /* Make a text control filled with initial value.  If charSize
  * is zero it's calculated from initialVal size. */
 {
 cgiMakeOnKeypressTextVar(varName, initialVal, charSize, NULL);
 }
 
 void cgiMakeTextVarWithExtraHtml(char *varName, char *initialVal, int width, char *extra)
 /* Make a text control filled with initial value. */
 {
 if (initialVal == NULL)
     initialVal = "";
 if (width==0)
     width=strlen(initialVal)*10;
 if (width==0)
     width = 100;
 
 printf("<INPUT TYPE=TEXT class='inputBox' NAME=\"%s\" style='width: %dpx' VALUE=\"%s\"",
        varName,width, initialVal);
 if (isNotEmpty(extra))
     printf(" %s",extra);
 printf(">\n");
 }
 
 void cgiMakeIntVarWithExtra(char *varName, int initialVal, int maxDigits, char *extra)
 /* Make a text control filled with initial value and optional extra HTML.  */
 {
 if (maxDigits == 0) maxDigits = 4;
 printf("<INPUT TYPE=TEXT NAME=\"%s\" SIZE=%d VALUE=%d %s>", 
                 varName, maxDigits, initialVal, extra ? extra : "");
 }
 
 void cgiMakeIntVar(char *varName, int initialVal, int maxDigits)
 /* Make a text control filled with initial value.  */
 {
 cgiMakeIntVarWithExtra(varName, initialVal, maxDigits, NULL);
 }
 
 void cgiMakeIntVarInRange(char *varName, int initialVal, char *title, int width,
                           char *min, char *max)
 /* Make a integer control filled with initial value.
    If min and/or max are non-NULL will enforce range
    Requires utils.js jQuery.js and inputBox class */
 {
 if (width==0)
     {
     if (max)
         width=strlen(max)*10;
     else
         {
         int sz=initialVal+1000;
         if (min)
             sz=atoi(min) + 1000;
         width = 10;
         while (sz/=10)
             width+=10;
         }
     }
 if (width < 65)
     width = 65;
 
 printf("<INPUT TYPE=TEXT class='inputBox' name=\"%s\" style='width: %dpx' value=%d",
        varName,width,initialVal);
 printf(" onChange='return validateInt(this,%s,%s);'",
        (min ? min : "\"null\""),(max ? max : "\"null\""));
 if (title)
     printf(" title='%s'",title);
 printf(">\n");
 }
 
 void cgiMakeIntVarWithLimits(char *varName, int initialVal, char *title, int width,
                              int min, int max)
 {
 char minLimit[20];
 char maxLimit[20];
 char *minStr=NULL;
 char *maxStr=NULL;
 if (min != NO_VALUE)
     {
     safef(minLimit,sizeof(minLimit),"%d",min);
     minStr = minLimit;
     }
 if (max != NO_VALUE)
     {
     safef(maxLimit,sizeof(maxLimit),"%d",max);
     maxStr = maxLimit;
     }
 cgiMakeIntVarInRange(varName,initialVal,title,width,minStr,maxStr);
 }
 
 void cgiMakeIntVarWithMin(char *varName, int initialVal, char *title, int width, int min)
 {
 char minLimit[20];
 char *minStr=NULL;
 if (min != NO_VALUE)
     {
     safef(minLimit,sizeof(minLimit),"%d",min);
     minStr = minLimit;
     }
 cgiMakeIntVarInRange(varName,initialVal,title,width,minStr,NULL);
 }
 
 void cgiMakeIntVarWithMax(char *varName, int initialVal, char *title, int width, int max)
 {
 char maxLimit[20];
 char *maxStr=NULL;
 if (max != NO_VALUE)
     {
     safef(maxLimit,sizeof(maxLimit),"%d",max);
     maxStr = maxLimit;
     }
 cgiMakeIntVarInRange(varName,initialVal,title,width,NULL,maxStr);
 }
 
 void cgiMakeDoubleVar(char *varName, double initialVal, int maxDigits)
 /* Make a text control filled with initial floating-point value.  */
 {
 if (maxDigits == 0) maxDigits = 4;
 
 printf("<INPUT TYPE=TEXT NAME=\"%s\" SIZE=%d VALUE=%g>", varName,
         maxDigits, initialVal);
 }
 
 void cgiMakeDoubleVarInRange(char *varName, double initialVal, char *title, int width,
                              char *min, char *max)
 /* Make a floating point control filled with initial value.
    If min and/or max are non-NULL will enforce range
    Requires utils.js jQuery.js and inputBox class */
 {
 if (width==0)
     {
     if (max)
         width=strlen(max)*10;
     }
 if (width < 65)
     width = 65;
 
 printf("<INPUT TYPE=TEXT class='inputBox' name=\"%s\" style='width: %dpx' value=%g",
        varName,width,initialVal);
 printf(" onChange='return validateFloat(this,%s,%s);'",
        (min ? min : "\"null\""),(max ? max : "\"null\""));
 if (title)
     printf(" title='%s'",title);
 printf(">\n");
 }
 
 void cgiMakeDoubleVarWithLimits(char *varName, double initialVal, char *title, int width,
                                 double min, double max)
 {
 char minLimit[20];
 char maxLimit[20];
 char *minStr=NULL;
 char *maxStr=NULL;
 if ((int)min != NO_VALUE)
     {
     safef(minLimit,sizeof(minLimit),"%g",min);
     minStr = minLimit;
     }
 if ((int)max != NO_VALUE)
     {
     safef(maxLimit,sizeof(maxLimit),"%g",max);
     maxStr = maxLimit;
     }
 cgiMakeDoubleVarInRange(varName,initialVal,title,width,minStr,maxStr);
 }
 
 void cgiMakeDoubleVarWithMin(char *varName, double initialVal, char *title, int width, double min)
 {
 char minLimit[20];
 char *minStr=NULL;
 if ((int)min != NO_VALUE)
     {
     safef(minLimit,sizeof(minLimit),"%g",min);
     minStr = minLimit;
     }
 cgiMakeDoubleVarInRange(varName,initialVal,title,width,minStr,NULL);
 }
 
 void cgiMakeDoubleVarWithMax(char *varName, double initialVal, char *title, int width, double max)
 {
 char maxLimit[20];
 char *maxStr=NULL;
 if ((int)max != NO_VALUE)
     {
     safef(maxLimit,sizeof(maxLimit),"%g",max);
     maxStr = maxLimit;
     }
 cgiMakeDoubleVarInRange(varName,initialVal,title,width,NULL,maxStr);
 }
 
 void cgiMakeDropListClassWithStyleAndJavascript(char *name, char *menu[],
         int menuSize, char *checked, char *class, char *style,char *javascript)
 /* Make a drop-down list with names, text class, style and javascript. */
 {
 int i;
 char *selString;
 if (checked == NULL) checked = menu[0];
 printf("<SELECT");
 if (name)
     printf(" NAME='%s'", name);
 if (class)
     printf(" class='%s'", class);
 if (style)
     printf(" style='%s'", style);
 if (javascript)
     printf(" %s", javascript);
 printf(">\n");
 for (i=0; i<menuSize; ++i)
     {
     if (sameWord(menu[i], checked))
         selString = " SELECTED";
     else
         selString = "";
     printf("<OPTION%s>%s</OPTION>\n", selString, menu[i]);
     }
 printf("</SELECT>\n");
 }
 
 void cgiMakeDropListClassWithStyle(char *name, char *menu[],
                                    int menuSize, char *checked, char *class, char *style)
 /* Make a drop-down list with names, text class and style. */
 {
 cgiMakeDropListClassWithStyleAndJavascript(name,menu,menuSize,checked,class,style,"");
 }
 
 void cgiMakeDropListClass(char *name, char *menu[],
                           int menuSize, char *checked, char *class)
 /* Make a drop-down list with names. */
 {
 cgiMakeDropListClassWithStyle(name, menu, menuSize, checked, class, NULL);
 }
 
 void cgiMakeDropList(char *name, char *menu[], int menuSize, char *checked)
 /* Make a drop-down list with names.
  * uses style "normalText" */
 {
     cgiMakeDropListClass(name, menu, menuSize, checked, "normalText");
 }
 
 char *cgiMultListShadowPrefix()
 /* Prefix for shadow variable set with multi-select inputs. */
 {
 return "multishad.";
 }
 
 void cgiMakeMultList(char *name, char *menu[], int menuSize, struct slName *checked, int length)
 /* Make a list of names with window height equalt to length,
  * which can have multiple selections. Same as drop-down list
  * except "multiple" is added to select tag. */
 {
 int i;
 char *selString;
 if (checked == NULL) checked = slNameNew(menu[0]);
 printf("<SELECT MULTIPLE SIZE=%d ALIGN=CENTER NAME=\"%s\">\n", length, name);
 for (i=0; i<menuSize; ++i)
     {
     if (slNameInList(checked, menu[i]))
         selString = " SELECTED";
     else
         selString = "";
     printf("<OPTION%s>%s</OPTION>\n", selString, menu[i]);
     }
 printf("</SELECT>\n");
 char buf[512];
 safef(buf, sizeof(buf), "%s%s", cgiMultListShadowPrefix(), name);
 cgiMakeHiddenVar(buf, "1");
 }
 
 void cgiMakeCheckboxGroupWithVals(char *name, char *menu[], char *values[], int menuSize,
 				  struct slName *checked, int tableColumns)
 /* Make a table of checkboxes that have the same variable name but different
  * values (same behavior as a multi-select input), with nice labels in menu[]. */
 {
 int i;
 if (values == NULL) values = menu;
 if (menu == NULL) menu = values;
 puts("<TABLE BORDERWIDTH=0><TR>");
 for (i = 0;  i < menuSize;  i++)
     {
     if (i > 0 && (i % tableColumns) == 0)
 	printf("</TR><TR>");
     printf("<TD><INPUT TYPE=CHECKBOX NAME=\"%s\" VALUE=\"%s\" %s></TD>"
 	   "<TD>%s</TD>\n",
 	   name, values[i], (slNameInList(checked, values[i]) ? "CHECKED" : ""),
 	   menu[i]);
     }
 if ((i % tableColumns) != 0)
     while ((i++ % tableColumns) != 0)
 	printf("<TD></TD>");
 puts("</TR></TABLE>");
 char buf[512];
 safef(buf, sizeof(buf), "%s%s", cgiMultListShadowPrefix(), name);
 cgiMakeHiddenVar(buf, "0");
 }
 
 void cgiMakeCheckboxGroup(char *name, char *menu[], int menuSize, struct slName *checked,
 			  int tableColumns)
 /* Make a table of checkboxes that have the same variable name but different
  * values (same behavior as a multi-select input). */
 {
 cgiMakeCheckboxGroupWithVals(name, menu, NULL, menuSize, checked, tableColumns);
 }
 
 void cgiMakeDropListFull(char *name, char *menu[], char *values[],
                          int menuSize, char *checked, char *extraAttribs)
 /* Make a drop-down list with names and values. */
 {
 int i;
 char *selString;
 if (checked == NULL) checked = menu[0];
 
 if (NULL != extraAttribs)
     {
     printf("<SELECT NAME=\"%s\" %s>\n", name, extraAttribs);
     }
 else
     {
     printf("<SELECT NAME=\"%s\">\n", name);
     }
 
 for (i=0; i<menuSize; ++i)
     {
     if (sameWord(values[i], checked))
         selString = " SELECTED";
     else
         selString = "";
     printf("<OPTION%s VALUE=\"%s\">%s</OPTION>\n", selString, values[i], menu[i]);
     }
 printf("</SELECT>\n");
 }
 
 char *cgiMakeSelectDropList(boolean multiple, char *name, struct slPair *valsAndLabels,
                             char *selected, char *anyAll,char *extraClasses, char *extraHtml)
 // Returns allocated string of HTML defining a drop-down select
 // (if multiple, REQUIRES ui-dropdownchecklist.js)
 // valsAndLabels: val (pair->name) must be filled in but label (pair->val) may be NULL.
 // selected: if not NULL is a val found in the valsAndLabels (multiple then comma delimited list).
 //           If null and anyAll not NULL, that will be selected
 // anyAll: if not NULL is the string for an initial option. It can contain val and label,
 //         delimited by a comma
 // extraHtml: if not NULL contains id, javascript calls and style.
 //            It does NOT contain class definitions
 {
 struct dyString *output = dyStringNew(1024);
 boolean checked = FALSE;
 
 dyStringPrintf(output,"<SELECT name='%s'",name);
 if (multiple)
     dyStringAppend(output," MULTIPLE");
 if (extraClasses != NULL)
     dyStringPrintf(output," class='%s%s'",extraClasses,(multiple ? " filterBy" : ""));
 else if (multiple)
     dyStringAppend(output," class='filterBy'");
 
 if (extraHtml != NULL)
     dyStringPrintf(output," %s",extraHtml);
 dyStringAppend(output,">\n");
 
 // Handle initial option "Any" or "All"
 if (anyAll != NULL)
     {
     char *val = anyAll;  // Could contain a label after the value
     char *label = strchr(val,',');  // Could contain a label after the value
     if (label != NULL)
         {
         val = cloneString(anyAll);
         label = strchr(val,',');  // again because this is new mem
         *label = '\0';
         label = label+1;
         }
     else
         label = val;
     checked = TRUE; // The default case
     if (selected != NULL)
         {
         if (multiple)
             checked = (findWordByDelimiter(val,',', selected) != NULL);
         else
             checked = sameString(val,selected);
         }
     dyStringPrintf(output, "<OPTION%s VALUE='%s'>%s</OPTION>\n",(checked ? " SELECTED" : ""),
                    val, javaScriptLiteralEncode(label));
     if (label != val)
         freeMem(val);
     }
 
 // All other options
 struct slPair *valPair = valsAndLabels;
 for (; valPair != NULL; valPair = valPair->next)
     {
     checked = FALSE;
     if (selected != NULL)
         {
         if (multiple)
             checked = (findWordByDelimiter(valPair->name,',', selected) != NULL);
         else
             checked = sameString(valPair->name,selected);
         }
     char *label = valPair->name;
     if (valPair->val != NULL)
         label = valPair->val;
     dyStringPrintf(output, "<OPTION%s VALUE='%s'>%s</OPTION>\n",(checked ? " SELECTED" : ""),
                    (char *)valPair->name, javaScriptLiteralEncode(label));
     }
 
 dyStringPrintf(output,"</SELECT>\n");
 
 return dyStringCannibalize(&output);
 }
 
 void cgiMakeDropListWithVals(char *name, char *menu[], char *values[],
                          int menuSize, char *checked)
 /* Make a drop-down list with names and values. In this case checked
  * corresponds to a value, not a menu. */
 {
 int i;
 char *selString;
 if (checked == NULL) checked = values[0];
 
 printf("<SELECT NAME=\"%s\">\n", name);
 for (i=0; i<menuSize; ++i)
     {
     if (sameWord(values[i], checked))
         selString = " SELECTED";
     else
         selString = "";
     printf("<OPTION%s VALUE=\"%s\">%s</OPTION>\n", selString, values[i], menu[i]);
     }
 printf("</SELECT>\n");
 }
 
 void cgiDropDownWithTextValsAndExtra(char *name, char *text[], char *values[],
                                      int count, char *selected, char *extra)
 /* Make a drop-down list with both text and values. */
 {
 int i;
 char *selString;
 assert(values != NULL && text != NULL);
 if (selected == NULL)
     selected = values[0];
 printf("<SELECT");
 if (name)
     printf(" NAME='%s'", name);
 if (extra)
     printf("%s", extra);
 printf(">\n");
 for (i=0; i<count; ++i)
     {
     if (sameWord(values[i], selected))
         selString = " SELECTED";
     else
         selString = "";
     printf("<OPTION%s value='%s'>%s</OPTION>\n", selString, values[i], text[i]);
     }
 printf("</SELECT>\n");
 }
 
 void cgiMakeHiddenVarWithExtra(char *varName, char *string,char *extra)
 /* Store string in hidden input for next time around. */
 {
 printf("<INPUT TYPE=HIDDEN NAME='%s' VALUE='%s'", varName, string);
 if (extra)
     printf(" %s>\n",extra);
 else
     puts(">");
 }
 
 void cgiContinueHiddenVar(char *varName)
 /* Write CGI var back to hidden input for next time around. */
 {
 if (cgiVarExists(varName))
     cgiMakeHiddenVar(varName, cgiString(varName));
 }
 
 void cgiVarExclude(char *varName)
 /* If varName exists, remove it. */
 {
 if (cgiVarExists(varName))
     {
     struct cgiVar *cv = hashRemove(inputHash, varName);
     slRemoveEl(&inputList, cv);
     }
 }
 
 void cgiVarExcludeExcept(char **varNames)
 /* Exclude all variables except for those in NULL
  * terminated array varNames.  varNames may be NULL
  * in which case nothing is excluded. */
 {
 struct hashEl *list, *el;
 struct hash *exclude = newHash(8);
 char *s;
 
 /* Build up hash of things to exclude */
 if (varNames != NULL)
    {
    while ((s = *varNames++) != NULL)
        hashAdd(exclude, s, NULL);
    }
 
 /* Step through variable list and remove them if not
  * excluded. */
 initCgiInput();
 list = hashElListHash(inputHash);
 for (el = list; el != NULL; el = el->next)
     {
     if (!hashLookup(exclude, el->name))
         cgiVarExclude(el->name);
     }
 hashElFreeList(&list);
 freeHash(&exclude);
 }
 
 void cgiVarSet(char *varName, char *val)
 /* Set a cgi variable to a particular value. */
 {
 struct cgiVar *var;
 initCgiInput();
 AllocVar(var);
 var->val = cloneString(val);
 hashAddSaveName(inputHash, varName, var, &var->name);
 }
 
 struct dyString *cgiUrlString()
 /* Get URL-formatted that expresses current CGI variable state. */
 {
 struct dyString *dy = newDyString(0);
 struct cgiVar *cv;
 char *e;
 
 
 for (cv = inputList; cv != NULL; cv = cv->next)
     {
     if (cv != inputList)
        dyStringAppend(dy, "&");
     e = cgiEncode(cv->val);
     dyStringPrintf(dy, "%s=", cv->name);
     dyStringAppend(dy, e);
     freez(&e);
     }
 return dy;
 }
 
 void cgiEncodeIntoDy(char *var, char *val, struct dyString *dy)
 /* Add a CGI-encoded &var=val string to dy. */
 {
 if (dy->stringSize != 0)
     dyStringAppendC(dy, '&');
 dyStringAppend(dy, var);
 dyStringAppendC(dy, '=');
 char *s = cgiEncode(val);
 dyStringAppend(dy, s);
 freez(&s);
 }
 
 void cgiContinueAllVars()
 /* Write back all CGI vars as hidden input for next time around. */
 {
 struct cgiVar *cv;
 for (cv = inputList; cv != NULL; cv = cv->next)
     cgiMakeHiddenVar(cv->name, cv->val);
 }
 
 
 boolean cgiFromCommandLine(int *pArgc, char *argv[], boolean preferWeb)
 /* Use the command line to set up things as if we were a CGI program.
  * User types in command line (assuming your program called cgiScript)
  * like:
  *        cgiScript nonCgiArg1 var1=value1 var2=value2 var3=value3 nonCgiArg2
  * or like
  *        cgiScript nonCgiArg1 var1=value1&var2=value2&var3=value3 nonCgiArg2
  * or even like
  *        cgiScript nonCgiArg1 -x -y=bogus z=really
  * (The non-cgi arguments can occur anywhere.  The cgi arguments (all containing
  * the character '=' or starting with '-') are erased from argc/argv.  Normally
  * you call this cgiSpoof(&argc, argv);
  */
 {
 int argc = *pArgc;
 int i;
 int argcLeft = argc;
 char *name;
 static char queryString[65536];
 char *q = queryString;
 boolean needAnd = TRUE;
 boolean gotAny = FALSE;
 boolean startDash;
 boolean gotEq;
 static char hostLine[512];
 
 if (preferWeb && cgiIsOnWeb())
     return TRUE;	/* No spoofing required! */
 q += safef(q, queryString + sizeof(queryString) - q,
 	   "%s", "QUERY_STRING=cgiSpoof=on");
 for (i=0; i<argcLeft; )
     {
     name = argv[i];
     if ((startDash = (name[0] == '-')))
        ++name;
     gotEq = (strchr(name, '=') != NULL);
     if (gotEq || startDash)
         {
         if (needAnd)
             *q++ = '&';
         q += safef(q, queryString + sizeof(queryString) - q, "%s", name);
         if (!gotEq || strchr(name, '&') == NULL)
             needAnd = TRUE;
 	if (!gotEq)
 	    q += safef(q, queryString + sizeof(queryString) - q, "=on");
         memcpy(&argv[i], &argv[i+1], sizeof(argv[i]) * (argcLeft-i-1));
         argcLeft -= 1;
         gotAny = TRUE;
         }
     else
         i++;
     }
 if (gotAny)
     {
     *pArgc = argcLeft;
     }
 putenv("REQUEST_METHOD=GET");
 putenv(queryString);
 char *host = getenv("HOST");
 if (host == NULL)
     host = "unknown";
 safef(hostLine, sizeof(hostLine), "SERVER_NAME=%s", host);
 putenv(hostLine);
 initCgiInput();
 return gotAny;
 }
 
 boolean cgiSpoof(int *pArgc, char *argv[])
 /* If run from web line set up input
  * variables from web line, otherwise
  * set up from command line. */
 {
 return cgiFromCommandLine(pArgc, argv, TRUE);
 }
 
 boolean cgiFromFile(char *fileName)
 /* Set up a cgi environment using parameters stored in a file.
  * Takes file with arguments in the form:
  *       argument1=someVal
  *       # This is a comment
  *       argument2=someOtherVal
  *       ...
  * and puts them into the cgi environment so that the usual
  * cgiGetVar() commands can be used. Useful when a program
  * has a lot of possible parameters.
  */
 {
 char **argv = NULL;
 int argc = 0;
 int maxArgc = 10;
 int i;
 struct lineFile *lf = lineFileOpen(fileName, TRUE);
 char *line;
 boolean spoof= FALSE;
 AllocArray(argv, maxArgc);
 /* Remember that first arg is program name.
    Put filename there instead. */
 argc = 1;
 argv[0] = cloneString(fileName);
 for(;;)
     {
     /* If we are at the end we're done. */
     if(!lineFileNext(lf, &line, NULL))
 	break;
     /* If it is a comment or blank line skip it. */
     if (line[0] == '#' || sameString(line, ""))
         continue;
     /* If our argv array is full expand it. */
     if((argc+1) >= maxArgc)
 	{
 	ExpandArray(argv, maxArgc, 2*maxArgc);
 	maxArgc *= 2;
 	}
     /* Fill in another argument to our psuedo arguments. */
     argv[argc++] = cloneString(line);
     }
 spoof = cgiSpoof(&argc, argv);
 /* Cleanup. */
 lineFileClose(&lf);
 for(i=0; i<argc; i++)
     freez(&argv[i]);
 freez(&argv);
 return spoof;
 }
 
 void logCgiToStderr()
 /* Log useful CGI info to stderr */
 {
 char *ip = getenv("REMOTE_ADDR");
 char *cgiBinary = getenv("SCRIPT_FILENAME");
 char *requestUri = getenv("REQUEST_URI");
 char *hgsid = cgiOptionalString("hgsid");
 char *cgiFileName = NULL;
 time_t nowTime = time(NULL);
 struct tm *tm;
 tm = localtime(&nowTime);
 char *ascTime = asctime(tm);
 size_t len = strlen(ascTime);
 if (cgiBinary)
     cgiFileName = basename(cgiBinary);
 else
     cgiFileName = "cgi-bin";
 if (len > 3) ascTime[len-2] = '\0';
 if (!ip)
     ip = "unknown";
 if (!hgsid)
     hgsid = "unknown";
 if (!requestUri)
     requestUri = "unknown";
 fprintf(stderr, "[%s] [%s] [client %s] [hgsid=%.24s] [%.1024s] ", ascTime, cgiFileName, ip,
 	hgsid, requestUri);
 }
 
 void cgiResetState()
 /* This is for reloading CGI settings multiple times in the same program
  * execution.  No effect if state has not yet been initialized. */
 {
 freez(&inputString);
 inputString = NULL;
 if (inputHash != NULL)
     hashFree(&inputHash);
 inputHash = NULL;
 inputList = NULL;
 
 haveCookiesHash = FALSE;
 if (cookieHash != NULL)
     hashFree(&cookieHash);
 cookieHash = NULL;
 cookieList = NULL;
 }
 
 void cgiDown(float lines)
 // Drop down a certain number of lines (may be fractional)
 {
 printf("<div style='height:%fem;'></div>\n",lines);
 }
 
 char *commonCssStyles()
 /* Returns a string of common CSS styles */
 {
 // Contents currently is OBSOLETE as these have been moved to HGStyle.css
 // TODO: remove all traces (from web.c, hgTracks, hgTables) as this funtion does nothing.
 return "";
 }
 
 static void turnCgiVarsToVals(struct hashEl *hel)
 /* To save cgiParseVars clients from doing an extra lookup, replace
  * hash filled with cgiVars as values with one filled with cgiVar->val
  * instead.  Since cgiVar->name is same as hashEl->name,  no info is really
  * lost, and it simplifies the code that uses us. */
 {
 struct cgiVar *var = hel->val;
 hel->val = var->val;
 }
 
 struct cgiParsedVars *cgiParsedVarsNew(char *cgiString)
 /* Build structure containing parsed out cgiString */
 {
 struct cgiParsedVars *tags;
 AllocVar(tags);
 tags->stringBuf = cloneString(cgiString);
 cgiParseInputAbort(tags->stringBuf, &tags->hash, &tags->list);
 hashTraverseEls(tags->hash, turnCgiVarsToVals);
 return tags;
 }
 
 void cgiParsedVarsFree(struct cgiParsedVars **pTags)
 /* Free up memory associated with cgiParsedVars */
 {
 struct cgiParsedVars *tags = *pTags;
 if (tags != NULL)
     {
     slFreeList(&tags->list);
     hashFree(&tags->hash);
     freeMem(tags->stringBuf);
     freez(pTags);
     }
 }
 
 void cgiParsedVarsFreeList(struct cgiParsedVars **pList)
 /* Free up list of cgiParsedVars */
 {
 struct cgiParsedVars *el, *next;
 
 for (el = *pList; el != NULL; el = next)
     {
     next = el->next;
     cgiParsedVarsFree(&el);
     }
 *pList = NULL;
 }
 
 void cgiEncodeHash(struct hash *hash, struct dyString *dy)
 /* Put a cgi-encoding of a string valued hash into dy.  Tags are always
  * alphabetical to make it easier to compare if two hashes are same. */
 {
 struct hashEl *el, *elList = hashElListHash(hash);
 slSort(&elList, hashElCmp);
 boolean firstTime = TRUE;
 char *s = NULL;
 for (el = elList; el != NULL; el = el->next)
     {
     if (firstTime)
 	firstTime = FALSE;
     else
 	dyStringAppendC(dy, '&');
     dyStringAppend(dy, el->name);
     dyStringAppendC(dy, '=');
     s = cgiEncode(el->val);
     dyStringAppend(dy, s);
     freez(&s);
     }
 hashElFreeList(&elList);
 }