c393b2f6fa265761873dc9222bc8ba83015b3d5d
mspeir
Fri Jun 3 13:04:17 2016 -0700
Custom tracks in sessions no longer expire. Changing cart message when session with custom tracks is saved. Reminds users to keep a local back-up of their session contents and custom tracks, refs #14724
diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index a16e65b..08aca5a 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -1,2915 +1,2916 @@
/* Copyright (C) 2014 The Regents of the University of California
* See README in this or parent directory 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 "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"
static char *sessionVar = "hgsid"; /* Name of cgi variable session is stored in. */
static char *positionCgiName = "position";
DbConnector cartDefaultConnector = hConnectCart;
DbDisconnect cartDefaultDisconnector = hDisconnectCart;
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 = newDyString(100);
dyStringPrintf(hubWarnDy, "%s\n", warning);
}
void cartFlushHubWarnings()
/* flush the hub warning (if any) */
{
if (hubWarnDy)
warn("%s",hubWarnDy->string);
}
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, "ASH: %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, "ASH: 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, "ASH: 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, "ASH: cartTablesOk failed on %s.%s! pid=%ld\n",
sqlGetDatabase(conn), userDbTable(), (long)getpid());
return FALSE;
}
if (!sqlTableExists(conn, sessionDbTable()))
{
fprintf(stderr, "ASH: cartTablesOk failed on %s.%s! pid=%ld\n",
sqlGetDatabase(conn), sessionDbTable(), (long)getpid());
return FALSE;
}
return TRUE;
}
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). */
{
struct hash *hash = cart->hash;
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;
}
}
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);
sqlDyStringPrintfFrag(where, "id = %u", id);
if (cartDbUseSessionKey())
{
if (!sessionKey)
sessionKey = "";
sqlDyStringPrintfFrag(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;
}
}
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 = cartDbMakeRandomKey(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);
}
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);
}
#ifndef GBROWSE
void cartCopyCustomTracks(struct cart *cart)
/* If cart contains any live custom tracks, save off a new copy of them,
* to prevent clashes by multiple uses of the same session. */
{
struct hashEl *el, *elList = hashElListHash(cart->hash);
for (el = elList; el != NULL; el = el->next)
{
if (startsWith(CT_FILE_VAR_PREFIX, el->name))
{
char *db = &el->name[strlen(CT_FILE_VAR_PREFIX)];
struct slName *browserLines = NULL;
struct customTrack *ctList = NULL;
char *ctFileName = (char *)(el->val);
if (fileExists(ctFileName))
ctList = customFactoryParseAnyDb(db, ctFileName, TRUE, &browserLines);
/* Save off only if the custom tracks are live -- if none are live,
* leave cart variables in place so hgSession can detect and inform
* the user. */
if (ctList)
{
struct customTrack *ct;
static struct tempName tn;
char *ctFileVar = el->name;
char *ctFileName;
for (ct = ctList; ct != NULL; ct = ct->next)
{
copyFileToTrash(&(ct->htmlFile), "ct", CT_PREFIX, ".html");
copyFileToTrash(&(ct->wibFile), "ct", CT_PREFIX, ".wib");
copyFileToTrash(&(ct->wigFile), "ct", CT_PREFIX, ".wig");
}
trashDirFile(&tn, "ct", CT_PREFIX, ".bed");
ctFileName = tn.forCgi;
cartSetString(cart, ctFileVar, ctFileName);
customTracksSaveFile(db, ctList, ctFileName);
}
}
}
}
#endif /* GBROWSE */
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 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. */
{
struct sqlResult *sr = NULL;
char **row = NULL;
/* Validate login cookies if login is enabled */
if (loginSystemEnabled())
{
loginValidateCookies(cart);
}
char *userName = wikiLinkUserName();
char *encSessionName = cgiEncodeFull(sessionName);
char *encSessionOwner = cgiEncodeFull(sessionOwner);
char query[512];
if (isEmpty(sessionOwner))
errAbort("Please go back and enter a wiki user name for this session.");
if (isEmpty(sessionName))
errAbort("Please go back and enter a session name to load.");
sqlSafef(query, sizeof(query), "SELECT shared, contents FROM %s "
"WHERE userName = '%s' AND sessionName = '%s';",
namedSessionTable, encSessionOwner, encSessionName);
sr = sqlGetResult(conn, query);
if ((row = sqlNextRow(sr)) != NULL)
{
boolean shared = atoi(row[0]);
if (shared ||
(userName && sameString(sessionOwner, userName)))
{
char *sessionVar = cartSessionVarName();
char *hgsid = cartSessionId(cart);
struct sqlConnection *conn2 = hConnectCentral();
sessionTouchLastUse(conn2, encSessionOwner, encSessionName);
cartRemoveLike(cart, "*");
cartParseOverHash(cart, row[1]);
cartSetString(cart, sessionVar, hgsid);
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);
}
#endif /* GBROWSE */
void cartLoadSettings(struct lineFile *lf, struct cart *cart,
struct hash *oldVars, char *actionVar)
/* Load settings (cartDump output) 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. */
{
char *line = NULL;
int size = 0;
char *sessionVar = cartSessionVarName();
char *hgsid = cartSessionId(cart);
cartRemoveLike(cart, "*");
cartSetString(cart, sessionVar, hgsid);
while (lineFileNext(lf, &line, &size))
{
char *var = nextWord(&line);
if (isEmpty(var))
// blank line
continue;
char *val = line;
if (sameString(var, sessionVar))
continue;
else
{
if (val != NULL)
{
struct dyString *dy = dyStringSub(val, "\\n", "\n");
cartAddString(cart, var, dy->string);
dyStringFree(&dy);
}
else if (var != NULL)
{
cartSetString(cart, var, "");
}
} /* not hgsid */
} /* each line */
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);
}
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);
}
cartRemove(cart, "hubId");
cartRemove(cart, hgHubDoDisconnect);
}
static void hideIfNotInCart(struct cart *cart, char *track)
/* If this track is not mentioned in the cart, set it to hide */
{
if (cartOptionalString(cart, track) == NULL)
cartSetString(cart, track, "hide");
}
void cartHideDefaultTracks(struct cart *cart)
/* Hide all the tracks who have default visibilities in trackDb
* that are something other than hide. Do this only if the
* variable CART_HAS_DEFAULT_VISIBILITY is set in the cart. */
{
char *defaultString = cartOptionalString(cart, CART_HAS_DEFAULT_VISIBILITY);
boolean cartHasDefaults = (defaultString != NULL) && sameString(defaultString, "on");
if (!cartHasDefaults)
return;
char *db = cartString(cart, "db");
struct trackDb *tdb = hTrackDb(db);
for(; tdb; tdb = tdb->next)
{
struct trackDb *parent = tdb->parent;
if (parent && parent->isShow)
hideIfNotInCart(cart, parent->track);
if (tdb->visibility != tvHide)
hideIfNotInCart(cart, tdb->track);
}
// Don't do this again until someone sets this variable,
// presumably on session load.
cartRemove(cart, CART_HAS_DEFAULT_VISIBILITY);
}
struct cart *cartNew(char *userId, char *sessionId,
char **exclude, struct hash *oldVars)
/* Load up cart from user & session id's. Exclude is a null-terminated list of
* strings to not include */
{
cgiApoptosisSetup();
if (cfgOptionBooleanDefault("showEarlyErrors", FALSE))
errAbortSetDoContentType(TRUE);
if (cfgOptionBooleanDefault("suppressVeryEarlyErrors", FALSE))
htmlSuppressErrors();
struct cart *cart;
struct sqlConnection *conn = cartDefaultConnector();
char *ex;
boolean userIdFound = FALSE, sessionIdFound = FALSE;
AllocVar(cart);
cart->hash = newHash(12);
cart->exclude = newHash(7);
cart->userId = userId;
cart->sessionId = sessionId;
cart->userInfo = loadDb(conn, userDbTable(), userId, &userIdFound);
cart->sessionInfo = loadDb(conn, sessionDbTable(), sessionId, &sessionIdFound);
if (sessionIdFound)
cartParseOverHash(cart, cart->sessionInfo->contents);
else if (userIdFound)
cartParseOverHash(cart, cart->userInfo->contents);
char when[1024];
safef(when, sizeof(when), "open %s %s", userId, sessionId);
cartTrace(cart, when, conn);
loadCgiOverHash(cart, oldVars);
// I think this is the place to justify old and new values
cartJustify(cart, oldVars);
#ifndef GBROWSE
/* If some CGI other than hgSession been passed hgSession loading instructions,
* apply those to cart before we do anything else. (If this is hgSession,
* let it handle the settings so it can display feedback to the user.) */
boolean didSessionLoad = FALSE;
if (! (cgiScriptName() && endsWith(cgiScriptName(), "hgSession")))
{
if (cartVarExists(cart, hgsDoOtherUser))
{
setUdcCacheDir();
char *otherUser = cartString(cart, hgsOtherUserName);
char *sessionName = cartString(cart, hgsOtherUserSessionName);
struct sqlConnection *conn2 = hConnectCentral();
cartLoadUserSession(conn2, otherUser, sessionName, cart,
oldVars, hgsDoOtherUser);
hDisconnectCentral(&conn2);
cartTrace(cart, "after cartLUS", conn);
didSessionLoad = TRUE;
}
else if (cartVarExists(cart, hgsDoLoadUrl))
{
setUdcCacheDir();
char *url = cartString(cart, hgsLoadUrlName);
struct lineFile *lf = netLineFileOpen(url);
cartLoadSettings(lf, cart, oldVars, hgsDoLoadUrl);
lineFileClose(&lf);
cartTrace(cart, "after cartLS", conn);
didSessionLoad = TRUE;
}
}
#endif /* GBROWSE */
/* wire up the assembly hubs so we can operate without sql */
setUdcTimeout(cart);
if (cartVarExists(cart, hgHubDoDisconnect))
doDisconnectHub(cart);
pushWarnHandler(cartHubWarn);
char *newDatabase = hubConnectLoadHubs(cart);
popWarnHandler();
#ifndef GBROWSE
if (didSessionLoad)
cartCopyCustomTracks(cart);
#endif /* GBROWSE */
if (newDatabase != NULL)
{
// 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 = newDyString(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 = newDyString(4096);
/* Make up encoded string holding all variables. */
cartEncodeState(cart, encoded);
/* Make up update statement unless it looks like a robot with
* a great bunch of variables. */
if (encoded->stringSize < 16*1024 || cart->userInfo->useCount > 0)
{
updateOne(conn, userDbTable(), cart->userInfo, encoded->string, encoded->stringSize);
updateOne(conn, sessionDbTable(), cart->sessionInfo, encoded->string, encoded->stringSize);
}
else
{
fprintf(stderr, "Cart stuffing bot? Not writing %d bytes to cart on first use of %d from IP=%s\n",
encoded->stringSize, cart->userInfo->id, cgiRemoteAddr());
/* Do increment the useCount so that cookie-users don't get stuck here: */
updateOne(conn, userDbTable(), cart->userInfo, "", 0);
}
/* 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);
}
}
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;
}
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;
}
static char *cartMultShadowVar(struct cart *cart, char *var)
/* Return a pointer to the list variable shadow variable name for var.
* Don't modify or free result. */
{
static char multShadowVar[PATH_LEN];
safef(multShadowVar, sizeof(multShadowVar), "%s%s", cgiMultListShadowPrefix(), var);
return multShadowVar;
}
static int cartRemoveAndCountNoShadow(struct cart *cart, char *var)
/* Remove variable from cart, returning count of removed vars. */
{
int removed = 0;
struct hashEl *hel = hashLookup(cart->hash, var);
while (hel != NULL)
{
struct hashEl *nextHel = hashLookupNext(hel);
freez(&hel->val);
hashRemove(cart->hash, var);
removed++;
hel = nextHel;
}
return removed;
}
static int cartRemoveAndCount(struct cart *cart, char *var)
/* Remove variable from cart, returning count of removed vars. */
{
int removed = cartRemoveAndCountNoShadow(cart, var);
(void)cartRemoveAndCountNoShadow(cart, cartMultShadowVar(cart, var));
return removed;
}
void cartRemove(struct cart *cart, char *var)
/* Remove variable from cart. */
{
(void)cartRemoveAndCount(cart, var);
}
void cartRemoveExcept(struct cart *cart, char **except)
/* Remove variables except those in null terminated except array
* from cart. Except array may be NULL in which case all
* are removed. */
{
struct hash *exceptHash = newHash(10);
struct hashEl *list = NULL, *el;
char *s;
/* Build up hash of things to exclude. */
if (except != NULL)
{
while ((s = *except++) != NULL)
hashAdd(exceptHash, s, NULL);
}
/* Get all cart variables and remove most of them. */
list = hashElListHash(cart->hash);
for (el = list; el != NULL; el = el->next)
{
if (!hashLookup(exceptHash, el->name))
cartRemove(cart, el->name);
}
/* Clean up. */
hashFree(&exceptHash);
hashElFreeList(&list);
}
struct slPair *cartVarsLike(struct cart *cart, char *wildCard)
/* Return a slPair list of cart vars that match the wildcard */
{
struct slPair *cartVars = NULL;
struct hashEl *el, *elList = hashElListHash(cart->hash);
slSort(&elList, hashElCmp);
for (el = elList; el != NULL; el = el->next)
{
if (wildMatch(wildCard, el->name))
slAddHead(&cartVars,slPairNew(el->name,el->val));
}
hashElFreeList(&elList);
return cartVars;
}
struct slPair *cartVarsWithPrefix(struct cart *cart, char *prefix)
/* Return a slPair list of cart vars that begin with prefix */
{
struct slPair *cartVars = NULL;
struct hashEl *el, *elList = hashElListHash(cart->hash);
slSort(&elList, hashElCmp);
for (el = elList; el != NULL; el = el->next)
{
if (startsWith(prefix, el->name))
slAddHead(&cartVars,slPairNew(el->name,el->val));
}
hashElFreeList(&elList);
return cartVars;
}
struct slPair *cartVarsWithPrefixLm(struct cart *cart, char *prefix, struct lm *lm)
/* Return list of cart vars that begin with prefix allocated in local memory.
* Quite a lot faster than cartVarsWithPrefix. */
{
struct slPair *cartVars = NULL;
struct hash *hash = cart->hash;
int hashSize = hash->size;
struct hashEl *hel;
int i;
for (i=0; i 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. ");
}
dyStringPrintf(dyMessage,
- "Custom tracks are subject to an expiration policy described in the "
- ""
- "Session documentation. \n");
}
else
printf("%s %s\n", var, val);
freeMem(var);
freeMem(val);
}
void cartDumpList(struct hashEl *elList,boolean asTable)
/* Dump list of cart variables optionally as a table with ajax update support. */
{
struct hashEl *el;
if (elList == NULL)
return;
slSort(&elList, hashElCmp);
if (asTable)
printf("%s ", var);
int width=(strlen(val)+1)*8;
if (width<100)
width = 100;
cgiMakeTextVarWithExtraHtml(hel->name, val, width,
"onchange='setCartVar(this.name,this.value);'");
printf(" \n");
for (el = elList; el != NULL; el = el->next)
cartDumpItem(el,asTable);
if (asTable)
{
printf("
\n");
}
hashElFreeList(&elList);
}
void cartDump(struct cart *cart)
/* Dump contents of cart. */
{
struct hashEl *elList = hashElListHash(cart->hash);
cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE));
}
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));
}
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));
}
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);
sqlDyStringPrintf(query, "update %s set contents='' where id=%u", table, 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);
}
}
struct cart *cartForSession(char *cookieName, char **exclude,
struct hash *oldVars)
/* This gets the cart without writing any HTTP lines at all to stdout. */
{
char *hguid = getCookieId(cookieName);
char *hgsid = getSessionId();
struct cart *cart = cartNew(hguid, hgsid, exclude, oldVars);
cartExclude(cart, sessionVar);
if (sameOk(cfgOption("signalsHandler"), "on")) /* most cgis call this routine */
initSigHandlers(hDumpStackEnabled());
char *httpProxy = cfgOption("httpProxy"); /* most cgis call this routine */
if (httpProxy)
setenv("http_proxy", httpProxy, TRUE); /* net.c cannot see the cart, pass the value through env var */
return cart;
}
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. */
{
if (doContentType)
htmlPushEarlyHandlers();
else
pushWarnHandler(cartEarlyWarningHandler);
struct cart *cart = cartForSession(cookieName, exclude, oldVars);
popWarnHandler();
cartWriteCookie(cart, cookieName);
if (doContentType)
{
puts("Content-Type:text/html");
puts("\n");
}
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. */
{
int status = setjmp(htmlRecover);
pushAbortHandler(htmlAbort);
hDumpStackPushAbortHandler();
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)
{
htmStart(stdout, "Early Error");
initted = TRUE;
}
printf("%s", htmlWarnStartPattern());
htmlVaParagraph(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;
}
void cartVaWebStart(struct cart *cart, char *db, char *format, va_list args)
/* Print out pretty wrapper around things when working
* from cart. */
{
pushWarnHandler(htmlVaWarn);
webStartWrapper(cart, trackHubSkipHubName(db), format, args, FALSE, FALSE);
inWeb = TRUE;
}
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);
jsIncludeFile("jquery.js", NULL);
jsIncludeFile("utils.js", NULL);
jsIncludeFile("ajax.js", NULL);
// WTF - variable outside of a form on almost every page we make below?
// 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 cartWebEnd()
/* Write out HTML footer and get rid or error handler. */
{
webEnd();
popWarnHandler();
inWeb = FALSE;
}
void cartFooter(void)
/* Write out HTML footer, possibly with googleAnalytics too */
{
#ifndef GBROWSE
googleAnalytics(); /* can't do this in htmlEnd */
#endif /* GBROWSE */
htmlEnd(); /* because it is in a higher library */
}
void cartHtmlEnd()
/* Write out HTML footer and get rid or error handler. */
{
if (inWeb)
webEnd(); /* this does googleAnalytics for a lot of CGIs */
else if (didCartHtmlStart)
cartFooter();
else
return;
popWarnHandler();
}
void setThemeFromCart(struct cart *cart)
/* If 'theme' variable is set in cart: overwrite background with the one from
* defined for this theme Also set the "styleTheme", with additional styles
* that can overwrite the main style settings */
{
// Get theme from cart and use it to get background file from config;
// format is browser.theme. \n",slCount(elList));
printf(" count: %d
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); if (sameOk(cgiOptionalString("position"), "lastDbPos")) { char *dbLocalPosContent = cartUsualString(cart, dbPosKey, NULL); if (dbLocalPosContent) { //warn("dbLocalPosContent=%s",dbLocalPosContent); // DEBUG REMOVE if (strchr(dbLocalPosContent, '=')) { gotCart = TRUE; cartParseOverHash(lastDbPosCart, cloneString(dbLocalPosContent)); // this function chews up input position = cloneString(cartUsualString(lastDbPosCart, "position", NULL)); //warn("gotCart position=%s",position); // DEBUG REMOVE // DEBUG REMOVE: //struct dyString *dbPosValue = newDyString(4096); //cartEncodeState(lastDbPosCart, dbPosValue); //warn("gotCart dbPosValue->string=[%s]",dbPosValue->string); } else { position = dbLocalPosContent; // old style value } } else { position = defaultPosition; // no value was set } } if (position == NULL) { position = 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 = newDyString(4096); cartEncodeState(lastDbPosCart, dbPosValue); cartSetString(cart, dbPosKey, dbPosValue->string); }