feb380e4c526092d9793e1d70f8a484cca2ae338 galt Tue Oct 20 01:18:28 2020 -0700 Fix speed of javascript in View, Facets, and Subtracks for hgTrackUi. fixes #26381 diff --git src/lib/cheapcgi.c src/lib/cheapcgi.c index 51e1071..7d71148 100644 --- src/lib/cheapcgi.c +++ src/lib/cheapcgi.c @@ -1,2697 +1,2701 @@ /* 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" #include "htmshell.h" #ifndef GBROWSE #include "mime.h" #endif /* GBROWSE */ #include <signal.h> //============ javascript inline-separation routines =============== // One of the main services that CSP (Content Security Policy) provides // is protecting from reflected and stored XSS attacks by disabling all inline javacript, // both in script tags, and in inline event handlers. The separated javascript // can be either added back to the end of the html page with a nonce or sha hashid, // or it can be saved to a temp file in trash and then included as a non-inline, off-page .js. struct dyString *jsInlineLines = NULL; void jsInlineInit() /* init if needed */ { if (!jsInlineLines) { jsInlineLines = dyStringNew(1024); } } void jsInline(char *javascript) /* Add javascript text to output file or memory structure */ { jsInlineInit(); // init if needed dyStringAppend(jsInlineLines, javascript); } void jsInlineF(char *format, ...) /* Add javascript text to output file or memory structure */ { jsInlineInit(); // init if needed va_list args; va_start(args, format); dyStringVaPrintf(jsInlineLines, format, args); va_end(args); } boolean jsInlineFinishCalled = FALSE; void jsInlineFinish() /* finish outputting accumulated inline javascript */ { if (jsInlineFinishCalled) { // jsInlineFinish can be called multiple times when generating framesets or genomeSpace. warn("jsInlineFinish() called already."); } jsInlineInit(); // init if needed printf("<script type='text/javascript' nonce='%s'>\n%s</script>\n", getNonce(), jsInlineLines->string); dyStringClear(jsInlineLines); jsInlineFinishCalled = TRUE; } void jsInlineReset() /* used by genomeSpace to repeatedly output multiple pages to stdout */ { jsInlineFinishCalled = FALSE; } const char * const jsEvents[] = { "abort", "activate", "afterprint", "afterupdate", "beforeactivate", "beforecopy", "beforecut", "beforedeactivate", "beforeeditfocus", "beforepaste", "beforeprint", "beforeunload", "beforeupdate", "blur", "bounce", "cellchange", "change", "click", "contextmenu", "controlselect", "copy", "cut", "dataavailable", "datasetchanged", "datasetcomplete", "dblclick", "deactivate", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop", "error", "errorupdate", "filterchange", "finish", "focus", "focusin", "focusout", "hashchange", "help", "input", "keydown", "keypress", "keyup", "load", "losecapture", "message", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "mousewheel", "move", "moveend", "movestart", "offline", "line", "online", "paste", "propertychange", "readystatechange", "reset", "resize", "resizeend", "resizestart", "rowenter", "rowexit", "rowsdelete", "rowsinserted", "scroll", "search", "select", "selectionchange", "selectstart", "start", "stop", "submit", "unload", "" }; boolean findJsEvent(char *event) /* see if it is in the list */ { int i = 0; while (TRUE) { const char *w = jsEvents[i]; if (sameString(w, event)) return TRUE; if (sameString(w, "")) return FALSE; ++i; } return FALSE; // should never get here } void checkValidEvent(char *event) /* check if it is lowercase and a known valid event name */ { char *temp = cloneString(event); tolowers(temp); if (!sameString(temp, event)) warn("jsInline: javascript event %s should be given in lower-case", event); event = temp; if (!findJsEvent(event)) warn("jsInline: unknown javascript event %s", event); freeMem (event); } void jsOnEventById(char *eventName, char *idText, char *jsText) /* Add js mapping for inline event */ { checkValidEvent(eventName); jsInlineF("document.getElementById('%s').on%s = function(event) {if (!event) {event=window.event}; %s};\n", idText, eventName, jsText); } void jsOnEventByIdF(char *eventName, char *idText, char *format, ...) /* Add js mapping for inline event with printf formatting */ { checkValidEvent(eventName); jsInlineF("document.getElementById('%s').on%s = function(event) {if (!event) {event=window.event}; ", idText, eventName); va_list args; va_start(args, format); dyStringVaPrintf(jsInlineLines, format, args); va_end(args); jsInlineF("};\n"); } //============ END of javascript inline-separation routines =============== /* 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 to %s: CONTENT_LENGTH=%ld, only %ld supplied", cgiScriptName(), inputSize, i); 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 id[256]; safef(id, sizeof id, "%s_clickBut", form); char javascript[1024]; safef(javascript, sizeof(javascript), "document.%s.%s.value = ''; document.%s.submit();", form, field, form); cgiMakeOnClickButton(id, javascript, " Clear "); } void cgiMakeClearButtonNoSubmit(char *form, char *field) /* Make button to clear a text field, without resubmitting the form. */ { char id[256]; safef(id, sizeof id, "%s_clear", field); char javascript[1024]; safef(javascript, sizeof javascript, "document.%s.%s.value = '';", form, field); cgiMakeOnClickButton(id, javascript, " Clear "); } void cgiMakeSubmitButtonMaybePressed(char *name, char *value, char *msg, char *onClick, boolean pressed) /* Make 'submit' type button, with optional messsage and onclick javascript. Set styling to indicate whether button has been pressed (for buttons that change browser mode). */ { printf("<input type='submit' name='%s' id='%s' value='%s'", name, name, value); if (pressed) printf(" class='pressed'"); if (msg) printf(" title='%s'", msg); printf(">"); if (onClick) jsOnEventById("click", name, onClick); } void cgiMakeButtonWithMsg(char *name, char *value, char *msg) /* Make 'submit' type button. Display msg on mouseover, if present*/ { cgiMakeSubmitButtonMaybePressed(name, value, msg, NULL, FALSE); } void cgiMakeOnClickSubmitButton(char *command, char *name, char *value) /* Make submit button with both variable name and value with client side * onClick (java)script. */ { cgiMakeSubmitButtonMaybePressed(name, value, NULL, command, FALSE); } void cgiMakeButtonWithOnClick(char *name, char *value, char *msg, char *onClick) /* Make 'submit' type button, with onclick javascript */ { cgiMakeSubmitButtonMaybePressed(name, value, msg, onClick, FALSE); } void cgiMakeButton(char *name, char *value) /* Make 'submit' type button */ { cgiMakeButtonWithMsg(name, value, NULL); } void cgiMakeOnClickButton(char *id, char *command, char *value) /* Make button with client side onClick javascript. */ { printf("<INPUT TYPE='button' id='%s' VALUE=\"%s\">", id, value); jsOnEventById("click", id, 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' id='%s' value='%s'", name, name, value); if (checked) printf(" CHECKED"); printf(">"); } void cgiMakeOnEventRadioButtonWithClass(char *name, char *value, boolean checked, char *class, char *event, char *command) /* Make radio type button with an event and an optional class attribute. * A group of radio buttons should have the * same name but different values. The default selection should be * sent with checked on. If class is non-null it is included. */ { char temp[256]; safef(temp, sizeof temp, "%s_%s", name, value); char *valNoSpc = replaceChars(temp, " ", "_"); // replace spaces with underscore char *id = replaceChars(valNoSpc, ".", "_"); // replace dots with underscore, js does not like dots in ids freeMem(valNoSpc); printf("<input type=radio name='%s' id='%s' value='%s'", name, id, value); if (checked) printf(" CHECKED"); printf(" class='%s'", class); printf(">"); jsOnEventById(event, id, command); freeMem(id); } 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 cgiMakeCheckBoxMore(char *name, boolean checked, char *moreHtml) /* Make check box with moreHtml. */ { cgiMakeCheckBox2Bool(name,checked,TRUE,NULL,moreHtml); } void cgiMakeCheckBoxIdAndMore(char *name, boolean checked, char *id, char *moreHtml) /* Make check box with ID and extra (non-javascript) html. */ { cgiMakeCheckBox2Bool(name,checked,TRUE,id,moreHtml); } 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]; +char shadId[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); +if (id) + safef(shadId, sizeof(shadId), "%s%s", cgiBooleanShadowPrefix(), id); +cgiMakeHiddenVarWithIdExtra(shadName, id ? shadId : NULL, ( 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 area has rowCount X * columnCount and with text: intialVal * * We found out the hard way be very careful with linefeeds and their encoding inside the text "initialVal". * All browsers submit the textarea values with CR LF newlines. * If there is a lone CR or LF, the browser will insert the missing partner to form a new CR LF pair. * This can lead to exponential doubling of newline characters or their html entities, * if one of CR or LF is encoded and the other is not. */ { htmlPrintf("<TEXTAREA NAME='%s|attr|' ROWS=%d COLS=%d %s|none|>%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; htmlPrintf("<INPUT TYPE=TEXT NAME='%s|attr|' ID='%s|attr|' SIZE=%d VALUE='%s|attr|'", varName, varName, charSize, initialVal); if (isNotEmpty(script)) jsOnEventById("keypress", varName, 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 cgiMakeTextVarWithJs(char *varName, char *initialVal, int width, char *event, char *javascript) /* Make a text control filled with initial value. */ { if (initialVal == NULL) initialVal = ""; if (width==0) width=strlen(initialVal)*10; if (width==0) width = 100; htmlPrintf("<INPUT TYPE=TEXT class='inputBox' NAME='%s|attr|' id='%s' style='width:%dpx' VALUE='%s|attr|'>\n", varName, varName, width, initialVal); if (event) jsOnEventById(event, varName, javascript); } 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; htmlPrintf("<INPUT TYPE=TEXT NAME='%s|attr|' SIZE=%d VALUE=%d %s|none|>", // TODO XSS risk in extra 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' id='%s' style='width: %dpx' value=%d", varName,varName,width,initialVal); jsOnEventByIdF("change", varName, "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' id='%s' style='width: %dpx' value=%s", varName,varName,width,shorterDouble(initialVal)); jsOnEventByIdF("change", varName, "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),"%s",shorterDouble(min)); minStr = minLimit; } if ((int)max != NO_VALUE) { safef(maxLimit,sizeof(maxLimit),"%s",shorterDouble(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 cgiMakeDropListClassWithIdStyleAndJavascript(char *name, char *id, char *menu[], int menuSize, char *checked, char *class, char *style, struct slPair *events) /* Make a drop-down list with name, id, text class, style and javascript. */ { int i; char *selString; if (checked == NULL) checked = menu[0]; printf("<SELECT"); if (name) printf(" NAME='%s'", name); if (events && !id) // use name as id id = name; if (id) printf(" id='%s'", id); if (class) printf(" class='%s'", class); if (events) { struct slPair *e; for(e = events; e; e = e->next) { jsOnEventById(e->name, id, e->val); } } if (style) printf(" style='%s'", style); 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 cgiMakeDropListClassWithStyleAndJavascript(char *name, char *menu[], int menuSize, char *checked, char *class, char *style, struct slPair *events) /* Make a drop-down list with names, text class, style and javascript. */ { cgiMakeDropListClassWithIdStyleAndJavascript(name,NULL,menu,menuSize,checked,class,style,events); } 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,NULL); } 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 cgiMakeDropListFullExt(char *name, char *menu[], char *values[], int menuSize, char *checked, char *event, char *javascript, char *style, char *class) /* Make a drop-down list with names and values. * Optionally include values for style and class */ { int i; char *selString; if (checked == NULL) checked = menu[0]; printf("<SELECT NAME='%s'", name); if (class) printf(" class='%s'", class); if (javascript) { printf(" id='%s'", name); jsOnEventById(event, name, javascript); } if (style) printf(" style='%s'", style); printf(">\n"); 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 cgiMakeDropListFull(char *name, char *menu[], char *values[], int menuSize, char *checked, char *event, char *javascript) /* Make a table of checkboxes that have the same variable name but different * values (same behavior as a multi-select input). */ { cgiMakeDropListFullExt(name, menu, values, menuSize, checked, event, javascript, NULL, NULL); } char *cgiMakeSelectDropList(boolean multiple, char *name, struct slPair *valsAndLabels, char *selected, char *anyAll,char *extraClasses, char *event, char *javascript, char *style, char *id) // 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 // event: click, etc. // javacript: what to execute when the event happens. // style: inline style // id is optional { 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'"); char *autoId = NULL; if (javascript) { if (!id) { id = name; } } if (id) dyStringPrintf(output, " id='%s'", id); if (javascript) { jsOnEventById(event, id, javascript); freeMem(autoId); } if (style) { dyStringPrintf(output, " style='%s'", style); } 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) +void cgiMakeHiddenVarWithIdExtra(char *varName, char *id, char *string,char *extra) /* Store string in hidden input for next time around. */ { -printf("<INPUT TYPE=HIDDEN NAME='%s' VALUE='%s'", varName, string); +printf("<INPUT TYPE=HIDDEN NAME='%s'", varName); +if (id) + printf(" ID='%s'", id); if (extra) - printf(" %s>\n",extra); -else - puts(">"); + printf(" %s",extra); +printf(" VALUE='%s'>\n", string); } void cgiContinueHiddenVar(char *varName) /* Write CGI var back to hidden input for next time around. */ { if (cgiVarExists(varName)) - cgiMakeHiddenVar(varName, cgiString(varName)); + cgiMakeHiddenVarWithIdExtra(varName, varName, cgiString(varName), NULL); } 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); }