6412b58f4efb9beba7d01f8cf50f6026d0188436 braney Thu Apr 27 13:31:02 2023 -0700 fix a couple of bugs in squishyPack mode diff --git src/hg/lib/cart.c src/hg/lib/cart.c index 0e8fb83..7e664d2 100644 --- src/hg/lib/cart.c +++ src/hg/lib/cart.c @@ -1,3823 +1,3826 @@ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "hCommon.h" #include "obscure.h" #include "linefile.h" #include "errAbort.h" #include "hash.h" #include "cheapcgi.h" #include "cartDb.h" #include "htmshell.h" #include "hgConfig.h" #include "cart.h" #include "verbose.h" #include "net.h" #include "web.h" #include "hdb.h" #include "jksql.h" #include "jsHelper.h" #include "trashDir.h" #ifndef GBROWSE #include "customFactory.h" #include "googleAnalytics.h" #include "wikiLink.h" #endif /* GBROWSE */ #include "hgMaf.h" #include "hui.h" #include "geoMirror.h" #include "hubConnect.h" #include "trackHub.h" #include "cgiApoptosis.h" #include "customComposite.h" #include "regexHelper.h" #include "windowsToAscii.h" #include "jsonWrite.h" static char *sessionVar = "hgsid"; /* Name of cgi variable session is stored in. */ static char *positionCgiName = "position"; DbConnector cartDefaultConnector = hConnectCart; DbDisconnect cartDefaultDisconnector = hDisconnectCart; static boolean cartDidContentType = FALSE; struct slPair *httpHeaders = NULL; // A list of headers to output before the content-type static void hashUpdateDynamicVal(struct hash *hash, char *name, void *val) /* Val is a dynamically allocated (freeMem-able) entity to put * in hash. Override existing hash item with that name if any. * Otherwise make new hash item. */ { struct hashEl *hel = hashLookup(hash, name); if (hel == NULL) hashAdd(hash, name, val); else { freeMem(hel->val); hel->val = val; } } static struct dyString *hubWarnDy; void cartHubWarn(char *format, va_list args) /* save up hub related warnings to put out later */ { char warning[1024]; vsnprintf(warning,sizeof(warning),format, args); if (hubWarnDy == NULL) hubWarnDy = dyStringNew(100); dyStringPrintf(hubWarnDy, "%s\n", warning); } static void sanitizeString(char *str) // Remove % so we can disable format-security { for(; *str; str++) if (*str == '%') *str = ' '; } void cartFlushHubWarnings() /* flush the hub warning (if any) */ { if (hubWarnDy) { sanitizeString(hubWarnDy->string); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-security" warn(hubWarnDy->string); #pragma GCC diagnostic pop } } void cartTrace(struct cart *cart, char *when, struct sqlConnection *conn) /* Write some properties of the cart to stderr for debugging. */ { if (cfgOption("cart.trace") == NULL) return; struct cartDb *u = cart->userInfo, *s = cart->sessionInfo; char *pix = hashFindVal(cart->hash, "pix"); char *textSize = hashFindVal(cart->hash, "textSize"); char *trackControls = hashFindVal(cart->hash, "trackControlsOnMain"); int uLen, sLen; if (conn != NULL) { /* Since the content string is chopped, query for the actual length. */ struct dyString *query = dyStringNew(1024); sqlDyStringPrintf(query, "select length(contents) from %s" " where id = %d", userDbTable(), u->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(query, " and sessionKey='%s'", u->sessionKey); uLen = sqlQuickNum(conn, query->string); dyStringClear(query); sqlDyStringPrintf(query, "select length(contents) from %s" " where id = %d", sessionDbTable(),s->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(query, " and sessionKey='%s'", s->sessionKey); sLen = sqlQuickNum(conn, query->string); dyStringFree(&query); } else { uLen = strlen(u->contents); sLen = strlen(s->contents); } if (pix == NULL) pix = "-"; if (textSize == NULL) textSize = "-"; if (trackControls == NULL) trackControls = "-"; fprintf(stderr, "cartTrace: %22s: " "u.i=%d u.l=%d u.c=%d s.i=%d s.l=%d s.c=%d " "p=%s f=%s t=%s pid=%ld %s\n", when, u->id, uLen, u->useCount, s->id, sLen, s->useCount, pix, textSize, trackControls, (long)getpid(), cgiRemoteAddr()); char userIdKey[256]; cartDbSecureId(userIdKey, sizeof userIdKey, u); if (cart->userId && !sameString(userIdKey, cart->userId)) fprintf(stderr, "cartTrace: bad userId %s --> %d_%s! pid=%ld\n", cart->userId, u->id, u->sessionKey, (long)getpid()); char sessionIdKey[256]; cartDbSecureId(sessionIdKey, sizeof sessionIdKey, s); if (cart->sessionId && !sameString(sessionIdKey, cart->sessionId)) fprintf(stderr, "cartTrace: bad sessionId %s --> %d_%s! pid=%ld\n", cart->sessionId, s->id, s->sessionKey, (long)getpid()); } boolean cartTablesOk(struct sqlConnection *conn) /* Return TRUE if cart tables are accessible (otherwise, the connection * doesn't do us any good). */ { if (!sqlTableExists(conn, userDbTable())) { fprintf(stderr, "cartTablesOk failed on %s.%s pid=%ld\n", sqlGetDatabase(conn), userDbTable(), (long)getpid()); return FALSE; } if (!sqlTableExists(conn, sessionDbTable())) { fprintf(stderr, "cartTablesOk failed on %s.%s pid=%ld\n", sqlGetDatabase(conn), sessionDbTable(), (long)getpid()); return FALSE; } return TRUE; } static void mergeHash(struct hash *origHash, struct hash *overlayHash) /* Merge one hash on top of another. */ { struct hashCookie cookie = hashFirst(overlayHash); struct hashEl *helOverlay; while ((helOverlay = hashNext(&cookie)) != NULL) { char *varName = helOverlay->name; struct hashEl *helOrig = hashLookup(origHash, varName); if (helOrig) { // we don't want to hide a track that's visible in the overlay if (differentString("hide", helOverlay->val)) hashReplace(origHash, helOverlay->name, helOverlay->val); } else hashAdd(origHash, varName, helOverlay->val); } } static void loadHash(struct hash *hash, char *contents) /* Load a hash from a cart-like string. */ { char *namePt, *dataPt, *nextNamePt; namePt = contents; while (namePt != NULL && namePt[0] != 0) { dataPt = strchr(namePt, '='); if (dataPt == NULL) errAbort("Mangled input string %s", namePt); *dataPt++ = 0; nextNamePt = strchr(dataPt, '&'); if (nextNamePt == NULL) nextNamePt = strchr(dataPt, ';'); /* Accomodate DAS. */ if (nextNamePt != NULL) *nextNamePt++ = 0; cgiDecode(dataPt,dataPt,strlen(dataPt)); hashAdd(hash, namePt, cloneString(dataPt)); namePt = nextNamePt; } } void cartParseOverHashExt(struct cart *cart, char *contents, boolean merge) /* Parse cgi-style contents into a hash table. If merge is FALSE, this will *not* * replace existing members of hash that have same name, so we can * support multi-select form inputs (same var name can have multiple * values which will be in separate hashEl's). If merge is TRUE, we * replace existing values with new values */ { if (merge) { struct hash *newHash = newHash(8); loadHash(newHash, contents); mergeHash(newHash, cart->hash); cart->hash = newHash; } else loadHash(cart->hash, contents); } void cartParseOverHash(struct cart *cart, char *contents) /* Parse cgi-style contents into a hash table. This will *not* * replace existing members of hash that have same name, so we can * support multi-select form inputs (same var name can have multiple * values which will be in separate hashEl's). */ { cartParseOverHashExt(cart, contents, FALSE); } static boolean looksCorrupted(struct cartDb *cdb) /* Test for db corruption by checking format of firstUse field. */ { if (cdb == NULL) return FALSE; else { char *words[3]; int wordCount = 0; boolean isCorr = FALSE; char *fu = cloneString(cdb->firstUse); wordCount = chopByChar(fu, '-', words, ArraySize(words)); if (wordCount < 3) isCorr = TRUE; else { time_t theTime = time(NULL); struct tm *tm = localtime(&theTime); int year = atoi(words[0]); int month = atoi(words[1]); if ((year < 2000) || (year > (1900+tm->tm_year)) || (month < 1) || (month > 12)) isCorr = TRUE; } freez(&fu); return isCorr; } } struct cartDb *cartDbLoadFromId(struct sqlConnection *conn, char *table, char *secureId) /* Load up cartDb entry for particular ID. Returns NULL if no such id. */ { if (!secureId) return NULL; else { struct cartDb *cdb = NULL; struct dyString *where = dyStringNew(256); char *sessionKey = NULL; unsigned int id = cartDbParseId(secureId, &sessionKey); sqlDyStringPrintf(where, "id = %u", id); if (cartDbUseSessionKey()) { if (!sessionKey) sessionKey = ""; sqlDyStringPrintf(where, " and sessionKey='%s'", sessionKey); } cdb = cartDbLoadWhere(conn, table, where->string); dyStringFree(&where); if (looksCorrupted(cdb)) { /* Can't use warn here -- it interrupts the HTML header, causing an * err500 (and nothing useful in error_log) instead of a warning. */ fprintf(stderr, "%s id=%u looks corrupted -- starting over with new %s id.\n", table, id, table); cdb = NULL; } return cdb; } } static char *getDefaultCart(struct sqlConnection *conn) /* Get the default cart if any. */ { char *contents = ""; char *table = defaultCartTable(); if (sqlTableExists(conn, table)) { char query[1024]; sqlSafef(query, sizeof query, "select length(contents) from %s", table); int length = sqlQuickNum(conn, query) + 1; contents = needLargeMem(length); sqlSafef(query, sizeof query, "select contents from %s", table); sqlQuickQuery(conn, query, contents, length); } return contents; } struct cartDb *loadDb(struct sqlConnection *conn, char *table, char *secureId, boolean *found) /* Load bits from database and save in hash. */ { struct cartDb *cdb; boolean result = TRUE; cdb = cartDbLoadFromId(conn, table, secureId); if (!cdb) { result = FALSE; struct dyString *query = dyStringNew(256); sqlDyStringPrintf(query, "INSERT %s VALUES(0,'',0,now(),now(),0", table); char *sessionKey = ""; if (cartDbHasSessionKey(conn, table)) { if (cartDbUseSessionKey()) { sessionKey = makeRandomKey(128+33); // at least 128 bits of protection, 33 for the world population size. } sqlDyStringPrintf(query, ",'%s'", sessionKey); } sqlDyStringPrintf(query, ")"); sqlUpdate(conn, query->string); dyStringFree(&query); unsigned int id = sqlLastAutoId(conn); char newSecureId[256]; if (cartDbUseSessionKey() && !sameString(sessionKey,"")) safef(newSecureId, sizeof newSecureId, "%u_%s", id, sessionKey); else safef(newSecureId, sizeof newSecureId, "%u", id); if ((cdb = cartDbLoadFromId(conn,table,newSecureId)) == NULL) errAbort("Couldn't get cartDb for id=%u right after loading. " "MySQL problem??", id); if (!sameString(sessionKey,"")) freeMem(sessionKey); } *found = result; return cdb; } void cartExclude(struct cart *cart, char *var) /* Exclude var from persistent storage. */ { hashAdd(cart->exclude, var, NULL); } static char *_cartNamedSessionDbTable = NULL; char *cartNamedSessionDbTable() /* Get the name of the table that lists named sessions. Don't free the result. */ { if (_cartNamedSessionDbTable == NULL) _cartNamedSessionDbTable = cfgOptionEnvDefault("HGDB_NAMED_SESSION_DB", namedSessionDbTableConfVariable, defaultNamedSessionDb); return _cartNamedSessionDbTable; } void sessionTouchLastUse(struct sqlConnection *conn, char *encUserName, char *encSessionName) /* Increment namedSessionDb.useCount and update lastUse for this session. */ { struct dyString *dy = dyStringNew(1024); int useCount; sqlDyStringPrintf(dy, "SELECT useCount FROM %s " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, encUserName, encSessionName); useCount = sqlQuickNum(conn, dy->string) + 1; dyStringClear(dy); sqlDyStringPrintf(dy, "UPDATE %s SET useCount = %d, lastUse=now() " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, useCount, encUserName, encSessionName); sqlUpdate(conn, dy->string); dyStringFree(&dy); } static void copyCustomComposites(struct cart *cart, struct hashEl *el) /* Copy a set of custom composites to a new hub file. Update the * relevant cart variables. */ { struct tempName hubTn; char *hubFileVar = el->name; char *oldHubFileName = el->val; trashDirDateFile(&hubTn, "hgComposite", "hub", ".txt"); char *newHubFileName = cloneString(hubTn.forCgi); // let's make sure the hub hasn't been cleaned up int fd = open(oldHubFileName, O_RDONLY); if (fd < 0) { cartRemove(cart, hubFileVar); return; } close(fd); copyFile(oldHubFileName, newHubFileName); cartReplaceHubVars(cart, hubFileVar, oldHubFileName, newHubFileName); } void cartReplaceHubVars(struct cart *cart, char *hubFileVar, char *oldHubUrl, char *newHubUrl) /* Replace all cart variables corresponding to oldHubUrl (and/or its hub ID) with * equivalents for newHubUrl. */ { if (! startsWith(customCompositeCartName, hubFileVar)) errAbort("cartReplaceHubVars: expected hubFileVar to begin with '"customCompositeCartName"' " "but got '%s'", hubFileVar); char *errorMessage; unsigned oldHubId = hubFindOrAddUrlInStatusTable(cart, oldHubUrl, &errorMessage); unsigned newHubId = hubFindOrAddUrlInStatusTable(cart, newHubUrl, &errorMessage); // need to change hgHubConnect.hub.#hubNumber# (connected hubs) struct slPair *hv, *hubVarList = cartVarsWithPrefix(cart, hgHubConnectHubVarPrefix); char buffer[4096]; for(hv = hubVarList; hv; hv = hv->next) { unsigned hubId = sqlUnsigned(hv->name + strlen(hgHubConnectHubVarPrefix)); if (hubId == oldHubId) { cartRemove(cart, hv->name); safef(buffer, sizeof buffer, "%s%d", hgHubConnectHubVarPrefix, newHubId); cartSetString(cart, buffer, "1"); } } // need to change hub_#hubNumber#* (track visibilities) safef(buffer, sizeof buffer, "%s%d_", hubTrackPrefix, oldHubId); int oldNameLength = strlen(buffer); hubVarList = cartVarsWithPrefix(cart, buffer); for(hv = hubVarList; hv; hv = hv->next) { char *name = hv->name + oldNameLength; safef(buffer, sizeof buffer, "%s%d_%s", hubTrackPrefix, newHubId, name); cartSetString(cart, buffer, cloneString(hv->val)); cartRemove(cart, hv->name); } // need to change hgtgroup_hub_#hubNumber# (blue bar open ) // need to change expOrder_hub_#hubNumber#, simOrder_hub_#hubNumber# (sorting) -- values too // need to change trackHubs #hubNumber# cartSetString(cart, hgHubConnectRemakeTrackHub, "on"); cartSetString(cart, hubFileVar, newHubUrl); } void cartCopyCustomComposites(struct cart *cart) /* Find any custom composite hubs and copy them so they can be modified. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); for (el = elList; el != NULL; el = el->next) { if (startsWith(customCompositeCartName, el->name)) copyCustomComposites(cart, el); } } static void storeInOldVars(struct cart *cart, struct hash *oldVars, char *var) /* Store all cart hash elements for var into oldVars (if it exists). */ { if (oldVars == NULL) return; struct hashEl *hel = hashLookup(cart->hash, var); // NOTE: New cgi vars not in old cart cannot be distinguished from vars not newly set by cgi. // Solution: Add 'empty' var to old vars for cgi vars not already in cart if (hel == NULL) hashAdd(oldVars, var, cloneString(CART_VAR_EMPTY)); while (hel != NULL) { hashAdd(oldVars, var, cloneString(hel->val)); hel = hashLookupNext(hel); } } static void cartJustify(struct cart *cart, struct hash *oldVars) /* Handles rules for allowing some cart settings to override others. */ { // New priority (position) settings should erase imgOrd settings if (oldVars == NULL) return; #define POSITION_SUFFIX ".priority" #define IMGORD_SUFFIX "_imgOrd" struct hashEl *list = NULL, *el; list = hashElListHash(cart->hash); //warn("cartJustify() begins"); for (el = list; el != NULL; el = el->next) { if (endsWith(el->name,POSITION_SUFFIX)) { if (cartValueHasChanged(cart,oldVars,el->name,TRUE,TRUE)) { int suffixOffset = strlen(el->name) - strlen(POSITION_SUFFIX); if (suffixOffset>0) { char *name = cloneString(el->name); safecpy(name+suffixOffset,strlen(POSITION_SUFFIX),IMGORD_SUFFIX); // We know that POSITION_SUFFIX is longer than IMGORD_SUFFIX cartRemove(cart, name); // Removes if found freeMem(name); } } } } } static void loadCgiOverHash(struct cart *cart, struct hash *oldVars) /* Store CGI variables in cart. */ { struct cgiVar *cv, *cvList = cgiVarList(); char *booShadow = cgiBooleanShadowPrefix(); int booSize = strlen(booShadow); char *multShadow = cgiMultListShadowPrefix(); int multSize = strlen(multShadow); struct hash *booHash = newHash(8); struct hash *cgiHash = hashNew(11); /* First handle boolean variables and store in cgiHash. We store in a * separate hash in order to distinguish between a list-variable's old * values in the cart hash and new values from cgi. */ for (cv = cvList; cv != NULL; cv = cv->next) { if (startsWith(booShadow, cv->name)) { char *booVar = cv->name + booSize; // Support for 2 boolean CBs: checked/unchecked (1/0) and enabled/disabled:(-1/-2) char *val = (cgiVarExists(booVar) ? "1" : cv->val); storeInOldVars(cart, oldVars, booVar); cartRemove(cart, booVar); hashAdd(cgiHash, booVar, val); hashAdd(booHash, booVar, NULL); } else if (startsWith(multShadow, cv->name)) { /* This shadow variable enables us to detect when all inputs in * the multi-select box have been deselected. */ char *multVar = cv->name + multSize; if (! cgiVarExists(multVar)) { storeInOldVars(cart, oldVars, multVar); storeInOldVars(cart, oldVars, cv->name); cartRemove(cart, multVar); } } } /* Handle non-boolean vars. */ for (cv = cgiVarList(); cv != NULL; cv = cv->next) { if (! (startsWith(booShadow, cv->name) || hashLookup(booHash, cv->name))) { storeInOldVars(cart, oldVars, cv->name); cartRemove(cart, cv->name); if (differentString(cv->val, CART_VAR_EMPTY)) // NOTE: CART_VAR_EMPTY logic not implemented for boolShad hashAdd(cgiHash, cv->name, cv->val); } } /* Add new settings to cart (old values of these variables have been * removed above). */ struct hashEl *hel = hashElListHash(cgiHash); while (hel != NULL) { cartAddString(cart, hel->name, hel->val); hel = hel->next; } hashFree(&cgiHash); hashFree(&booHash); } static void hashEmpty(struct hash *hash) /* Remove everything from hash. */ { struct hashEl *hel, *helList = hashElListHash(hash); for (hel = helList; hel != NULL; hel = hel->next) { freez(&(hel->val)); hashRemove(hash, hel->name); } hashElFreeList(&helList); assert(hashNumEntries(hash) == 0); } #ifndef GBROWSE void cartLoadUserSessionExt(struct sqlConnection *conn, char *sessionOwner, char *sessionName, struct cart *cart, struct hash *oldVars, char *actionVar, boolean merge) /* If permitted, load the contents of the given user's session, and then * reload the CGI settings (to support override of session settings). * If non-NULL, oldVars will contain values overloaded when reloading CGI. * If non-NULL, actionVar is a cartRemove wildcard string specifying the * CGI action variable that sent us here. * If merge is TRUE, then don't clear the cart first. */ { struct sqlResult *sr = NULL; char **row = NULL; char *userName = wikiLinkUserName(); char *encSessionName = cgiEncodeFull(sessionName); char *encSessionOwner = cgiEncodeFull(sessionOwner); char query[512]; if (isEmpty(sessionOwner)) errAbort("Please go back and enter a wiki user name for this session."); if (isEmpty(sessionName)) errAbort("Please go back and enter a session name to load."); sqlSafef(query, sizeof(query), "SELECT shared, contents FROM %s " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, encSessionOwner, encSessionName); sr = sqlGetResult(conn, query); if ((row = sqlNextRow(sr)) != NULL) { boolean shared = atoi(row[0]); if (shared || (userName && sameString(sessionOwner, userName))) { char *sessionVar = cartSessionVarName(); char *hgsid = cartSessionId(cart); char *sessionTableString = cartOptionalString(cart, hgSessionTableState); sessionTableString = cloneString(sessionTableString); char *pubSessionsTableString = cartOptionalString(cart, hgPublicSessionsTableState); pubSessionsTableString = cloneString(pubSessionsTableString); struct sqlConnection *conn2 = hConnectCentral(); sessionTouchLastUse(conn2, encSessionOwner, encSessionName); if (!merge) { cartRemoveLike(cart, "*"); cartParseOverHash(cart, row[1]); } else cartParseOverHashExt(cart, row[1], TRUE); cartSetString(cart, sessionVar, hgsid); if (sessionTableString != NULL) cartSetString(cart, hgSessionTableState, sessionTableString); if (pubSessionsTableString != NULL) cartSetString(cart, hgPublicSessionsTableState, pubSessionsTableString); if (oldVars) hashEmpty(oldVars); /* Overload settings explicitly passed in via CGI (except for the * command that sent us here): */ loadCgiOverHash(cart, oldVars); if (isNotEmpty(actionVar)) cartRemove(cart, actionVar); hDisconnectCentral(&conn2); } else errAbort("Sharing has not been enabled for user %s's session %s.", sessionOwner, sessionName); } else errAbort("Could not find session %s for user %s.", sessionName, sessionOwner); sqlFreeResult(&sr); freeMem(encSessionName); } void cartLoadUserSession(struct sqlConnection *conn, char *sessionOwner, char *sessionName, struct cart *cart, struct hash *oldVars, char *actionVar) /* If permitted, load the contents of the given user's session, and then * reload the CGI settings (to support override of session settings). * If non-NULL, oldVars will contain values overloaded when reloading CGI. * If non-NULL, actionVar is a cartRemove wildcard string specifying the * CGI action variable that sent us here. */ { cartLoadUserSessionExt(conn, sessionOwner, sessionName, cart, oldVars, actionVar, FALSE); } #endif /* GBROWSE */ boolean containsNonPrintable(char *string) /* Return TRUE if string contains non-ascii printable character(s). */ { if (isEmpty(string)) return FALSE; boolean hasNonPrintable = FALSE; int i; for (i = 0; string[i] != '\0'; i++) { if ((string[i] < 32 || string[i] > 126) && string[i] != '\t') { hasNonPrintable = TRUE; break; } } return hasNonPrintable; } enum vsErrorType { vsNone=0, vsValid, vsBinary, vsWeird, vsData, vsVarLong, vsValLong }; struct validityStats /* Watch out for incoming garbage data loaded as if it were a saved session file. * This helps decide whether to just bail or simply remove some garbage and alert the user. */ { char *weirdCharsExample; // First "cart variable" with unexpected punct/space/etc char *dataExample; // First "cart variable" that looks like custom track data char *valTooLongExample; // First cart variable whose value is too long to include. uint validCount; // Number of cart variables with no red flags, so *probably* ok. uint binaryCount; // Number of "cart variables" with binary data uint weirdCharsCount; // Number of "cart variables" with unexpected punct/space/etc uint dataCount; // Number of "cart variables" that look like custom track data uint varTooLongCount; // Number of "cart variables" whose length is too long. uint varTooLongLength; // Longest too-long cart var found. uint valTooLongCount; // Number of cart variables whose values are too long. uint valTooLongLength; // Longest too-long cart var value found. enum vsErrorType lastType; // The latest type of error, so we can roll back the previous call. }; static void vsInit(struct validityStats *stats) /* Set all counts to 0 and pointers to NULL. */ { ZeroVar(stats); } static void vsFreeMembers(struct validityStats *stats) /* Free all allocated members of stats (not stats itself, it may be a stack var). */ { freeMem(stats->weirdCharsExample); freeMem(stats->dataExample); freeMem(stats->valTooLongExample); } static void vsGotValid(struct validityStats *stats) /* Update validity stats after finding a cart var with no obvious red flags. (No guarantee * it's actually a real cart variable, but at worst it's just bloating the cart a bit.) */ { if (stats) { stats->validCount++; stats->lastType = vsValid; } } static void vsGotBinary(struct validityStats *stats) /* Update validity stats after finding unprintable characters in "cart var". */ { if (stats) { stats->binaryCount++; stats->lastType = vsBinary; } } static void vsGotWeirdChar(struct validityStats *stats, char *var) /* Update validity stats after finding unexpected but printable characters in "cart var". */ { if (stats) { stats->weirdCharsCount++; if (stats->weirdCharsExample == NULL) stats->weirdCharsExample = cloneString(var); stats->lastType = vsWeird; } } static void vsGotData(struct validityStats *stats, char *var) /* Update validity stats after finding apparent custom track data in "cart var". */ { if (stats) { stats->dataCount++; if (stats->dataExample == NULL) stats->dataExample = cloneString(var); stats->lastType = vsData; } } static void vsGotVarTooLong(struct validityStats *stats, size_t varLen) /* Update validity stats after finding suspiciously lengthy "cart var". */ { if (stats) { stats->varTooLongCount++; stats->varTooLongLength = max(stats->varTooLongLength, varLen); stats->lastType = vsVarLong; } } static void vsGotValTooLong(struct validityStats *stats, char *var, size_t valLen) /* Update validity stats after finding cart var whose value is too long. */ { if (stats) { stats->valTooLongCount++; stats->valTooLongLength = max (stats->valTooLongLength, valLen); if (stats->valTooLongExample == NULL) stats->valTooLongExample = cloneString(var); stats->lastType = vsValLong; } } static void vsUndo(struct validityStats *stats) /* Roll back the latest increment to stats after finding an exceptional case. Only one level of undo is supported. Not supported for vs{Binary,VarLong,ValLong}. */ { if (stats) { switch (stats->lastType) { case vsNone: errAbort("vsUndo: nothing to undo (only one level of undo is possible)"); break; case vsValid: stats->validCount--; stats->lastType = vsNone; break; case vsWeird: stats->weirdCharsCount--; if (stats->weirdCharsCount < 1) freez(&stats->weirdCharsExample); stats->lastType = vsNone; break; case vsData: stats->dataCount--; if (stats->dataCount < 1) freez(&stats->dataExample); stats->lastType = vsNone; break; case vsBinary: case vsVarLong: case vsValLong: errAbort("vsUndo: not supported for lastType vsBinary, vsVarLong or vsValLong (%d)", stats->lastType); break; default: errAbort("vsUndo: invalid lastType %d", stats->lastType); } } } static uint vsErrorCount(struct validityStats *stats) /* Return the sum of all error counts. */ { return (stats->binaryCount + stats->weirdCharsCount + stats->dataCount + stats->varTooLongCount + stats->valTooLongCount); } #define CART_LOAD_TOO_MANY_ERRORS 100 #define CART_LOAD_ENOUGH_VALID 20 #define CART_LOAD_WAY_TOO_MANY_ERRORS 1000 static boolean vsTooManyErrors(struct validityStats *stats) /* Return TRUE if the input seems to be completely invalid. */ { if (stats) { uint errorSum = vsErrorCount(stats); uint total = errorSum + stats->validCount; return ((total > (CART_LOAD_TOO_MANY_ERRORS + CART_LOAD_ENOUGH_VALID) && errorSum > CART_LOAD_TOO_MANY_ERRORS && stats->validCount < CART_LOAD_ENOUGH_VALID) || errorSum > CART_LOAD_WAY_TOO_MANY_ERRORS); } return FALSE; } #define CART_VAR_MAX_LENGTH 1024 #define CART_VAL_MAX_LENGTH (64 * 1024) static void vsReport(struct validityStats *stats, struct dyString *dyMessage) /* Append summary/explanation to dyMessage. */ { if (stats && dyMessage) { boolean quitting = vsTooManyErrors(stats); char *atLeast = (quitting ? "At least " : ""); dyStringPrintf(dyMessage, "<br>%d valid settings found. ", stats->validCount); if (stats->binaryCount || stats->weirdCharsCount || stats->dataCount || stats->varTooLongCount || stats->valTooLongCount) dyStringPrintf(dyMessage, "<b>Note: invalid settings were found and omitted.</b> "); if (stats->binaryCount) dyStringPrintf(dyMessage, "%s%d setting names contained binary data. ", atLeast, stats->binaryCount); if (stats->weirdCharsCount) dyStringPrintf(dyMessage, "%s%d setting names contained unexpected characters, for example '%s'. ", atLeast, stats->weirdCharsCount, htmlEncode(stats->weirdCharsExample)); if (stats->dataCount) dyStringPrintf(dyMessage, "%s%d lines appeared to be custom track data, for example " "a line begins with '%s'. ", atLeast, stats->dataCount, stats->dataExample); if (stats->varTooLongCount) dyStringPrintf(dyMessage, "%s%d setting names were too long (up to %d). ", atLeast, stats->varTooLongCount, stats->varTooLongLength); if (stats->valTooLongCount) dyStringPrintf(dyMessage, "%s%d setting values were too long (up to %d). ", atLeast, stats->valTooLongCount, stats->valTooLongLength); if (quitting) dyStringPrintf(dyMessage, "Encountered too many errors -- quitting. "); } } // Our timestamp vars (_, hgt_) are an exception to the usual cart var naming patterns: #define CART_VAR_TIMESTAMP "^([a-z]+)?_$" // Legitimate cart vars look like this (but so do some not-vars, so we filter further below): #define CART_VAR_VALID_CHARACTERS "^[A-Za-z]([A-Za-z0-9._:-]*[A-Za-z0-9]+)?$" // These are "cart variables" that are actually custom track data: static char *cartVarBlackList[] = { "X", "Y", "MT", "fixedStep", "variableStep", }; // Prefixes of "cart variables" from data files that have caused trouble in the past: static char *cartVarBlackListPrefix[] = { "ENS", // Giant Ensembl gene info dump "RRBS", // Some other big tab-sep dump "VGXS", // Genotypes NULL }; // More complicated patterns of custom track or genotype data: static char *cartVarBlackListRegex[] = { "^chr[0-9XYMTUnLR]+(_[a-zA-Z0-9_]+)?$", "^(chr)?[A-Z]{2}[0-9]{5}[0-9]+", "^rs[0-9]+$", // Genotypes "^i[0-9]{5}[0-9]*$)", // Genotypes NULL }; static boolean isValidCartVar(char *var, struct validityStats *stats) /* Return TRUE if var looks like a plausible cart variable name (as opposed to other stuff * that users try to load in as saved-to-file session data). If var doesn't look right, * return FALSE and if stats is not NULL, record the problem. */ { boolean isValid = TRUE; size_t varLen = strlen(var); if (containsNonPrintable(var)) { vsGotBinary(stats); isValid = FALSE; } else if (varLen > CART_VAR_MAX_LENGTH) { vsGotVarTooLong(stats, varLen); isValid = FALSE; } else if (!regexMatch(var, CART_VAR_TIMESTAMP) && !regexMatch(var, CART_VAR_VALID_CHARACTERS)) { vsGotWeirdChar(stats, var); isValid = FALSE; } else { if (stringArrayIx(var, cartVarBlackList, ArraySize(cartVarBlackList)) >= 0) { vsGotData(stats, var); isValid = FALSE; } else { int i; for (i = 0; cartVarBlackListPrefix[i] != NULL; i++) { if (startsWith(cartVarBlackListPrefix[i], var)) { vsGotData(stats, var); isValid = FALSE; break; } } if (isValid) for (i = 0; cartVarBlackListRegex[i] != NULL; i++) { if (regexMatchNoCase(var, cartVarBlackListRegex[i])) { vsGotData(stats, var); isValid = FALSE; break; } } } } if (isValid) vsGotValid(stats); return isValid; } static char *encodeForHgSession(char *string) /* Allocate and return a new string with \-escaped '\\' and '\n' so that newline characters * don't cause bogus cart variables to appear (while truncating the original value) in files * downloaded from hgSession. Convert "\r\n" and lone '\r' to '\n'. */ { if (string == NULL) return NULL; int inLen = strlen(string); char outBuf[2*inLen + 1]; char *pIn, *pOut; for (pIn = string, pOut = outBuf; *pIn != '\0'; pIn++) { if (*pIn == '\\') { *pOut++ = '\\'; *pOut++ = '\\'; } else if (*pIn == '\r') { if (*(pIn+1) != '\n') { *pOut++ = '\\'; *pOut++ = 'n'; } } else if (*pIn == '\n') { *pOut++ = '\\'; *pOut++ = 'n'; } else *pOut++ = *pIn; } *pOut = '\0'; return cloneString(outBuf); } static void decodeForHgSession(char *string) /* Decode in place \-escaped '\\' and '\n' in string. Note: some older files have only * \n escaped, not backslashes -- so watch out for those. */ { if (string == NULL) return; char *pIn, *pOut; for (pIn = string, pOut = string; *pIn != '\0'; pIn++, pOut++) { if (*pIn == '\\') { char *pNext = pIn + 1; if (*pNext == 'n') { pIn++; *pOut = '\n'; } else if (*pNext == '\\') { pIn++; *pOut = '\\'; } else // '\\' followed by anything other than '\\' or 'n' means we're reading in // an older file in which '\\' was not escaped; ignore. *pOut = *pIn; } else *pOut = *pIn; } *pOut = '\0'; } // DEVELOPER NOTE: If you add anything to this list, verify that the specific multiline // input variable occurs in the middle of an alphabetically ordered cluster of variables // with the same CGI prefix. If so, hasMultilineCgiPrefix needs to include the prefix. // If not then we need a new approach to detecting unencoded newlines. static char *multilineVars[] = { "hgS_newSessionDescription", "hgta_enteredUserRegionFile", "hgta_enteredUserRegions", "hgta_pastedIdentifiers", "hgva_hgvs", "hgva_variantIds", "phyloGif_tree", "phyloPng_tree", "suggestDetails" }; static boolean isMultilineVar(char *var) /* Return TRUE if var may contain newlines that we forgot to encode for many years. */ { return (var && stringArrayIx(var, multilineVars, ArraySize(multilineVars)) >= 0); } static boolean hasMultilineCgiPrefix(char *var) /* Return TRUE if var seems to come from the same CGI as a multiline var. */ { boolean matches = FALSE; if (isNotEmpty(var)) { if (startsWith("hgS_", var) || startsWith("hgta_", var) || startsWith("hgva_", var) || startsWith("phyloGif_", var) || startsWith("phyloPng_", var) || startsWith("suggest", var)) matches = TRUE; } return matches; } static void extendPrevCartVar(struct cart *cart, char *prevVar, char *var, char *val) /* Concatenate newline and var/val onto previous variable's value. */ { char *prevVal = cartString(cart, prevVar); struct dyString *dy = dyStringCreate("%s\n%s", prevVal, var); if (isNotEmpty(val)) dyStringPrintf(dy, " %s", val); cartSetString(cart, prevVar, dy->string); dyStringFree(&dy); } static void updatePrevVar(char **pPrevVar, char *var) /* If pPrevVar is not NULL, free the old value of *pPrevVar and set it to a clone of var. */ { if (pPrevVar) { freeMem(*pPrevVar); *pPrevVar = cloneString(var); } } static boolean cartAddSettingIfValid(struct cart *cart, char *var, char *val, struct validityStats *stats, char **pPrevVar, boolean decodeVal) /* If var and val raise no red flags then add the setting to cart and return TRUE. * Use *pPrevVar to detect and fix unencoded newlines. Update *pPrevVar if setting is valid. * If decodeVal, then call decodeForHgSession on val (from hgSession saved settings file). */ { char *prevVar = pPrevVar ? *pPrevVar : NULL; boolean addToCart = TRUE; if (! isValidCartVar(var, stats)) { // This might be a sign of garbage being uploaded as a session -- or it might // be a case of unencoded newlines causing the appearance of bogus cart variables. addToCart = FALSE; if (isMultilineVar(prevVar) && (stats->lastType == vsWeird || stats->lastType == vsData)) { // It's our fault with the unencoded newlines, don't count it as invalid cart var. vsUndo(stats); extendPrevCartVar(cart, prevVar, var, val); } } else if (isMultilineVar(prevVar)) { // We need to watch out for "vars" that make it past the validity patterns // but are part of unencoded multiline input. // A bit dicey, but all of the multi-line variables that I know of occur // in the middle of an alphabetized cluster of vars with the same prefix. // DEVELOPER NOTE: check that assumption when changing multilineVars/hasMultilineCgiPrefix. if (! hasMultilineCgiPrefix(var)) { extendPrevCartVar(cart, prevVar, var, val); addToCart = FALSE; } else { // Check cart var length post-extension. char *extendedVal = cartOptionalString(cart, prevVar); size_t extendedValLen = strlen(extendedVal); if (extendedValLen > CART_VAL_MAX_LENGTH) { vsGotValTooLong(stats, prevVar, extendedValLen); cartRemove(cart, prevVar); } } } if (addToCart) { if (val != NULL) { size_t valLen = strlen(val); if (valLen > CART_VAL_MAX_LENGTH) { addToCart = FALSE; vsGotValTooLong(stats, var, valLen); } else { if (decodeVal) decodeForHgSession(val); cartAddString(cart, var, val); updatePrevVar(pPrevVar, var); } } else if (var != NULL) { cartSetString(cart, var, ""); updatePrevVar(pPrevVar, var); } } return addToCart; } boolean cartLoadSettingsFromUserInput(struct lineFile *lf, struct cart *cart, struct hash *oldVars, char *actionVar, struct dyString *dyMessage) /* Verify that the user data in lf looks like valid settings (hgSession saved file; * like cartDump output, but values may or may not be htmlEncoded). * Older session files may have unencoded newlines, causing bogus variables; * watch out for those after pasted input variables like hgta_pastedIdentifiers. * Users have uploaded custom tracks, DTC genotypes, hgTracks HTML, even * binary data files. Look for problematic patterns observed in the past. * Load settings into current session, and then reload the CGI settings * (to support override of session settings). * If non-NULL, oldVars will contain values overloaded when reloading CGI. * If non-NULL, actionVar is a cartRemove wildcard string specifying the * CGI action variable that sent us here. * If input contains suspect data, then add diagnostics to dyMessage. If input * contains so much garbage that we shouldn't even try to load what passes the filters, * return FALSE. */ { boolean isValidEnough = TRUE; char *line = NULL; int size = 0; char *sessionVar = cartSessionVarName(); char *hgsid = cartSessionId(cart); char *sessionTableString = cartOptionalString(cart, hgSessionTableState); sessionTableString = cloneString(sessionTableString); char *pubSessionsTableString = cartOptionalString(cart, hgPublicSessionsTableState); pubSessionsTableString = cloneString(pubSessionsTableString); cartRemoveLike(cart, "*"); cartSetString(cart, sessionVar, hgsid); if (sessionTableString != NULL) cartSetString(cart, hgSessionTableState, sessionTableString); if (pubSessionsTableString != NULL) cartSetString(cart, hgPublicSessionsTableState, pubSessionsTableString); char *prevVar = NULL; struct validityStats stats; vsInit(&stats); while (lineFileNext(lf, &line, &size)) { char *var = line; // We actually want to keep leading spaces intact... they're a sign of var not being a real var. char *p = skipLeadingSpaces(var); if (!p) p = var; char *val = skipToSpaces(p); if (val) *val++ = '\0'; if (isEmpty(var) || var[0] == '#') // Ignore blank line / comment continue; else if (sameString(var, sessionVar)) // Ignore old sessionVar (already set above) continue; else if (! cartAddSettingIfValid(cart, var, val, &stats, &prevVar, TRUE)) { if (vsTooManyErrors(&stats)) { isValidEnough = FALSE; break; } } } freeMem(prevVar); if (stats.validCount == 0 && vsErrorCount(&stats) > 0) isValidEnough = FALSE; if (isValidEnough) { if (oldVars) hashEmpty(oldVars); /* Overload settings explicitly passed in via CGI (except for the * command that sent us here): */ loadCgiOverHash(cart, oldVars); } if (isNotEmpty(actionVar)) cartRemove(cart, actionVar); vsReport(&stats, dyMessage); vsFreeMembers(&stats); return isValidEnough; } static char *now() /* Return a mysql-formatted time like "2008-05-19 15:33:34". */ { char nowBuf[256]; time_t seconds = clock1(); struct tm *theTime = localtime(&seconds); strftime(nowBuf, sizeof nowBuf, "%Y-%m-%d %H:%M:%S", theTime); return cloneString(nowBuf); } static struct cartDb *emptyCartDb() /* Create a new empty placeholder cartDb. */ { struct cartDb *cdb; AllocVar(cdb); cdb->contents = cloneString(""); cdb->firstUse = now(); cdb->lastUse = now(); cdb->useCount = 1; // TODO does anything need to go here for sessionKey? maybe not since id is not set here. return cdb; } struct cart *cartFromHash(struct hash *hash) /* Create a cart from hash */ { struct cart *cart; AllocVar(cart); cart->hash = hash; cart->exclude = newHash(7); cart->userInfo = emptyCartDb(); cart->sessionInfo = emptyCartDb(); return cart; } struct cart *cartOfNothing() /* Create a new, empty, cart with no real connection to the database. */ { return cartFromHash(newHash(0)); } struct cart *cartFromCgiOnly(char *userId, char *sessionId, char **exclude, struct hash *oldVars) /* Create a new cart that contains only CGI variables, nothing from the * database. */ { struct cart *cart = cartOfNothing(); cart->userId = userId; cart->sessionId = sessionId; loadCgiOverHash(cart, oldVars); if (exclude != NULL) { char *ex; while ((ex = *exclude++)) cartExclude(cart, ex); } return cart; } static void doDisconnectHub(struct cart *cart) { char *id = cartOptionalString(cart, "hubId"); if (id != NULL) { char buffer[1024]; safef(buffer, sizeof buffer, "hgHubConnect.hub.%s", id); cartRemove(cart, buffer); // now we need to remove any custom tracks that are on this hub safef(buffer, sizeof buffer, "ctfile_hub_%s", id); cartRemovePrefix(cart, buffer); } cartRemove(cart, "hubId"); cartRemove(cart, hgHubDoDisconnect); } static void hideIfNotInCart(struct cart *cart, char *track) /* If this track is not mentioned in the cart, set it to hide */ { if (cartOptionalString(cart, track) == NULL) cartSetString(cart, track, "hide"); } void cartHideDefaultTracks(struct cart *cart) /* Hide all the tracks who have default visibilities in trackDb * that are something other than hide. Do this only if the * variable CART_HAS_DEFAULT_VISIBILITY is set in the cart. */ { char *defaultString = cartOptionalString(cart, CART_HAS_DEFAULT_VISIBILITY); boolean cartHasDefaults = (defaultString != NULL) && sameString(defaultString, "on"); if (!cartHasDefaults) return; char *db = cartString(cart, "db"); struct trackDb *tdb = hTrackDb(db); for(; tdb; tdb = tdb->next) { struct trackDb *parent = tdb->parent; if (parent && parent->isShow) hideIfNotInCart(cart, parent->track); if (tdb->visibility != tvHide) hideIfNotInCart(cart, tdb->track); } // Don't do this again until someone sets this variable, // presumably on session load. cartRemove(cart, CART_HAS_DEFAULT_VISIBILITY); } struct cart *cartNew(char *userId, char *sessionId, char **exclude, struct hash *oldVars) /* Load up cart from user & session id's. Exclude is a null-terminated list of * strings to not include */ { cgiApoptosisSetup(); if (cfgOptionBooleanDefault("showEarlyErrors", FALSE)) errAbortSetDoContentType(TRUE); if (cfgOptionBooleanDefault("suppressVeryEarlyErrors", FALSE)) htmlSuppressErrors(); setUdcCacheDir(); struct cart *cart; struct sqlConnection *conn = cartDefaultConnector(); char *ex; boolean userIdFound = FALSE, sessionIdFound = FALSE; AllocVar(cart); cart->hash = newHash(12); cart->exclude = newHash(7); cart->userId = userId; cart->sessionId = sessionId; cart->userInfo = loadDb(conn, userDbTable(), userId, &userIdFound); cart->sessionInfo = loadDb(conn, sessionDbTable(), sessionId, &sessionIdFound); if (sessionIdFound) cartParseOverHash(cart, cart->sessionInfo->contents); else if (userIdFound) cartParseOverHash(cart, cart->userInfo->contents); else { char *defaultCartContents = getDefaultCart(conn); cartParseOverHash(cart, defaultCartContents); } char when[1024]; safef(when, sizeof(when), "open %s %s", userId, sessionId); cartTrace(cart, when, conn); loadCgiOverHash(cart, oldVars); // I think this is the place to justify old and new values cartJustify(cart, oldVars); #ifndef GBROWSE /* If some CGI other than hgSession been passed hgSession loading instructions, * apply those to cart before we do anything else. (If this is hgSession, * let it handle the settings so it can display feedback to the user.) */ boolean didSessionLoad = FALSE; if (! (cgiScriptName() && endsWith(cgiScriptName(), "hgSession"))) { if (cartVarExists(cart, hgsDoOtherUser)) { char *otherUser = cartString(cart, hgsOtherUserName); char *sessionName = cartString(cart, hgsOtherUserSessionName); boolean mergeCart = cartUsualBoolean(cart, hgsMergeCart, FALSE); struct sqlConnection *conn2 = hConnectCentral(); cartLoadUserSessionExt(conn2, otherUser, sessionName, cart, oldVars, hgsDoOtherUser, mergeCart); hDisconnectCentral(&conn2); cartTrace(cart, "after cartLUS", conn); didSessionLoad = TRUE; } else if (cartVarExists(cart, hgsDoLoadUrl)) { char *url = cartString(cart, hgsLoadUrlName); struct lineFile *lf = netLineFileOpen(url); struct dyString *dyMessage = dyStringNew(0); boolean ok = cartLoadSettingsFromUserInput(lf, cart, oldVars, hgsDoLoadUrl, dyMessage); lineFileClose(&lf); cartTrace(cart, "after cartLS", conn); if (! ok) { warn("Unable to load session file: %s", dyMessage->string); } didSessionLoad = ok; dyStringFree(&dyMessage); } } #endif /* GBROWSE */ /* wire up the assembly hubs so we can operate without sql */ setUdcOptions(cart); if (cartVarExists(cart, hgHubDoDisconnect)) doDisconnectHub(cart); if (didSessionLoad) cartCopyCustomComposites(cart); char *newDatabase = hubConnectLoadHubs(cart); if (newDatabase != NULL) { char *cartDb = cartOptionalString(cart, "db"); if ((cartDb == NULL) || differentString(cartDb, newDatabase)) { // this is some magic to use the defaultPosition and reset cart variables if (oldVars) { struct hashEl *hel; if ((hel = hashLookup(oldVars,"db")) != NULL) hel->val = "none"; else hashAdd(oldVars, "db", "none"); } cartSetString(cart,"db", newDatabase); } } if (exclude != NULL) { while ((ex = *exclude++)) cartExclude(cart, ex); } cartDefaultDisconnector(&conn); if (didSessionLoad) cartHideDefaultTracks(cart); return cart; } static void updateOne(struct sqlConnection *conn, char *table, struct cartDb *cdb, char *contents, int contentSize) /* Update cdb in database. */ { struct dyString *dy = dyStringNew(4096); sqlDyStringPrintf(dy, "UPDATE %s SET contents='", table); sqlDyAppendEscaped(dy, contents); sqlDyStringPrintf(dy, "',lastUse=now(),useCount=%d ", cdb->useCount+1); sqlDyStringPrintf(dy, " where id=%u", cdb->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(dy, " and sessionKey='%s'", cdb->sessionKey); sqlUpdate(conn, dy->string); dyStringFree(&dy); } void cartEncodeState(struct cart *cart, struct dyString *dy) /* Add a CGI-encoded var=val&... string of all cart variables to dy. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); boolean firstTime = TRUE; char *s = NULL; for (el = elList; el != NULL; el = el->next) { if (!hashLookup(cart->exclude, el->name)) { if (firstTime) firstTime = FALSE; else dyStringAppendC(dy, '&'); dyStringAppend(dy, el->name); dyStringAppendC(dy, '='); s = cgiEncode(el->val); dyStringAppend(dy, s); freez(&s); } } hashElFreeList(&elList); } static void saveState(struct cart *cart) /* Save out state to permanent storage in both user and session db. */ { struct sqlConnection *conn = cartDefaultConnector(); struct dyString *encoded = dyStringNew(4096); /* Make up encoded string holding all variables. */ cartEncodeState(cart, encoded); /* update sessionDb and userDb tables (removed check for cart stuffing bots) */ updateOne(conn, userDbTable(), cart->userInfo, encoded->string, encoded->stringSize); updateOne(conn, sessionDbTable(), cart->sessionInfo, encoded->string, encoded->stringSize); /* Cleanup */ cartDefaultDisconnector(&conn); dyStringFree(&encoded); } void cartCheckout(struct cart **pCart) /* Free up cart and save it to database. */ { struct cart *cart = *pCart; if (cart != NULL) { saveState(cart); struct sqlConnection *conn = cartDefaultConnector(); cartTrace(cart, "checkout", conn); cartDefaultDisconnector(&conn); cartDbFree(&cart->userInfo); cartDbFree(&cart->sessionInfo); freeHash(&cart->hash); freeHash(&cart->exclude); freez(pCart); } } void cartSaveState(struct cart *cart) /* Free up cart and save it to database. * Intended for updating cart before background CGI runs. * Use cartCheckout() instead. */ { if (cart != NULL) { saveState(cart); } } char *cartSessionVarName() /* Return name of CGI session ID variable. */ { return sessionVar; } char *cartSessionId(struct cart *cart) /* Return session id. */ { static char buf[256]; cartDbSecureId(buf, sizeof buf, cart->sessionInfo); return buf; } unsigned cartSessionRawId(struct cart *cart) /* Return raw session id without security key. */ { return cart->sessionInfo->id; } char *cartSidUrlString(struct cart *cart) /* Return session id string as in hgsid=N . */ { static char buf[64]; safef(buf, sizeof(buf), "%s=%s", cartSessionVarName(), cartSessionId(cart)); return buf; } char *cartUserId(struct cart *cart) /* Return session id. */ { static char buf[256]; cartDbSecureId(buf, sizeof buf, cart->userInfo); return buf; } unsigned cartUserRawId(struct cart *cart) /* Return raw user id without security key. */ { return cart->userInfo->id; } static char *cartMultShadowVar(struct cart *cart, char *var) /* Return a pointer to the list variable shadow variable name for var. * Don't modify or free result. */ { static char multShadowVar[PATH_LEN]; safef(multShadowVar, sizeof(multShadowVar), "%s%s", cgiMultListShadowPrefix(), var); return multShadowVar; } static int cartRemoveAndCountNoShadow(struct cart *cart, char *var) /* Remove variable from cart, returning count of removed vars. */ { int removed = 0; struct hashEl *hel = hashLookup(cart->hash, var); while (hel != NULL) { struct hashEl *nextHel = hashLookupNext(hel); freez(&hel->val); hashRemove(cart->hash, var); removed++; hel = nextHel; } return removed; } static int cartRemoveAndCount(struct cart *cart, char *var) /* Remove variable from cart, returning count of removed vars. */ { int removed = cartRemoveAndCountNoShadow(cart, var); (void)cartRemoveAndCountNoShadow(cart, cartMultShadowVar(cart, var)); return removed; } void cartRemove(struct cart *cart, char *var) /* Remove variable from cart. */ { (void)cartRemoveAndCount(cart, var); } void cartRemoveExcept(struct cart *cart, char **except) /* Remove variables except those in null terminated except array * from cart. Except array may be NULL in which case all * are removed. */ { struct hash *exceptHash = newHash(10); struct hashEl *list = NULL, *el; char *s; /* Build up hash of things to exclude. */ if (except != NULL) { while ((s = *except++) != NULL) hashAdd(exceptHash, s, NULL); } /* Get all cart variables and remove most of them. */ list = hashElListHash(cart->hash); for (el = list; el != NULL; el = el->next) { if (!hashLookup(exceptHash, el->name)) cartRemove(cart, el->name); } /* Clean up. */ hashFree(&exceptHash); hashElFreeList(&list); } struct slPair *cartVarsLike(struct cart *cart, char *wildCard) /* Return a slPair list of cart vars that match the wildcard */ { struct slPair *cartVars = NULL; struct hashEl *el, *elList = hashElListHash(cart->hash); slSort(&elList, hashElCmp); for (el = elList; el != NULL; el = el->next) { if (wildMatch(wildCard, el->name)) slAddHead(&cartVars,slPairNew(el->name,el->val)); } hashElFreeList(&elList); return cartVars; } struct slPair *cartVarsWithPrefix(struct cart *cart, char *prefix) /* Return a slPair list of cart vars that begin with prefix */ { struct slPair *cartVars = NULL; struct hashEl *el, *elList = hashElListHash(cart->hash); slSort(&elList, hashElCmp); for (el = elList; el != NULL; el = el->next) { if (startsWith(prefix, el->name)) slAddHead(&cartVars,slPairNew(el->name,el->val)); } hashElFreeList(&elList); return cartVars; } void cartCloneVarsWithPrefix(struct cart *cart, char *prefix, char *newPrefix) /* Add a copy of all vars that start with prefix to cart. The new vars will * start with newPrefix instead of prefix */ { int prefixSize = strlen(prefix); struct dyString *buf = dyStringNew(0); struct slPair *pair, *pairList = cartVarsWithPrefix(cart, prefix); for (pair = pairList; pair != NULL; pair = pair->next) { dyStringClear(buf); dyStringAppend(buf, newPrefix); dyStringAppend(buf, pair->name + prefixSize); cartAddString(cart, buf->string, pair->val); } slFreeList(&pairList); dyStringFree(&buf); } struct slPair *cartVarsWithPrefixLm(struct cart *cart, char *prefix, struct lm *lm) /* Return list of cart vars that begin with prefix allocated in local memory. * Quite a lot faster than cartVarsWithPrefix. */ { struct slPair *cartVars = NULL; struct hash *hash = cart->hash; int hashSize = hash->size; struct hashEl *hel; int i; for (i=0; i<hashSize; ++i) { for (hel = hash->table[i]; hel != NULL; hel = hel->next) { if (startsWith(prefix, hel->name)) { struct slPair *pair; lmAllocVar(lm, pair); pair->name = lmCloneString(lm, hel->name); pair->val = lmCloneString(lm, hel->val); slAddHead(&cartVars, pair); } } } return cartVars; } void cartRemoveLike(struct cart *cart, char *wildCard) /* Remove all variable from cart that match wildCard. */ { struct slPair *cartVars = cartVarsLike(cart,wildCard); while (cartVars != NULL) { struct slPair *cartVar = slPopHead(&cartVars); cartRemove(cart, cartVar->name); freeMem(cartVar); } } void cartRemovePrefix(struct cart *cart, char *prefix) /* Remove variables with given prefix from cart. */ { struct slPair *cartVars = cartVarsWithPrefix(cart,prefix); while (cartVars != NULL) { struct slPair *cartVar = slPopHead(&cartVars); cartRemove(cart, cartVar->name); freeMem(cartVar); } } boolean cartVarExists(struct cart *cart, char *var) /* Return TRUE if variable is in cart. */ { return hashFindVal(cart->hash, var) != NULL; } boolean cartListVarExists(struct cart *cart, char *var) /* Return TRUE if a list variable is in cart (list may still be empty). */ { return cartVarExists(cart, cartMultShadowVar(cart, var)); } boolean cartListVarExistsAnyLevel(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Return TRUE if a list variable for tdb->track (or tdb->parent->track, * or tdb->parent->parent->track, etc.) is in cart (list itself may be NULL). */ { if (parentLevel) tdb = tdb->parent; for ( ; tdb != NULL; tdb = tdb->parent) { char var[PATH_LEN]; if (suffix[0] == '.' || suffix[0] == '_') safef(var, sizeof var, "%s%s%s", cgiMultListShadowPrefix(), tdb->track, suffix); else safef(var, sizeof var, "%s%s.%s", cgiMultListShadowPrefix(), tdb->track, suffix); char *cartSetting = hashFindVal(cart->hash, var); if (cartSetting != NULL) return TRUE; } return FALSE; } char *cartString(struct cart *cart, char *var) /* Return string valued cart variable. */ { return hashMustFindVal(cart->hash, var); } char *cartOptionalString(struct cart *cart, char *var) /* Return string valued cart variable or NULL if it doesn't exist. */ { return hashFindVal(cart->hash, var); } char *cartNonemptyString(struct cart *cart, char *name) /* Return string value associated with name. Return NULL * if value doesn't exist or if it is pure white space. */ { char *val = trimSpaces(cartOptionalString(cart, name)); if (val != NULL && val[0] == 0) val = NULL; return val; } char *cartUsualString(struct cart *cart, char *var, char *usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return s; } char *cartCgiUsualString(struct cart *cart, char *var, char *usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return(val); if (cart != NULL) return cartUsualString(cart, var, usual); return(usual); } struct slName *cartOptionalSlNameList(struct cart *cart, char *var) /* Return slName list (possibly with multiple values for the same var) or * NULL if not found. */ { struct slName *slnList = NULL; struct hashEl *hel = hashLookup(cart->hash, var); while (hel != NULL) { if (hel->val != NULL) { struct slName *sln = slNameNew(hel->val); slAddHead(&slnList, sln); } hel = hashLookupNext(hel); } return slnList; } struct hash *cartHashList(struct cart *cart, char *var) /* Return hash with multiple values for the same var or NULL if not found. */ { struct hashEl *hel = hashLookup(cart->hash, var); struct hash *valHash = hashNew(0); while (hel != NULL) { if (hel->val != NULL) { hashAdd(valHash, hel->val, NULL); } hel = hashLookupNext(hel); } return valHash; } void cartAddString(struct cart *cart, char *var, char *val) /* Add string valued cart variable (if called multiple times on same var, * will create a list -- retrieve with cartOptionalSlNameList. */ { hashAdd(cart->hash, var, cloneString(val)); } void cartSetString(struct cart *cart, char *var, char *val) /* Set string valued cart variable. */ { hashUpdateDynamicVal(cart->hash, var, cloneString(val)); } int cartInt(struct cart *cart, char *var) /* Return int valued variable. */ { char *s = cartString(cart, var); return atoi(s); } int cartIntExp(struct cart *cart, char *var) /* Return integer valued expression in variable. */ { return intExp(cartString(cart, var)); } int cartUsualInt(struct cart *cart, char *var, int usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return atoi(s); } int cartUsualIntClipped(struct cart *cart, char *var, int usual, int minVal, int maxVal) /* Return integer variable clipped to lie between minVal/maxVal */ { int val = cartUsualInt(cart, var, usual); if (val < minVal) val = minVal; if (val > maxVal) val = maxVal; return val; } int cartCgiUsualInt(struct cart *cart, char *var, int usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return atoi(val); if (cart != NULL) return cartUsualInt(cart, var, usual); return(usual); } void cartSetInt(struct cart *cart, char *var, int val) /* Set integer value. */ { char buf[32]; safef(buf, sizeof(buf), "%d", val); cartSetString(cart, var, buf); } double cartDouble(struct cart *cart, char *var) /* Return double valued variable. */ { char *s = cartString(cart, var); return atof(s); } double cartUsualDouble(struct cart *cart, char *var, double usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return atof(s); } double cartCgiUsualDouble(struct cart *cart, char *var, double usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return atof(val); if (cart != NULL) return cartUsualDouble(cart, var, usual); return(usual); } void cartSetDouble(struct cart *cart, char *var, double val) /* Set double value. */ { char buf[32]; safef(buf, sizeof(buf), "%f", val); cartSetString(cart, var, buf); } boolean cartBoolean(struct cart *cart, char *var) /* Retrieve cart boolean. */ { char *s = cartString(cart, var); if (sameWord(s, "on") || atoi(s) > 0) return TRUE; else return FALSE; } boolean cartUsualBoolean(struct cart *cart, char *var, boolean usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return (sameWord(s, "on") || atoi(s) > 0); } boolean cartCgiUsualBoolean(struct cart *cart, char *var, boolean usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { if (cgiBooleanDefined(var)) return cgiBoolean(var); if (cart != NULL) return cgiBoolean(var) || cartUsualBoolean(cart, var, usual); return(usual); } void cartSetBoolean(struct cart *cart, char *var, boolean val) /* Set boolean value. */ { // Be explicit because some cartBools overloaded with negative "disabled" values cartSetInt(cart,var,(val?1:0)); } void cartMakeTextVar(struct cart *cart, char *var, char *defaultVal, int charSize) /* Make a text control filled with value from cart if it exists or * default value otherwise. If charSize is zero it's calculated to fit * current value. Default value may be NULL. */ { cgiMakeTextVar(var, cartUsualString(cart, var, defaultVal), charSize); } void cartMakeIntVar(struct cart *cart, char *var, int defaultVal, int maxDigits) /* Make a text control filled with integer value - from cart if available * otherwise default. */ { cgiMakeIntVar(var, cartUsualInt(cart, var, defaultVal), maxDigits); } void cartMakeDoubleVar(struct cart *cart, char *var, double defaultVal, int maxDigits) /* Make a text control filled with integer value - from cart if available * otherwise default. */ { cgiMakeDoubleVar(var, cartUsualDouble(cart, var, defaultVal), maxDigits); } void cartMakeCheckBox(struct cart *cart, char *var, boolean defaultVal) /* Make a check box filled with value from cart if it exists or * default value otherwise. */ { cgiMakeCheckBox(var, cartUsualBoolean(cart, var, defaultVal)); } void cartMakeRadioButton(struct cart *cart, char *var, char *val, char *defaultVal) /* Make a radio button that is selected if cart variable exists and matches * value (or value matches default val if cart var doesn't exist). */ { boolean matches = sameString(val, cartUsualString(cart, var, defaultVal)); cgiMakeRadioButton(var, val, matches); } void cartSaveSession(struct cart *cart) /* Save session in a hidden variable. This needs to be called * somewhere inside of form or bad things will happen when user * has multiple windows open. */ { cgiMakeHiddenVar(sessionVar, cartSessionId(cart)); } static void cartDumpItem(struct hashEl *hel, boolean asTable, boolean encodeAsHtml) /* Dump one item in cart hash. If encodeAsHtml, call htmlEncode on variable name and value; * otherwise, call encodeForHgSession on value. */ { char *var = hel->name, *val = NULL; if (encodeAsHtml) { var = htmlEncode(hel->name); val = htmlEncode((char *)(hel->val)); } else { val = encodeForHgSession((char *)(hel->val)); } if (asTable) { printf("<TR><TD>%s</TD><TD>", var); int width=(strlen(val)+1)*8; if (width<100) width = 100; cgiMakeTextVarWithJs(hel->name, val, width, "change", "setCartVar(this.name,this.value);"); printf("</TD></TR>\n"); } else printf("%s %s\n", var, val); freeMem(val); if (encodeAsHtml) freeMem(var); } static void cartDumpList(struct hashEl *elList, boolean asTable, boolean encodeAsHtml) /* Dump list of cart variables optionally as a table with ajax update support. * If encodeAsHtml, call htmlEncode on variable name and value; otherwise, call * encodeForHgSession on value to prevent newlines from corrupting settings saved * to a file. */ { struct hashEl *el; if (elList == NULL) return; slSort(&elList, hashElCmp); if (asTable) printf("<table>\n"); for (el = elList; el != NULL; el = el->next) cartDumpItem(el, asTable, encodeAsHtml); if (asTable) { printf("<tr><td colspan=2> <em>count: %d</em></td></tr>\n",slCount(elList)); printf("</table>\n"); } hashElFreeList(&elList); } void cartDumpHgSession(struct cart *cart) /* Dump contents of cart with escaped newlines for hgSession output files. * Cart variable "cartDumpAsTable" is ignored. */ { struct hashEl *elList = hashElListHash(cart->hash); cartDumpList(elList, FALSE, FALSE); } void cartDump(struct cart *cart) /* Dump contents of cart. */ { struct hashEl *elList = hashElListHash(cart->hash); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE), TRUE); } void cartDumpPrefix(struct cart *cart, char *prefix) /* Dump all cart variables with prefix */ { struct hashEl *elList = cartFindPrefix(cart, prefix); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE), TRUE); } void cartDumpLike(struct cart *cart, char *wildcard) /* Dump all cart variables matching wildcard */ { struct hashEl *elList = cartFindLike(cart, wildcard); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE), TRUE); } char *cartFindFirstLike(struct cart *cart, char *wildCard) /* Find name of first variable that matches wildCard in cart. * Return NULL if none. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); char *name = NULL; for (el = elList; el != NULL; el = el->next) { if (wildMatch(wildCard, el->name)) { name = el->name; break; } } hashElFreeList(&el); return name; } static struct hashEl *cartFindSome(struct cart *cart, char *pattern, boolean (*match)(const char *a, const char *b)) /* Return list of name/val pairs from cart where name matches * pattern. Free when done with hashElFreeList. */ { struct hashEl *el, *next, *elList = hashElListHash(cart->hash); struct hashEl *outList = NULL; for (el = elList; el != NULL; el = next) { next = el->next; if (match(pattern, el->name)) { slAddHead(&outList, el); } else { hashElFree(&el); } } return outList; } struct hashEl *cartFindLike(struct cart *cart, char *wildCard) /* Return list of name/val pairs from cart where name matches * wildcard. Free when done with hashElFreeList. */ { return cartFindSome(cart, wildCard, wildMatch); } struct hashEl *cartFindPrefix(struct cart *cart, char *prefix) /* Return list of name/val pairs from cart where name starts with * prefix. Free when done with hashElFreeList. */ { return cartFindSome(cart, prefix, startsWith); } static char *cookieDate() /* Return date string for cookie format. We'll have to * revisit this in 35 years.... */ { return "Thu, 31-Dec-2037 23:59:59 GMT"; } static char *getCookieId(char *cookieName) /* Get id value from cookie. */ { return findCookieData(cookieName); } static char *getSessionId() /* Get session id if any from CGI. */ { return cgiOptionalString("hgsid"); } static void clearDbContents(struct sqlConnection *conn, char *table, char * secureId) /* Clear out contents field of row in table that matches id. */ { if (!secureId) return; struct dyString *query = dyStringNew(256); char *sessionKey = NULL; unsigned int id = cartDbParseId(secureId, &sessionKey); char *defaultCartContents = getDefaultCart(conn); sqlDyStringPrintf(query, "update %s set contents='%s' where id=%u", table, defaultCartContents, id); if (cartDbUseSessionKey()) { if (!sessionKey) sessionKey = ""; sqlDyStringPrintf(query, " and sessionKey='%s'", sessionKey); } sqlUpdate(conn, query->string); dyStringFree(&query); } void cartResetInDb(char *cookieName) /* Clear cart in database. */ { char *hguid = getCookieId(cookieName); char *hgsid = getSessionId(); struct sqlConnection *conn = cartDefaultConnector(); clearDbContents(conn, userDbTable(), hguid); clearDbContents(conn, sessionDbTable(), hgsid); cartDefaultDisconnector(&conn); } void cartWriteCookie(struct cart *cart, char *cookieName) /* Write out HTTP Set-Cookie statement for cart. */ { char *domain = cfgVal("central.domain"); if (sameWord("HTTPHOST", domain)) { // IE9 does not accept portnames in cookie domains char *hostWithPort = hHttpHost(); struct netParsedUrl npu; netParseUrl(hostWithPort, &npu); if (strchr(npu.host, '.') != NULL) // Domains without a . don't seem to be kept domain = cloneString(npu.host); else domain = NULL; } char userIdKey[256]; cartDbSecureId(userIdKey, sizeof userIdKey, cart->userInfo); // Some users reported blank cookie values. Do we see that here? if (sameString(userIdKey,"")) // make sure we do not write any blank cookies. { // Be sure we do not lose this message. // Because the error happens so early we cannot trust that the warn and error handlers // are setup correctly and working. verbose(1, "unexpected error in cartWriteCookie: userId string is empty."); dumpStack( "unexpected error in cartWriteCookie: userId string is empty."); warn( "unexpected error in cartWriteCookie: userId string is empty."); } else { if (!isEmpty(domain)) printf("Set-Cookie: %s=%s; path=/; domain=%s; expires=%s\r\n", cookieName, userIdKey, domain, cookieDate()); else printf("Set-Cookie: %s=%s; path=/; expires=%s\r\n", cookieName, userIdKey, cookieDate()); } if (geoMirrorEnabled()) { // This occurs after the user has manually choosen to go back to the original site; we store redirect value into a cookie so we // can use it in subsequent hgGateway requests before loading the user's cart char *redirect = cgiOptionalString("redirect"); if (redirect) { printf("Set-Cookie: redirect=%s; path=/; domain=%s; expires=%s\r\n", redirect, cgiServerName(), cookieDate()); } } /* Validate login cookies if login is enabled */ if (loginSystemEnabled()) { struct slName *newCookies = loginValidateCookies(cart), *sl; for (sl = newCookies; sl != NULL; sl = sl->next) printf("Set-Cookie: %s\r\n", sl->name); } } static void cartJsonStart() /* Write the necessary headers for Apache */ { puts("Content-Type: application/json\n"); } static void cartJsonEnd(struct jsonWrite *jw) /* Write the final string which may have nothing in it */ { if (jw) puts(jw->dy->string); } struct cart *cartForSession(char *cookieName, char **exclude, struct hash *oldVars) /* This gets the cart without writing any HTTP lines at all to stdout. */ { /* Most cgis call this routine */ if (sameOk(cfgOption("signalsHandler"), "on")) /* most cgis call this routine */ initSigHandlers(hDumpStackEnabled()); /* HTTPS SSL Cert Checking Settings */ char *httpsCertCheck = cfgOption("httpsCertCheck"); if (httpsCertCheck) setenv("https_cert_check", httpsCertCheck, TRUE); char *httpsCertCheckVerbose = cfgOption("httpsCertCheckVerbose"); if (httpsCertCheckVerbose) setenv("https_cert_check_verbose", httpsCertCheckVerbose, TRUE); char *httpsCertCheckDepth = cfgOption("httpsCertCheckDepth"); if (httpsCertCheckDepth) setenv("https_cert_check_depth", httpsCertCheckDepth, TRUE); char *httpsCertCheckDomainExceptions = cfgOption("httpsCertCheckDomainExceptions"); if (httpsCertCheckDomainExceptions) setenv("https_cert_check_domain_exceptions", httpsCertCheckDomainExceptions, TRUE); /* Proxy Settings * net.c cannot see the cart, pass the value through env var */ char *httpProxy = cfgOption("httpProxy"); if (httpProxy) setenv("http_proxy", httpProxy, TRUE); char *httpsProxy = cfgOption("httpsProxy"); if (httpsProxy) setenv("https_proxy", httpsProxy, TRUE); char *ftpProxy = cfgOption("ftpProxy"); if (ftpProxy) setenv("ftp_proxy", ftpProxy, TRUE); char *noProxy = cfgOption("noProxy"); if (noProxy) setenv("no_proxy", noProxy, TRUE); char *logProxy = cfgOption("logProxy"); if (logProxy) setenv("log_proxy", logProxy, TRUE); /* noSqlInj settings so they are accessible in src/lib too */ char *noSqlInj_level = cfgOption("noSqlInj.level"); if (noSqlInj_level) setenv("noSqlInj_level", noSqlInj_level, TRUE); char *noSqlInj_dumpStack = cfgOption("noSqlInj.dumpStack"); if (noSqlInj_dumpStack) setenv("noSqlInj_dumpStack", noSqlInj_dumpStack, TRUE); // if ignoreCookie is on the URL, don't check for cookies char *hguid = NULL; if ( cgiOptionalString("ignoreCookie") == NULL ) hguid = getCookieId(cookieName); // if _dumpToLog is on the URL, we can exit early with whatever // message we are trying to write to the stderr/error_log char *logMsg = NULL; if ( (logMsg = cgiOptionalString("_dumpToLog")) != NULL) { cartJsonStart(); fprintf(stderr, "%s", logMsg); cartJsonEnd(NULL); exit(0); } char *hgsid = getSessionId(); struct cart *cart = cartNew(hguid, hgsid, exclude, oldVars); cartExclude(cart, sessionVar); // activate optional debuging output for CGIs verboseCgi(cartCgiUsualString(cart, "verbose", NULL)); return cart; } static void addHttpHeaders() /* CGIs can initialize the global variable httpHeaders to control their own HTTP * headers. This allows, for example, to prevent web browser caching of hgTracks * responses, but implicitly allow web browser caching everywhere else */ { struct slPair *h; for (h = httpHeaders; h != NULL; h = h->next) { printf("%s: %s\n", h->name, (char *)h->val); } } struct cart *cartAndCookieWithHtml(char *cookieName, char **exclude, struct hash *oldVars, boolean doContentType) /* Load cart from cookie and session cgi variable. Write cookie * and optionally content-type part HTTP preamble to web page. Don't * write any HTML though. */ { // Note: early abort works fine but early warn does not htmlPushEarlyHandlers(); struct cart *cart = cartForSession(cookieName, exclude, oldVars); popWarnHandler(); popAbortHandler(); cartWriteCookie(cart, cookieName); if (doContentType && !cartDidContentType) { addHttpHeaders(); puts("Content-Type:text/html"); puts("\n"); cartDidContentType = TRUE; } return cart; } struct cart *cartAndCookie(char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write cookie and * content-type part HTTP preamble to web page. Don't write any HTML though. */ { return cartAndCookieWithHtml(cookieName, exclude, oldVars, TRUE); } struct cart *cartAndCookieNoContent(char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Don't write out * content type or any HTML. */ { return cartAndCookieWithHtml(cookieName, exclude, oldVars, FALSE); } static void cartErrorCatcher(void (*doMiddle)(struct cart *cart), struct cart *cart) /* Wrap error catcher around call to do middle. */ { pushAbortHandler(htmlAbort); hDumpStackPushAbortHandler(); int status = setjmp(htmlRecover); if (status == 0) { doMiddle(cart); } hDumpStackPopAbortHandler(); popAbortHandler(); } void cartEarlyWarningHandler(char *format, va_list args) /* Write an error message so user can see it before page is really started. */ { static boolean initted = FALSE; va_list argscp; va_copy(argscp, args); if (!initted && !cgiOptionalString("ajax")) { if (!cartDidContentType) { puts("Content-Type:text/html\n"); cartDidContentType = TRUE; } htmStart(stdout, "Early Error"); initted = TRUE; } printf("%s", htmlWarnStartPattern()); htmlVaEncodeErrorText(format,args); printf("%s", htmlWarnEndPattern()); /* write warning/error message to stderr so they get logged. */ logCgiToStderr(); vfprintf(stderr, format, argscp); va_end(argscp); putc('\n', stderr); fflush(stderr); } void cartWarnCatcher(void (*doMiddle)(struct cart *cart), struct cart *cart, WarnHandler warner) /* Wrap error and warning handlers around doMiddle. */ { pushWarnHandler(warner); cartErrorCatcher(doMiddle, cart); popWarnHandler(); } static boolean inWeb = FALSE; static boolean didCartHtmlStart = FALSE; void cartHtmlStart(char *title) /* Write HTML header and put in normal error handler. */ { pushWarnHandler(htmlVaWarn); htmStart(stdout, title); didCartHtmlStart = TRUE; } static void cartVaWebStartMaybeHeader(struct cart *cart, char *db, boolean withHttpHeader, char *format, va_list args) /* Print out optional Content-Type and pretty wrapper around things when working from cart. */ { pushWarnHandler(htmlVaWarn); webStartWrapper(cart, trackHubSkipHubName(db), format, args, withHttpHeader, FALSE); inWeb = TRUE; jsIncludeFile("jquery.js", NULL); jsIncludeFile("utils.js", NULL); jsIncludeFile("ajax.js", NULL); } void cartVaWebStart(struct cart *cart, char *db, char *format, va_list args) /* Print out pretty wrapper around things when working * from cart. */ { cartVaWebStartMaybeHeader(cart, db, FALSE, format, args); } void cartWebStart(struct cart *cart, char *db, char *format, ...) /* Print out pretty wrapper around things when working * from cart. */ { va_list args; va_start(args, format); cartVaWebStart(cart, db, format, args); va_end(args); if (isNotEmpty(db)) { // Why do we put an input outside of a form on almost every page we make? // Tim put this in. Talking with him it sounds like some pages might actually // depend on it. Not removing it until we have a chance to test. Best fix // might be to add it to cartSaveSession, though this would then no longer be // well named, and not all things have 'db.' Arrr. Probably best to remove // and test a bunch. cgiMakeHiddenVar("db", db); } } void cartWebStartHeader(struct cart *cart, char *db, char *format, ...) /* Print out Content-type header and then pretty wrapper around things when working * from cart. */ { va_list args; va_start(args, format); cartVaWebStartMaybeHeader(cart, db, TRUE, format, args); va_end(args); } void cartWebEnd() /* Write out HTML footer and get rid or error handler. */ { webEnd(); popWarnHandler(); inWeb = FALSE; } void cartFooter(void) /* Write out HTML footer, possibly with googleAnalytics too */ { #ifndef GBROWSE googleAnalytics(); /* can't do this in htmlEnd */ #endif /* GBROWSE */ htmlEnd(); /* because it is in a higher library */ } void cartHtmlEnd() /* Write out HTML footer and get rid or error handler. */ { if (inWeb) webEnd(); /* this does googleAnalytics for a lot of CGIs */ else if (didCartHtmlStart) cartFooter(); else return; popWarnHandler(); } void setThemeFromCart(struct cart *cart) /* If 'theme' variable is set in cart: overwrite background with the one from * defined for this theme Also set the "styleTheme", with additional styles * that can overwrite the main style settings */ { // Get theme from cart and use it to get background file from config; // format is browser.theme.<name>=<stylesheet>[,<background>] char *cartTheme = cartOptionalString(cart, "theme"); // XXXX which setting should take precedence? Currently browser.theme does. char *styleFile = cfgOption("browser.style"); if (styleFile != NULL) { char buf[512]; safef(buf, sizeof(buf), "<link rel='stylesheet' href='%s' type='text/css'>", styleFile); char *copy = cloneString(buf); htmlSetStyleTheme(copy); // for htmshell.c, used by hgTracks webSetStyle(copy); // for web.c, used by hgc } if (isNotEmpty(cartTheme)) { char *themeKey = catTwoStrings("browser.theme.", cartTheme); styleFile = cfgOption(themeKey); freeMem(themeKey); if (isEmpty(styleFile)) return; char * link = webCssLink(styleFile, FALSE); // resource file link wrapped in html if (link != NULL && !sameOk(link, "<>")) // "<>" means "default settings" = "no file" { htmlSetStyleTheme(link); // for htmshell.c, used by hgTracks webSetStyle(link); // for web.c, used by hgc } } } void cartSetLastPosition(struct cart *cart, char *position, struct hash *oldVars) /* If position and oldVars are non-NULL, and oldVars' position is different, add it to the cart * as lastPosition. This is called by cartHtmlShell{,WithHead} but not other cart openers; * it should be called after cartGetPosition or equivalent. */ { if (position != NULL && oldVars != NULL) { struct hashEl *oldPos = hashLookup(oldVars, positionCgiName); if (oldPos != NULL && differentString(position, oldPos->val)) cartSetString(cart, "lastPosition", oldPos->val); } } void cartHtmlShellWithHead(char *head, char *title, void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write web-page * preamble including head and title, call doMiddle with cart, and write end of web-page. * Exclude may be NULL. If it exists it's a comma-separated list of * variables that you don't want to save in the cart between * invocations of the cgi-script. */ { struct cart *cart; char *db, *org, *pos; char titlePlus[2048]; pushWarnHandler(cartEarlyWarningHandler); cart = cartAndCookie(cookieName, exclude, oldVars); getDbAndGenome(cart, &db, &org, oldVars); pos = cartGetPosition(cart, db, NULL); pos = addCommasToPos(db, stripCommas(pos)); cartSetLastPosition(cart, pos, oldVars); safef(titlePlus, sizeof(titlePlus), "%s %s %s %s", org ? trackHubSkipHubName(org) : "", db ? db : "", pos ? pos : "", title); popWarnHandler(); setThemeFromCart(cart); googleAnalyticsSetGa4Key(); htmStartWithHead(stdout, head, titlePlus); cartWarnCatcher(doMiddle, cart, htmlVaWarn); cartCheckout(&cart); cartFooter(); } static void cartEmptyShellMaybeContent(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars, boolean doContentType) /* Get cart and cookies and set up error handling. * If doContentType, print out Content-type:text/html * but don't start writing any html yet. * The doMiddleFunction has to call cartHtmlStart(title), and * cartHtmlEnd(), as well as writing the body of the HTML. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { struct cart *cart = cartAndCookieWithHtml(cookieName, exclude, oldVars, doContentType); setThemeFromCart(cart); cartWarnCatcher(doMiddle, cart, cartEarlyWarningHandler); cartCheckout(&cart); } void cartEmptyShell(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Get cart and cookies and set up error handling, but don't start writing any * html yet. The doMiddleFunction has to call cartHtmlStart(title), and * cartHtmlEnd(), as well as writing the body of the HTML. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { cartEmptyShellMaybeContent(doMiddle, cookieName, exclude, oldVars, TRUE); } void cartEmptyShellNoContent(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Get cart and cookies and set up error handling. * The doMiddle function must write the Content-Type header line. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { cartEmptyShellMaybeContent(doMiddle, cookieName, exclude, oldVars, FALSE); } void cartHtmlShell(char *title, void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write web-page * preamble, call doMiddle with cart, and write end of web-page. * Exclude may be NULL. If it exists it's a comma-separated list of * variables that you don't want to save in the cart between * invocations of the cgi-script. */ { cartHtmlShellWithHead("", title, doMiddle, cookieName, exclude, oldVars); } void cartSetDbConnector(DbConnector connector) /* Set the connector that will be used by the cart to connect to the * database. Default connector is hConnectCart */ { cartDefaultConnector = connector; } void cartSetDbDisconnector(DbDisconnect disconnector) /* Set the connector that will be used by the cart to disconnect from the * database. Default disconnector is hDisconnectCart */ { cartDefaultDisconnector = disconnector; } char *cartGetOrderFromFile(char *genomeDb, struct cart *cart, char *speciesUseFile) /* Look in a cart variable that holds the filename that has a list of * species to show in a maf file */ { char *val; struct dyString *orderDY = dyStringNew(256); char *words[16]; if ((val = cartUsualString(cart, speciesUseFile, NULL)) == NULL) { errAbort("can't find species list file var '%s' in cart\n",speciesUseFile); } struct lineFile *lf = lineFileOpen(val, TRUE); if (lf == NULL) errAbort("can't open species list file %s",val); while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) )) dyStringPrintf(orderDY, "%s ",words[0]); return dyStringCannibalize(&orderDY); } char *cartLookUpVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix,char **pVariable) /* Returns value or NULL for a cart variable from lowest level on up. Optionally * fills the non NULL pVariable with the actual name of the variable in the cart */ { if (parentLevel) tdb = tdb->parent; for ( ; tdb != NULL; tdb = tdb->parent) { char buf[512]; + char *trackName = tdb->track; + if (tdb->originalTrack) + trackName = tdb->originalTrack; if (suffix[0] == '.' || suffix[0] == '_') - safef(buf, sizeof buf, "%s%s", tdb->track,suffix); + safef(buf, sizeof buf, "%s%s", trackName,suffix); else - safef(buf, sizeof buf, "%s.%s", tdb->track,suffix); + safef(buf, sizeof buf, "%s.%s", trackName,suffix); char *cartSetting = hashFindVal(cart->hash, buf); if (cartSetting != NULL) { if(pVariable != NULL) *pVariable = cloneString(buf); return cartSetting; } } if (pVariable != NULL) *pVariable = NULL; return NULL; } void cartRemoveVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Looks for then removes a cart variable from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *var = NULL; (void)cartLookUpVariableClosestToHome(cart,tdb,parentLevel,suffix,&var); if (var != NULL) { cartRemove(cart,var); freeMem(var); } } char *cartStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns value or Aborts for a cart string from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) errAbort("cartStringClosestToHome: '%s' not found", suffix); return setting; } boolean cartVarExistsAnyLevel(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns TRUE if variable exists anywhere, looking from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { return (NULL != cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix)); } char *cartUsualStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, char *usual) /* Returns value or {usual} for a cart string from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if(setting == NULL) setting = usual; return setting; } boolean cartBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns value or Aborts for a cart boolean ('on' or != 0) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartStringClosestToHome(cart,tdb,parentLevel,suffix); return (sameString(setting, "on") || atoi(setting) > 0); } boolean cartUsualBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix,boolean usual) /* Returns value or {usual} for a cart boolean ('on' or != 0) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return (sameString(setting, "on") || atoi(setting) > 0); } int cartUsualIntClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, int usual) /* Returns value or {usual} for a cart int from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return atoi(setting); } double cartUsualDoubleClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, double usual) /* Returns value or {usual} for a cart fp double from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return atof(setting); } struct slName *cartOptionalSlNameListClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Return slName list (possibly with multiple values for the same var) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *var = NULL; cartLookUpVariableClosestToHome(cart,tdb,parentLevel,suffix,&var); if (var == NULL) return NULL; struct slName *slNames = cartOptionalSlNameList(cart,var); freeMem(var); return slNames; } void cartRemoveAllForTdb(struct cart *cart, struct trackDb *tdb) /* Remove all variables from cart that are associated with this tdb. */ { char setting[256]; safef(setting,sizeof(setting),"%s.",tdb->track); cartRemovePrefix(cart,setting); safef(setting,sizeof(setting),"%s_",tdb->track); // TODO: All should be {track}.{varName}... Fix {track}_sel cartRemovePrefix(cart,setting); cartRemove(cart,tdb->track); } void cartRemoveAllForTdbAndChildren(struct cart *cart, struct trackDb *tdb) /* Remove all variables from cart that are associated with this tdb and it's children. */ { struct trackDb *subTdb; for (subTdb=tdb->subtracks;subTdb!=NULL;subTdb=subTdb->next) cartRemoveAllForTdbAndChildren(cart,subTdb); cartRemoveAllForTdb(cart,tdb); saveState(cart); } char *cartOrTdbString(struct cart *cart, struct trackDb *tdb, char *var, char *defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *tdbDefault = trackDbSettingClosestToHomeOrDefault(tdb, var, defaultVal); boolean parentLevel = isNameAtParentLevel(tdb, var); return cartUsualStringClosestToHome(cart, tdb, parentLevel, var, tdbDefault); } int cartOrTdbInt(struct cart *cart, struct trackDb *tdb, char *var, int defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *a = cartOrTdbString(cart, tdb, var, NULL); if (a == NULL) return defaultVal; else return atoi(a); } double cartOrTdbDouble(struct cart *cart, struct trackDb *tdb, char *var, double defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *a = cartOrTdbString(cart, tdb, var, NULL); if (a == NULL) return defaultVal; else return atof(a); } boolean cartOrTdbBoolean(struct cart *cart, struct trackDb *tdb, char *var, boolean defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { boolean tdbVal = defaultVal; char *tdbSetting = trackDbSetting(tdb, var); if (tdbSetting != NULL) tdbVal = trackDbSettingClosestToHomeOn(tdb, var); return cartUsualBooleanClosestToHome(cart, tdb, isNameAtParentLevel(tdb, var), var, tdbVal); } // These macros allow toggling warn messages to NOOPS when no longer debugging //#define DEBUG_WITH_WARN #ifdef DEBUG_WITH_WARN #define WARN warn #define ASSERT assert #else///ifndef DEBUG_WITH_WARN #define WARN(...) #define ASSERT(...) #endif///ndef DEBUG_WITH_WARN boolean cartValueHasChanged(struct cart *newCart,struct hash *oldVars,char *setting, boolean ignoreRemoved,boolean ignoreCreated) /* Returns TRUE if new cart setting has changed from old cart setting */ { char *oldValue = hashFindVal(oldVars,setting); if (oldValue == NULL) return FALSE; // All vars changed by cgi will be found in old vars char *newValue = cartOptionalString(newCart,setting); if (newValue == NULL) return (!ignoreRemoved); if (sameString(oldValue,CART_VAR_EMPTY)) { if (sameString(newValue,"hide") || sameString(newValue,"off") || sameString(newValue,"0")) // Special cases DANGER! return FALSE; } return (differentString(newValue,oldValue)); } static int cartNamesPruneChanged(struct cart *newCart,struct hash *oldVars, struct slPair **cartNames,boolean ignoreRemoved,boolean unChanged) /* Prunes a list of cartNames if the settings have changed between new and old cart. Returns pruned count */ { int pruned = 0; struct slPair *oldList = *cartNames; struct slPair *newList = NULL; struct slPair *oneName = NULL; while ((oneName = slPopHead(&oldList)) != NULL) { boolean thisOneChanged = cartValueHasChanged(newCart,oldVars,oneName->name,ignoreRemoved,TRUE); if (unChanged != thisOneChanged) slAddHead(&newList,oneName); else { pruned++; } } *cartNames = newList; return pruned; } int cartRemoveFromTdbTree(struct cart *cart,struct trackDb *tdb,char *suffix,boolean skipParent) /* Removes a 'trackName.suffix' from all tdb descendents (but not parent). If suffix NULL then removes 'trackName' which holds visibility */ { int removed = 0; boolean vis = (suffix == NULL || *suffix == '\0'); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendants(skipParent?tdb->subtracks:tdb); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *descendentTdb = tdbRef->val; char setting[512]; if (vis) safef(setting,sizeof(setting),"%s",descendentTdb->track); else safef(setting,sizeof(setting),"%s.%s",descendentTdb->track,suffix); removed += cartRemoveAndCount(cart,setting); } return removed; } static int cartRemoveOldFromTdbTree(struct cart *newCart,struct hash *oldVars,struct trackDb *tdb, char *suffix,char *parentVal,boolean skipParent) // Removes a 'trackName.suffix' from all tdb descendents (but not parent), BUT ONLY // IF OLD or same as parentVal. If suffix NULL then removes 'trackName' which holds visibility { int removed = 0; boolean vis = (suffix == NULL || *suffix == '\0'); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendants(skipParent?tdb->subtracks:tdb); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *descendentTdb = tdbRef->val; char setting[512]; if (vis) safef(setting,sizeof(setting),"%s",descendentTdb->track); else safef(setting,sizeof(setting),"%s.%s",descendentTdb->track,suffix); char *newVal = cartOptionalString(newCart,setting); if ( newVal != NULL && ( (parentVal != NULL && sameString(newVal,parentVal)) || (FALSE == cartValueHasChanged(newCart,oldVars,setting,TRUE,FALSE)))) removed += cartRemoveAndCount(newCart,setting); } return removed; } static boolean cartTdbOverrideSuperTracks(struct cart *cart,struct trackDb *tdb, boolean ifJustSelected) // When when the child of a hidden supertrack is foudn and selected, then shape the // supertrack accordingly. Returns TRUE if any cart changes are made { // This is only pertinent to supertrack children just turned on if (!tdbIsSuperTrackChild(tdb)) return FALSE; char setting[512]; // Must be from having just selected the track in findTracks. // This will carry with it the "_sel" setting. if (ifJustSelected) { safef(setting,sizeof(setting),"%s_sel",tdb->track); if (!cartVarExists(cart,setting)) return FALSE; cartRemove(cart,setting); // Unlike composite subtracks, supertrack children keep } // the "_sel" setting only for detecting this moment // if parent is not hidden then nothing to do ASSERT(tdb->parent != NULL && tdbIsSuperTrack(tdb->parent)); enum trackVisibility vis = tdbVisLimitedByAncestry(cart, tdb->parent, FALSE); if (vis != tvHide) return FALSE; // Now turn all other supertrack children to hide and the supertrack to visible struct slRef *childRef; for (childRef = tdb->parent->children;childRef != NULL; childRef = childRef->next) { struct trackDb *child = childRef->val; if (child == tdb) continue; // Make sure this child hasn't also just been turned on! safef(setting,sizeof(setting),"%s_sel",child->track); if (cartVarExists(cart,setting)) { cartRemove(cart,setting); // Unlike composite subtracks, supertrack children keep continue; // the "_sel" setting only for detecting this moment } // hide this sibling if not already hidden char *cartVis = cartOptionalString(cart,child->track); if (cartVis != NULL) { if (child->visibility == tvHide) cartRemove(cart,child->track); else if (hTvFromString(cartVis) != tvHide) cartSetString(cart,child->track,"hide"); } else if (child->visibility != tvHide) cartSetString(cart,child->track,"hide"); } // and finally show the parent cartSetString(cart,tdb->parent->track,"show"); WARN("Set %s to 'show'",tdb->parent->track); return TRUE; } static int cartTdbParentShapeVis(struct cart *cart,struct trackDb *parent,char *view, struct hash *subVisHash,boolean reshapeFully) // This shapes one level of vis (view or container) based upon subtrack specific visibility. // Returns count of tracks affected { ASSERT(view || (tdbIsContainer(parent) && tdbIsContainerChild(parent->subtracks))); struct trackDb *subtrack = NULL; char setting[512]; safef(setting,sizeof(setting),"%s",parent->track); enum trackVisibility visMax = tvHide; enum trackVisibility visOrig = tdbVisLimitedByAncestry(cart, parent, FALSE); // Should walk through children to get max new vis for this parent for (subtrack = parent->subtracks;subtrack != NULL;subtrack = subtrack->next) { char *foundVis = hashFindVal(subVisHash, subtrack->track); // if the subtrack doesn't have if (foundVis != NULL) // individual vis AND... { enum trackVisibility visSub = hTvFromString(foundVis); if (tvCompare(visMax, visSub) >= 0) visMax = visSub; } else if (!reshapeFully && visOrig != tvHide) { int fourState = subtrackFourStateChecked(subtrack,cart); if (fourStateVisible(fourState)) // subtrack must be visible { enum trackVisibility visSub = tdbVisLimitedByAncestry(cart, subtrack, FALSE); if (tvCompare(visMax, visSub) >= 0) visMax = visSub; } } } // Now we need to update non-subtrack specific vis/sel in cart int countUnchecked=0; int countVisChanged=0; // If view, this should always be set, since if a single view needs // to be promoted, the composite will go to full. if (tdbIsCompositeView(parent)) { cartSetString(cart,setting,hStringFromTv(visMax)); // Set this explicitly. countVisChanged++; // The visOrig may be inherited! } if (visMax != visOrig || reshapeFully) { if (!tdbIsCompositeView(parent)) // view vis is always shaped, { // but composite vis is conditionally shaped. cartSetString(cart,setting,hStringFromTv(visMax)); // Set this explicitly. countVisChanged++; if (visOrig == tvHide && tdbIsSuperTrackChild(parent)) cartTdbOverrideSuperTracks(cart,parent,FALSE); // deal with superTrack vis! cleanup } // Now set all subtracks that inherit vis back to visOrig for (subtrack = parent->subtracks;subtrack != NULL;subtrack = subtrack->next) { int fourState = subtrackFourStateChecked(subtrack,cart); if (!fourStateChecked(fourState)) cartRemove(cart,subtrack->track); // Remove subtrack level vis if it isn't even checked else // subtrack is checked (should include subtrack level vis) { char *visFromHash = hashFindVal(subVisHash, subtrack->track); if (tdbIsMultiTrack(parent)) // MultiTrack vis is ALWAYS inherited vis cartRemove(cart,subtrack->track); // and non-selected should not have vis if (visFromHash == NULL) // if the subtrack doesn't have individual vis AND... { if (reshapeFully || visOrig == tvHide) { // uncheck subtrackFourStateCheckedSet(subtrack, cart,FALSE,fourStateEnabled(fourState)); cartRemove(cart,subtrack->track); // Remove it if it exists, just in case countUnchecked++; } else if (visOrig != tvHide) { cartSetString(cart,subtrack->track,hStringFromTv(visOrig)); countVisChanged++; } } else // This subtrack has explicit vis { enum trackVisibility vis = hTvFromString(visFromHash); if (vis == visMax) { cartRemove(cart,subtrack->track); // Remove vis which should now be inherited countVisChanged++; } } } } } else if (tdbIsMultiTrack(parent)) { // MultiTrack vis is ALWAYS inherited vis so remove any subtrack specific vis struct hashCookie brownie = hashFirst(subVisHash); struct hashEl* cartVar = NULL; while ((cartVar = hashNext(&brownie)) != NULL) { if (!endsWith(cartVar->name,"_sel")) cartRemove(cart,cartVar->name); } } if (countUnchecked + countVisChanged) { WARN("%s visOrig:%s visMax:%s unchecked:%d Vis changed:%d",parent->track, hStringFromTv(visOrig),hStringFromTv(visMax),countUnchecked,countVisChanged); } return (countUnchecked + countVisChanged); } boolean cartTdbTreeReshapeIfNeeded(struct cart *cart,struct trackDb *tdbContainer) /* When subtrack vis is set via findTracks, and composite has no cart settings, then "shape" composite to match found */ { if (!tdbIsContainer(tdbContainer)) return FALSE; // Don't do any shaping // First look for subtrack level vis char setting[512]; struct trackDb *subtrack = NULL; struct trackDb *tdbView = NULL; struct hash *subVisHash = newHash(0); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendantLeaves(tdbContainer->subtracks); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { subtrack = tdbRef->val; char *val=cartOptionalString(cart,subtrack->track); if (val && differentString(val,"hide")) // NOTE should we include hide? { int fourState = subtrackFourStateChecked(subtrack,cart); if (fourStateVisible(fourState)) // subtrack is checked too { WARN("Subtrack level vis %s=%s",subtrack->track,val); hashAdd(subVisHash,subtrack->track,val); safef(setting,sizeof(setting),"%s_sel",subtrack->track); hashAdd(subVisHash,setting,subtrack); // Add the "_sel" setting which should also exist. } // Point it to subtrack } } slFreeList(&tdbRefList); if (hashNumEntries(subVisHash) == 0) { //WARN("No subtrack level vis for %s",tdbContainer->track); return FALSE; } // Next look for any cart settings other than subtrack vis/sel // New directive means if composite is hidden, then ignore previous and don't bother checking cart. boolean reshapeFully = (tdbVisLimitedByAncestry(cart, tdbContainer, FALSE) == tvHide); boolean hasViews = tdbIsCompositeView(tdbContainer->subtracks); WARN("reshape: %s",reshapeFully?"Fully":"Incrementally"); // Now shape views and composite to match subtrack specific visibility int count = 0; if (hasViews) { for (tdbView = tdbContainer->subtracks;tdbView != NULL; tdbView = tdbView->next ) { char *view = NULL; if (tdbIsView(tdbView,&view) ) count += cartTdbParentShapeVis(cart,tdbView,view,subVisHash,reshapeFully); } if (count > 0) { // At least one view was shaped, so all views will get explicit vis. // This means composite must be set to full enum trackVisibility visOrig = tdbVisLimitedByAncestry(cart, tdbContainer, FALSE); cartSetString(cart,tdbContainer->track,"full"); // Now set composite to full. if (visOrig == tvHide && tdbIsSuperTrackChild(tdbContainer)) cartTdbOverrideSuperTracks(cart,tdbContainer,FALSE);// deal with superTrack vis cleanup } } else // If no views then composite is not set to fuul but to max of subtracks count = cartTdbParentShapeVis(cart,tdbContainer,NULL,subVisHash,reshapeFully); hashFree(&subVisHash); // If reshaped, be sure to set flag to stop composite cleanup if (count > 0) tdbExtrasReshapedCompositeSet(tdbContainer); return TRUE; } boolean cartTdbTreeCleanupOverrides(struct trackDb *tdb,struct cart *newCart,struct hash *oldVars, struct lm *lm) /* When container or composite/view settings changes, remove subtrack specific settings Returns TRUE if any cart vars are removed */ { boolean anythingChanged = cartTdbOverrideSuperTracks(newCart,tdb,TRUE); if (!tdbIsContainer(tdb)) return anythingChanged; // If composite has been reshaped then don't clean it up if (tdbExtrasReshapedComposite(tdb)) return anythingChanged; // vis is a special additive case! composite or view level changes then remove subtrack vis boolean containerVisChanged = cartValueHasChanged(newCart,oldVars,tdb->track,TRUE,FALSE); if (containerVisChanged) { // If just created and if vis is the same as tdb default then vis has not changed char *cartVis = cartOptionalString(newCart,tdb->track); char *oldValue = hashFindVal(oldVars,tdb->track); if (cartVis && oldValue == NULL && hTvFromString(cartVis) == tdb->visibility) containerVisChanged = FALSE; } struct trackDb *tdbView = NULL; struct slPair *oneName = NULL; char *suffix = NULL; int clensed = 0; // Build list of current settings for container or composite and views char setting[512]; safef(setting,sizeof(setting),"%s.",tdb->track); char * view = NULL; boolean hasViews = FALSE; struct slPair *changedSettings = cartVarsWithPrefixLm(newCart, setting, lm); for (tdbView = tdb->subtracks;tdbView != NULL; tdbView = tdbView->next) { if (!tdbIsView(tdbView,&view)) break; hasViews = TRUE; char *cartVis = cartOptionalString(newCart,tdbView->track); if (cartVis != NULL) // special to get viewVis in the list { lmAllocVar(lm, oneName); oneName->name = lmCloneString(lm, tdbView->track); oneName->val = lmCloneString(lm, cartVis); slAddHead(&changedSettings,oneName); } // Now the non-vis settings safef(setting,sizeof(setting),"%s.",tdbView->track); struct slPair *changeViewSettings = cartVarsWithPrefixLm(newCart, setting, lm); changedSettings = slCat(changedSettings, changeViewSettings); } if (changedSettings == NULL && !containerVisChanged) return anythingChanged; // Prune list to only those which have changed if (changedSettings != NULL) { (void)cartNamesPruneChanged(newCart,oldVars,&changedSettings,TRUE,FALSE); if (changedSettings == NULL && !containerVisChanged) return anythingChanged; } // Walk through views if (hasViews) { for (tdbView = tdb->subtracks;tdbView != NULL; tdbView = tdbView->next) { char *cartVis = NULL; boolean viewVisChanged = FALSE; if (!tdbIsView(tdbView,&view)) break; struct slPair *leftOvers = NULL; // Walk through settings that match this view while ((oneName = slPopHead(&changedSettings)) != NULL) { suffix = NULL; if (startsWith(tdbView->track,oneName->name)) { suffix = oneName->name + strlen(tdbView->track); if (*suffix == '.') // NOTE: standardize on '.' since its is so pervasive suffix++; else if (isalnum(*suffix)) // viewTrackName is subset of another track! suffix = NULL; // add back to list for next round } if (suffix == NULL) { slAddHead(&leftOvers,oneName); continue; } if (*suffix == '\0') { viewVisChanged = TRUE; cartVis = oneName->val; } else // be certain to exclude vis settings here if (cartRemoveOldFromTdbTree(newCart,oldVars,tdbView,suffix,oneName->val,TRUE) > 0) clensed++; //slPairFree(&oneName); // lm memory so free not needed } if (viewVisChanged) { // If just created and if vis is the same as tdb default then vis has not changed char *oldValue = hashFindVal(oldVars,tdbView->track); if (cartVis && oldValue == NULL && hTvFromString(cartVis) != tdbView->visibility) viewVisChanged = FALSE; } if (containerVisChanged || viewVisChanged) { // vis is a special additive case! WARN("Removing subtrack vis for %s.%s",tdb->track,view); char *viewVis = hStringFromTv(tdbVisLimitedByAncestry(newCart, tdbView, FALSE)); if (cartRemoveOldFromTdbTree(newCart,oldVars,tdbView,NULL,viewVis,TRUE) > 0) clensed++; } changedSettings = leftOvers; } } // Now deal with anything remaining at the container level while ((oneName = slPopHead(&changedSettings)) != NULL) { suffix = oneName->name + strlen(tdb->track) + 1; if (cartRemoveOldFromTdbTree(newCart,oldVars,tdb,suffix,oneName->val,TRUE) > 0) clensed++; } if (containerVisChanged && !hasViews) { // vis is a special additive case! char *vis = hStringFromTv(tdbVisLimitedByAncestry(newCart, tdb, FALSE)); if (cartRemoveOldFromTdbTree(newCart,oldVars,tdb,NULL,vis,TRUE) > 0) clensed++; } anythingChanged = (anythingChanged || (clensed > 0)); return anythingChanged; } void cgiExitTime(char *cgiName, long enteredMainTime) /* single stderr print out called at end of CGI binaries to record run * time in apache error_log */ { if (sameWord("yes", cfgOptionDefault("browser.cgiTime", "yes")) ) fprintf(stderr, "CGI_TIME: %s: Overall total time: %ld millis\n", cgiName, clock1000() - enteredMainTime); } // TODO This should probably be moved to customFactory.c // Only used by hgSession #include "errCatch.h" void cartCheckForCustomTracks(struct cart *cart, struct dyString *dyMessage) /* Scan cart for ctfile_<db> variables. Tally up the databases that have * live custom tracks and those that have expired custom tracks. */ /* While we're at it, also look for saved blat results. */ { struct hashEl *helList = cartFindPrefix(cart, CT_FILE_VAR_PREFIX); if (helList != NULL) { struct hashEl *hel; boolean gotLiveCT = FALSE, gotExpiredCT = FALSE, gotErrorCT = FALSE; struct slName *liveDbList = NULL, *expiredDbList = NULL, *errorDbList = NULL, *sln = NULL; for (hel = helList; hel != NULL; hel = hel->next) { char *db = hel->name + strlen(CT_FILE_VAR_PREFIX); boolean thisGotLiveCT = FALSE, thisGotExpiredCT = FALSE, thisGotErrorCT = FALSE; char errMsg[4096]; /* If the file doesn't exist, just remove the cart variable so it * doesn't get copied from session to session. If it does exist, * leave it up to customFactoryTestExistence to parse the file for * possible customTrash table references, some of which may exist * and some not. */ if (!fileExists(hel->val)) { cartRemove(cart, hel->name); thisGotExpiredCT = TRUE; } else { /* protect against errAbort */ struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { customFactoryTestExistence(db, hel->val, &thisGotLiveCT, &thisGotExpiredCT, NULL); } errCatchEnd(errCatch); if (errCatch->gotError) // tends to abort if db not found or hub not attached. { thisGotErrorCT = TRUE; safef(errMsg, sizeof errMsg, "%s {%s}", db, errCatch->message->string); } errCatchFree(&errCatch); } if (thisGotLiveCT) slNameAddHead(&liveDbList, db); if (thisGotExpiredCT) slNameAddHead(&expiredDbList, db); if (thisGotErrorCT) slNameAddHead(&errorDbList, errMsg); gotLiveCT |= thisGotLiveCT; gotExpiredCT |= thisGotExpiredCT; gotErrorCT |= thisGotErrorCT; } if (gotLiveCT) { slSort(&liveDbList, slNameCmp); dyStringPrintf(dyMessage, "<P>Note: the session has at least one active custom " "track (in database "); for (sln = liveDbList; sln != NULL; sln = sln->next) dyStringPrintf(dyMessage, "<A HREF=\"hgCustom?%s&db=%s\">%s</A>%s", cartSidUrlString(cart), sln->name, sln->name, (sln->next ? sln->next->next ? ", " : " and " : "")); dyStringAppend(dyMessage, "; click on the database link " "to manage custom tracks). "); } if (gotExpiredCT) { slSort(&expiredDbList, slNameCmp); dyStringPrintf(dyMessage, "<P>Note: the session has at least one expired custom " "track (in database "); for (sln = expiredDbList; sln != NULL; sln = sln->next) dyStringPrintf(dyMessage, "%s%s", sln->name, (sln->next ? sln->next->next ? ", " : " and " : "")); dyStringPrintf(dyMessage, "), so it may not appear as originally intended. "); } if (gotErrorCT) { slSort(&errorDbList, slNameCmp); dyStringPrintf(dyMessage, "<P>Note: the session has at least one custom " "track with errors (in database "); for (sln = errorDbList; sln != NULL; sln = sln->next) dyStringPrintf(dyMessage, "%s%s", sln->name, (sln->next ? sln->next->next ? ", " : " and " : "")); dyStringPrintf(dyMessage, "), so it may not appear as originally intended. "); } dyStringPrintf(dyMessage, "These custom tracks should not expire, however, " "the UCSC Genome Browser is not a data storage service; " "<b>please keep a local backup of your sessions contents " "and custom track data</b>. </P>"); slNameFreeList(&liveDbList); slNameFreeList(&expiredDbList); } /* Check for saved blat results (quasi custom track). */ char *ss = cartOptionalString(cart, "ss"); if (isNotEmpty(ss)) { char buf[1024]; char *words[2]; int wordCount; boolean exists = FALSE; safecpy(buf, sizeof(buf), ss); wordCount = chopLine(buf, words); if (wordCount < 2) exists = FALSE; else exists = fileExists(words[0]) && fileExists(words[1]); if (exists) dyStringPrintf(dyMessage, "<P>Note: the session contains BLAT results. "); else dyStringPrintf(dyMessage, "<P>Note: the session contains an expired reference to " "previously saved BLAT results, so it may not appear as " "originally intended. "); dyStringPrintf(dyMessage, "BLAT results are subject to an " "<A HREF=\"../goldenPath/help/hgSessionHelp.html#CTs\" TARGET=_BLANK>" "expiration policy</A>."); } } char *cartGetPosition(struct cart *cart, char *database, struct cart **pLastDbPosCart) /* get the current position in cart as a string chr:start-end. * This can handle the special CGI params 'default' and 'lastDbPos' * Returned value has to be freed. Returns * default position of assembly is no position set in cart nor as CGI var. * Returns NULL if no position set anywhere and no default position. * For virtual modes, returns the type and extraState. */ { // position=lastDbPos in URL? -> go back to the last browsed position for this db char *position = NULL; char *defaultPosition = hDefaultPos(database); struct cart *lastDbPosCart = cartOfNothing(); boolean gotCart = FALSE; char dbPosKey[256]; safef(dbPosKey, sizeof(dbPosKey), "position.%s", database); // use cartCgiUsualString in case request is coming via ajax call from hgGateway if (sameOk(cartCgiUsualString(cart, "position", NULL), "lastDbPos")) { char *dbLocalPosContent = cartUsualString(cart, dbPosKey, NULL); if (dbLocalPosContent) { if (strchr(dbLocalPosContent, '=')) { gotCart = TRUE; cartParseOverHash(lastDbPosCart, cloneString(dbLocalPosContent)); // this function chews up input position = cloneString(cartUsualString(lastDbPosCart, "position", NULL)); } else { position = dbLocalPosContent; // old style value } } else { position = defaultPosition; // no value was set } } if (position == NULL) { position = windowsToAscii(cloneString(cartUsualString(cart, "position", NULL))); } /* default if not set at all, as would happen if it came from a URL with no * position. Otherwise tell them to go back to the gateway. Also recognize * "default" as specifying the default position. */ if (((position == NULL) || sameString(position, "default")) && (defaultPosition != NULL)) position = cloneString(defaultPosition); if (!gotCart) { cartSetBoolean(lastDbPosCart, "virtMode", FALSE); cartSetString(lastDbPosCart, "virtModeType", "default"); cartSetString(lastDbPosCart, "lastVirtModeType", "default"); cartSetString(lastDbPosCart, "position", position); cartSetString(lastDbPosCart, "nonVirtPosition", position); cartSetString(lastDbPosCart, "lastVirtModeExtra", ""); } if (pLastDbPosCart) *pLastDbPosCart = lastDbPosCart; return position; } void cartSetDbPosition(struct cart *cart, char *database, struct cart *lastDbPosCart) /* Set the 'position.db' variable in the cart.*/ { char dbPosKey[256]; safef(dbPosKey, sizeof dbPosKey, "position.%s", database); struct dyString *dbPosValue = dyStringNew(4096); cartEncodeState(lastDbPosCart, dbPosValue); cartSetString(cart, dbPosKey, dbPosValue->string); } void cartTdbFetchMinMaxPixels(struct cart *theCart, struct trackDb *tdb, int defaultMin, int defaultMax, int defaultVal, int *retMin, int *retMax, int *retDefault, int *retCurrent) /* Configure maximum track height for variable height tracks (e.g. wiggle, barchart) * Initial height and limits may be defined in trackDb with the maxHeightPixels string, * Or user requested limits are defined in the cart. */ { boolean parentLevel = isNameAtParentLevel(tdb, tdb->track); char *heightPer = NULL; /* string from cart */ int minHeightPixels = defaultMin; int maxHeightPixels = defaultMax; int defaultHeightPixels = defaultVal; int defaultHeight; /* truncated by limits */ char defaultDefault[16]; safef(defaultDefault, sizeof defaultDefault, "%d", defaultVal); char *tdbDefault = cloneString( trackDbSettingClosestToHomeOrDefault(tdb, MAXHEIGHTPIXELS, defaultDefault)); if (sameWord(defaultDefault, tdbDefault)) { struct hashEl *hel; /* no maxHeightPixels from trackDb, maybe it is in tdb->settings * (custom tracks keep settings here) */ if ((tdb->settings != (char *)NULL) && (tdb->settingsHash != (struct hash *)NULL)) { if ((hel = hashLookup(tdb->settingsHash, MAXHEIGHTPIXELS)) != NULL) { freeMem(tdbDefault); tdbDefault = cloneString((char *)hel->val); } } } /* the maxHeightPixels string can be one, two, or three words * separated by : * All three would be: max:default:min * When only two: max:default * When only one: max * (this works too: min:default:max) * Where min is minimum allowed, default is initial default setting * and max is the maximum allowed * If it isn't available, these three have already been set * in their declarations above */ if (differentWord(defaultDefault, tdbDefault)) { char *words[3]; char *sep = ":"; int wordCount; wordCount=chopString(tdbDefault,sep,words,ArraySize(words)); switch (wordCount) { case 3: minHeightPixels = atoi(words[2]); defaultHeightPixels = atoi(words[1]); maxHeightPixels = atoi(words[0]); // flip max and min if min>max if (maxHeightPixels < minHeightPixels) { int pixels; pixels = maxHeightPixels; maxHeightPixels = minHeightPixels; minHeightPixels = pixels; } if (defaultHeightPixels > maxHeightPixels) defaultHeightPixels = maxHeightPixels; if (minHeightPixels > defaultHeightPixels) minHeightPixels = defaultHeightPixels; break; case 2: defaultHeightPixels = atoi(words[1]); maxHeightPixels = atoi(words[0]); if (defaultHeightPixels > maxHeightPixels) defaultHeightPixels = maxHeightPixels; if (minHeightPixels > defaultHeightPixels) minHeightPixels = defaultHeightPixels; break; case 1: maxHeightPixels = atoi(words[0]); defaultHeightPixels = maxHeightPixels; if (minHeightPixels > defaultHeightPixels) minHeightPixels = defaultHeightPixels; break; default: break; } } heightPer = cartOptionalStringClosestToHome(theCart, tdb, parentLevel, HEIGHTPER); /* Clip the cart value to range [minHeightPixels:maxHeightPixels] */ if (heightPer) defaultHeight = min( maxHeightPixels, atoi(heightPer)); else defaultHeight = defaultHeightPixels; defaultHeight = max(minHeightPixels, defaultHeight); *retMin = minHeightPixels; *retMax = maxHeightPixels; *retDefault = defaultHeightPixels; *retCurrent = defaultHeight; freeMem(tdbDefault); } unsigned cartGetVersion(struct cart *cart) /* Get the current version of the cart, which is stored in the variable "cartVersion" */ { unsigned ret = cartUsualInt(cart, "cartVersion", 0); return ret; } void cartSetVersion(struct cart *cart, unsigned version) /* Set the current version of the cart, which is stored in the variable "cartVersion" */ { cartSetInt(cart, "cartVersion", version); }