8fcdc8d74c55bc1ea951d03cfe374491b87285b4
chmalee
Fri Feb 13 16:40:26 2026 -0800
When disconnecting an assembly hub on hgHubConnect, remove any of those assembly hub genomes from the recents list in localStorage. If the last connected db was one of those hubs, clear the db, org and genome cart variables and reset the db variable back to the default hg38, otherwise the first row of defaultDb is chosen as the current genome, refs #36535
diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index 66874e63ad2..624ae97804a 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -1,4158 +1,4168 @@
/* 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"
#include "verbose.h"
#include "genark.h"
#include "quickLift.h"
#include "botDelay.h"
#include "curlWrap.h"
#include "hubSpaceKeys.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 copyLocalHubs(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;
if (startsWith(customCompositeCartName, el->name))
trashDirDateFile(&hubTn, "hgComposite", "hub", ".txt");
else if (startsWith(quickLiftCartName, el->name))
trashDirDateFile(&hubTn, "quickLift", "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) || startsWith(quickLiftCartName, hubFileVar) ))
errAbort("cartReplaceHubVars: expected hubFileVar to begin with '"customCompositeCartName"' "
"or '"quickLiftCartName";"
"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 "quickLift.#hubNumber#.#db# #quickLiftChainId#
hubVarList = cartVarsWithPrefix(cart, "quickLift.");
for(hv = hubVarList; hv; hv = hv->next)
{
char *hubPtr = cloneString(hv->name) + strlen("quickLift.");
char *db = strchr(hubPtr, '.');
*db++ = 0;
unsigned hubId = sqlUnsigned(hubPtr);
char *quickLiftChainId = cloneString(hv->val);
if (hubId == oldHubId)
{
cartRemove(cart, hv->name);
safef(buffer, sizeof buffer, "%s%d.%s", "quickLift.", newHubId,db);
cartSetString(cart, buffer, quickLiftChainId);
}
}
// 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 cartCopyLocalHubs(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)
{
// we probably shouldn't be doing this until the user actually makes a change in the collection
if (startsWith(customCompositeCartName, el->name))
copyLocalHubs(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.");
char *queryTempl = "SELECT shared, contents FROM %s WHERE userName = '%s' AND sessionName = '%s';";
sqlSafef(query, sizeof(query), queryTempl, namedSessionTable, encSessionOwner, encSessionName);
sr = sqlGetResult(conn, query);
row = sqlNextRow(sr);
// try alternative namedSessionDb tables if no result. Stop on first match.
struct slName *namedSessionAlts = cfgValsWithPrefix("namedSessionAlt.");
if (namedSessionAlts && row == NULL)
{
for (struct slName *sl = namedSessionAlts; sl != NULL; sl = sl->next)
{
char *namedSessionAlt = sl->name;
sqlFreeResult(&sr);
sqlSafef(query, sizeof(query), queryTempl, namedSessionAlt, encSessionOwner, encSessionName);
sr = sqlGetResult(conn, query);
row = sqlNextRow(sr);
if (row!=NULL)
break;
}
}
if (row != 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, " %d valid settings found. ", stats->validCount);
if (stats->binaryCount || stats->weirdCharsCount || stats->dataCount ||
stats->varTooLongCount || stats->valTooLongCount)
dyStringPrintf(dyMessage, "Note: invalid settings were found and omitted. ");
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. ",
atLeast, stats->weirdCharsCount);
if (stats->dataCount)
dyStringPrintf(dyMessage, "%s%d lines appeared to be custom track data.",
atLeast, stats->dataCount);
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);
safef(buffer, sizeof buffer, "quickLift.%s", id);
cartRemovePrefix(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);
// remove any visibilities in the cart
safef(buffer, sizeof buffer, "hub_%s", id);
cartRemovePrefix(cart, buffer);
+
+ // if the current db belongs to this hub, reset to a valid default
+ char *db = cartOptionalString(cart, "db");
+ safef(buffer, sizeof buffer, "hub_%s_", id);
+ if (db != NULL && startsWith(buffer, db))
+ {
+ cartSetString(cart, "db", hDefaultDb());
+ cartRemove(cart, "org");
+ cartRemove(cart, "genome");
+ }
}
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);
}
static void fixUpDb(struct cart *cart)
// we want to load Genark hubs or error out if db is not available
{
char *db = cartOptionalString(cart,"db");
if ((db == NULL) || startsWith("hub_", db) || sameString("0", db))
return;
else
{
char *url = genarkUrl(db);
if (url != NULL)
{
cartSetString(cart, "genome", db);
cartAddString(cart, "hubUrl", url);
cartRemove(cart, "db");
}
else if (!hDbIsActive(db))
errAbort("Can not find database '%s'", db);
}
}
boolean isValidToken(char *token)
/* send https req to cloudflare, check if the token that we got from the captcha is really the one made by cloudflare */
{
char *url = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
char *secret = cfgVal("cloudFlareSecretKey");
if (!secret)
errAbort("'cloudFlareSecretKey' must be set in hg.conf if cloudflare is activated in hg.conf");
char data[3000]; // cloudflare token is at most 2000 bytes
safef(data, sizeof(data), "secret=%s&response=%s", secret, token);
char *reply = curlPostUrl(url, data);
boolean res = strstr(reply, "\"success\":true") != NULL;
freez(&reply);
return res;
}
// hg.conf key with the cloud flare secret key, used twice here, so a global macro
#define CLOUDFLARESITEKEY "cloudFlareSiteKey"
void printCaptcha()
/* print an html page that shows the captcha and on success, reloads the page with the token added as token=x */
{
char *cfSiteKey = cfgVal(CLOUDFLARESITEKEY);
if (!cfSiteKey)
return;
puts("Content-Type:text/html\n"); // puts outputs one newline. Header requires two newlines.
puts("
");
puts("");
puts("");
puts("\n");
puts("
The Genome Browser is protecting itself from bots. This will just take a few seconds.
");
puts("To make programmatic queries, see our FAQ: https://genome.ucsc.edu/FAQ/FAQdownloads.html#CAPTCHA.");
puts("");
puts("");
puts("");
puts("");
sqlCleanupAll(); // we are wondering about hanging connections, so just in case, close them.
exit(0);
}
static boolean isUserAgentException()
/* return true if HTTP user-agent is in list of exceptions in hg.conf */
{
char *agent = cgiUserAgent();
if (!agent)
return FALSE;
struct slName *excStrs = cfgValsWithPrefix("noCaptchaAgent.");
if (!excStrs)
return FALSE;
struct excReStr;
for (struct slName *sl = excStrs; sl != NULL; sl = sl->next)
{
if (regexMatch(agent, sl->name))
{
fprintf(stderr, "CAPTCHAPASS %s matches %s\n", agent, sl->name);
return TRUE;
}
}
return FALSE;
}
void forceUserIdOrCaptcha(struct cart* cart, char *userId, boolean userIdFound, boolean fromCommandLine)
/* print captcha if user did not sent a valid hguid cookie or a valid
* cloudflare token. Allow certain IPs and user-agents. */
{
static boolean captchaCheckDone = FALSE;
// No need to do this again. Can happen if cartNew() is called somewhere else in a CGI
if (captchaCheckDone)
return;
captchaCheckDone = TRUE;
if (fromCommandLine || isEmpty(cfgOption(CLOUDFLARESITEKEY)))
return;
// no captcha for our own QA scripts running on a server with our IP address
if (botException())
return;
// certain user agents are allowed to use the website without a captcha
if (isUserAgentException())
return;
char *cgi = cgiScriptName();
// An apiKey can always be used to get around the captcha. Note that bottlenecking is then done on the level
// of the apiKey, if a valid apiKey has been supplied, see botDelay.c, so the check if the apiKey is valid is assumed
// to have been done at the bottleneck step, which in all our CGIs is called before the cart is initialized.
char *apiKey = cgiOptionalString("apiKey");
if (apiKey)
{
fprintf(stderr, "CAPTCHAPASS_APIKEY %s %s\n", apiKey, cgi);
return;
}
// hgRenderTracks should not show the captcha - it was made to be used from other websites
// For hgSession, we redirect from euro and asia to the RR - avoid showing the captcha there
// hgLogin is the redirect target for hgSession, so avoid it there as well
if ( sameWord(cgi, "/cgi-bin/hgRenderTracks") || sameWord(cgi, "/cgi-bin/hgSession") || sameWord(cgi, "/cgi-bin/hgLogin") )
return;
// Do not show a captcha if we have a valid cookie
// but for debugging, it's nice to be able to force the captcha
if (userId && userIdFound && !cgiOptionalString("captcha"))
return;
// when the captcha is solved, our JS code does a full page-reload, no AJAX. That saves us one round-trip.
// After the reload, the new page URL has the captcha token in the URL argument list, so now we need to validate it
// and remove it from the cart
char *token = cgiOptionalString("token");
if (token)
{
if (isValidToken(token))
{
cartRemove(cart, "token");
return;
}
else
{
puts("Content-Type: text/html\n");
puts("Internal captcha error: Cloudflare rejected the captcha token. "
"Something is not working internally, we are very sorry. You can try reloading the page. "
"If this problem persists, send an email to genome-www@soe.ucsc.edu and we will "
"look into it as quickly as we can in the PST timezone. You can use any internet browser "
"where you have used the genome browser before, but not from this internet browser. "
"You can try our mirror sites, "
"genome-euro.ucsc.edu or genome-asia.ucsc.edu, while we are working on a solution.");
exit(0);
}
}
printCaptcha();
}
void cartRemove(struct cart *cart, char *var);
static void genericCgiSetup()
/* Run steps that all CGIs must do that unrelated to the cart: timeout, logging setup, UDC.
*/
{
static boolean genericSetupDone = FALSE;
// do this only once per execution
if (genericSetupDone)
return;
genericSetupDone = TRUE;
cgiApoptosisSetup();
setUdcCacheDir();
netSetTimeoutErrorMsg("Connection timeout: either the server is offline or any firewall between UCSC and the server blocks the connection.");
}
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 */
{
genericCgiSetup();
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);
boolean fromCli = cgiWasSpoofed(); // QA runs our CGIs from the command line and we debug from there
forceUserIdOrCaptcha(cart, userId, userIdFound, fromCli);
// we rely on the cookie being validated, so if we reset a cookie, do this after the captcha
if ( cgiOptionalString("ignoreCookie") != NULL )
cart->userInfo = loadDb(conn, userDbTable(), NULL, &userIdFound);
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);
fixUpDb(cart); // now is the time to see if someone is loading a Genark hub or specified a bad database.
// 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)
cartCopyLocalHubs(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;
}
boolean cartVarExistsLike(struct cart *cart, char *wildCard)
/* Return TRUE if a variable matching the wildcard is in the cart. */
{
return hashItemExistsLike(cart->hash, wildCard);
}
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; itable[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("
%s
", var);
int width=(strlen(val)+1)*8;
if (width<100)
width = 100;
cgiMakeTextVarWithJs(hel->name, val, width,
"change", "setCartVar(this.name,this.value);");
printf("
\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("
\n");
for (el = elList; el != NULL; el = el->next)
cartDumpItem(el, asTable, encodeAsHtml);
if (asTable)
{
printf("
count: %d
\n",slCount(elList));
printf("
\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);
}
static void dumpCartWithTime(struct cart *cart, char *timeStr)
/* Dump out the current cart to trash named by how long a page draw took using it. */
{
char prefix[128];
// zero pad so the files will alphanumerically sort by elapsed time
sprintf(prefix, "%06d.", atoi(timeStr));
// make a temp file in trash
struct tempName hubTn;
trashDirDateFile(&hubTn, "cartDumps", prefix , ".txt");
char *dumpName = cloneString(hubTn.forCgi);
// write out the cart into the tempfile
FILE *f = mustOpen(dumpName, "w");
struct hashEl *el;
struct hashEl *elList = hashElListHash(cart->hash);
for (el = elList; el != NULL; el = el->next)
fprintf(f, "%s %s\n", el->name, (char *)el->val);
fclose(f);
// put something in the log to say we did this
fprintf(stderr, "warnTiming %s time=%s skipNotif=%s\n", getSessionId(), timeStr, cartUsualString(cart, "skipNotif", "null"));
}
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);
char *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);
char *timeStr;
if ( (timeStr = cgiOptionalString("_dumpCart")) != NULL)
{
dumpCartWithTime(cart, timeStr);
exit(0);
}
// activate optional debuging output for CGIs
verboseCgi(cgiUsualString("verbose", NULL));
cartExclude(cart, "verbose");
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);
}
}
void cartWriteHeaderAndCont(struct cart* cart, char *cookieName, char *contType)
/* write http headers including cookie and content type line.
* contType defaults to text/html when NULL.
* cookieName defaults to hUserCookie() when NULL */
{
if (!contType)
contType = "text/html";
if (!cookieName)
cookieName = hUserCookie();
addHttpHeaders();
cartWriteCookie(cart, cookieName);
printf("Content-Type: %s\n\n", contType);
cartDidContentType = TRUE;
}
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();
if (doContentType && !cartDidContentType)
cartWriteHeaderAndCont(cart, cookieName, NULL);
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 cartWebEndExtra(char *footer)
/* Write out HTML footer and get rid or error handler, with extra text
* at the end of the page as desired.
*/
{
webEndExtra(footer);
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.=[,]
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), "", 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);
}
}
static void cartGenericStartup(struct cart *cart)
/* generic startup code to initialize settings from the cart when using either -WithHead or -MaybeContent cartShell functions */
{
setThemeFromCart(cart);
googleAnalyticsSetGa4Key();
int verbose = cgiOptionalInt("verbose", -1);
if (verbose != -1)
verboseSetLevel(verbose);
}
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();
cartGenericStartup(cart);
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);
cartGenericStartup(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", trackName,suffix);
else
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_ 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,
"
Note: the session has at least one active custom "
"track (in database ");
for (sln = liveDbList; sln != NULL; sln = sln->next)
dyStringPrintf(dyMessage, "%s%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,
"
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,
"
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; "
"please keep a local backup of your sessions contents "
"and custom track data.
Note: the session contains BLAT results. ");
else
dyStringPrintf(dyMessage,
"
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 "
""
"expiration policy.");
}
}
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);
}
char *cartOrCfgOption(struct cart *cart, char *name)
/* Return the option with the given name. First check cart then hg.conf. Return NULL if * it doesn't exist. */
{
char *str = NULL;
if ((str = cartOptionalString(cart, name)) == NULL)
return str = cfgOption(name);
return str;
}