cb99f0b11bdeee5dfa76064d38b6410da0f4a709
max
Thu Sep 10 00:55:21 2026 -0700
Centralize CGI Content-Type printing in one cgiPrintContentType() helper
Around 90 places across the tree hand-rolled the CGI response header, each
with its own spelling: "Content-Type:" or "Content-type:", \n or \r\n, and
the terminating blank line written as part of the same string, as a separate
puts("\n") (which emits two newlines, so a stray blank line led the body) or
as printf("\r\n\r\n") (two blank lines). A handful forgot the terminator
entirely and relied on a following header to supply it.
cgiPrintContentType() in lib/cheapcgi.c now writes the Content-Type line and
the blank line that ends the header. Header lines are not ordered, so the
callers that also send Status, Set-Cookie, Content-Disposition, Content-Length
or X-Sendfile write those first and call this last to close the header; that
keeps it to a single helper rather than a print-the-line / end-the-header pair
that a caller can half-use. cart.c's existing httpHeaders list already worked
this way.
Only the CGI response path is touched. The dyStringPrintf("Content-type: ...")
calls that build outgoing HTTP *requests* (genomeSpace, oauthLogin, eapMetaSync,
edwWebAuthLogin, ga4ghToBed) are unrelated and left alone.
Also fills out the apiKey error message in botDelay.c to say where to create a
key and that keys are server-specific.
No behavior change on the wire beyond dropping those stray blank lines and
adding the missing newline after Retry-After.
diff --git src/hg/cgilib/cartJson.c src/hg/cgilib/cartJson.c
index 0bf5c2f92a0..43a6adc726e 100644
--- src/hg/cgilib/cartJson.c
+++ src/hg/cgilib/cartJson.c
@@ -1,997 +1,997 @@
/* cartJson - parse and execute JSON commands to update cart and/or return cart data as JSON. */
#include "common.h"
#include "cartJson.h"
#include "cartTrackDb.h"
#include "cheapcgi.h"
#include "errCatch.h"
#include "grp.h"
#include "hdb.h"
#include "hgFind.h"
#include "htmlSanitize.h"
#include "htmshell.h"
#include "hubConnect.h"
#include "hui.h"
#include "jsonParse.h"
#include "obscure.h"
#include "regexHelper.h"
#include "suggest.h"
#include "trackDb.h"
#include "trackHub.h"
#include "web.h"
char *cartJsonOptionalParam(struct hash *paramHash, char *name)
/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
* Return the string contained in its jsonElement value, or NULL if not found. */
{
struct jsonElement *jel = hashFindVal(paramHash, name);
if (jel)
return jsonStringVal(jel, name);
return NULL;
}
char *cartJsonParamDefault(struct hash *paramHash, char *name, char *defaultVal)
/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
* Return the string contained in its jsonElement value, or defaultVal if not found. */
{
struct jsonElement *jel = hashFindVal(paramHash, name);
if (jel)
return jsonStringVal(jel, name);
return defaultVal;
}
char *cartJsonRequiredParam(struct hash *paramHash, char *name, struct jsonWrite *jw, char *context)
/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
* If found, return the string contained in its jsonElement value.
* If not, write an error message (using context) and return NULL. */
{
char *param = cartJsonOptionalParam(paramHash, name);
if (param == NULL)
jsonWriteStringf(jw, "error",
"%s: required param %s is missing", context, name);
return param;
}
static char *stripAnchor(char *textIn)
/* If textIn contains an HTML anchor tag, strip it out (and its end tag). */
{
regmatch_t matches[3];
if (regexMatchSubstrNoCase(textIn, "<a href[^>]+>", matches, ArraySize(matches)))
{
char *textOut = cloneString(textIn);
memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
strlen(textOut) + 1 - matches[0].rm_eo);
if (regexMatchSubstrNoCase(textOut, "</a>", matches, ArraySize(matches)))
memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
strlen(textOut) + 1 - matches[0].rm_eo);
return textOut;
}
return textIn;
}
void hgPositionsJson(struct jsonWrite *jw, char *db, struct hgPositions *hgp, struct cart *cart)
/* Write out JSON description of multiple position matches. */
{
struct hgPosTable *table;
jsonWriteListStart(jw, "positionMatches");
struct trackDb *tdbList = NULL;
for (table = hgp->tableList; table != NULL; table = table->next)
{
if (table->posList != NULL)
{
char *trackName = table->name, *tableName = table->name;
struct trackDb *tdb = NULL;
// these are pseudo-table names with no trackDb entry to look up. chromInfo is what
// hgFind uses for a plain position range or a genomic HGVS match
if (! (sameString("trackDb", tableName) || sameString("helpDocs", tableName) ||
sameString("publicHubs", tableName) || sameString("chromInfo", tableName)))
{
// a native tdbList carried over from an earlier table won't hold hub tracks,
// drop it so tdbForTrack takes its hub lookup path
if (isHubTrack(tableName))
tdbList = NULL;
tdb = tdbForTrack(db, tableName, &tdbList);
if (!tdb && startsWith("all_", tableName))
tdb = tdbForTrack(db, tableName+strlen("all_"), &tdbList);
if (!tdb && startsWith("xeno", tableName))
{
// due to genbank track changes over the years, sometimes tables
// get left on different servers when their trackDb entry was removed
// long ago. In that case skip those hits
continue;
}
if (!tdb)
errAbort("no track for table \"%s\" found via a findSpec", tableName);
trackName = tdb->track;
}
jsonWriteObjectStart(jw, NULL);
jsonWriteString(jw, "name", table->name);
jsonWriteString(jw, "trackName", trackName);
jsonWriteString(jw, "description", table->description);
if (tdb != NULL)
jsonWriteString(jw, "vis", hCarefulTrackOpenVis(db, trackName));
jsonWriteListStart(jw, "matches");
struct hgPos *pos;
for (pos = table->posList; pos != NULL; pos = pos->next)
{
char *encMatches = cgiEncode(pos->browserName);
jsonWriteObjectStart(jw, NULL); // begin one match
if (pos->chrom != NULL)
jsonWriteStringf(jw, "position", "%s:%d-%d",
pos->chrom, pos->chromStart+1, pos->chromEnd);
else
// GenBank results set position to GB accession instead of chr:s-e position.
jsonWriteString(jw, "position", pos->name);
// this is magic to tell the browser to make the
// composite and this subTrack visible
if (tdb && tdb->parent)
{
if (tdbIsSuperTrackChild(tdb))
jsonWriteStringf(jw, "extraSel", "%s=show&", tdb->parent->track);
else
{
// tdb is a subtrack of a composite or a view
jsonWriteStringf(jw, "extraSel", "%s_sel=1&%s_sel=1&",
trackName, tdb->parent->track);
}
}
jsonWriteString(jw, "hgFindMatches", encMatches);
jsonWriteString(jw, "posName", htmlEncode(pos->name));
jsonWriteString(jw, "highlight", pos->highlight);
jsonWriteBoolean(jw, "canonical", pos->canonical);
if (pos->description)
{
stripString(pos->description, "\n");
jsonWriteString(jw, "description", stripAnchor(pos->description));
}
jsonWriteObjectEnd(jw); // end one match
}
jsonWriteListEnd(jw); // end matches
if (table->searchTime >= 0)
jsonWriteNumber(jw, "searchTime", table->searchTime);
jsonWriteObjectEnd(jw); // end one table
}
}
jsonWriteListEnd(jw); // end positionMatches
}
static struct hgPositions *collapseHgpList(struct hgPositions *hgpList, struct jsonWrite *jw, char *searchTerm, char *db)
/* Combine a bunch of singlePos hgps together */
{
struct hgPositions *hgp = hgpList, *final = NULL;
AllocVar(final);
final->query = searchTerm;
final->database = cloneString(hgp->database);
final->tableList = hgp->tableList;
final->posCount = hgp->posCount;
final->singlePos = hgp->singlePos;
final->extraCgi = cloneString(final->extraCgi);
final->useAlias = hgp->useAlias;
final->shortCircuited = hgp->shortCircuited;
struct dyString *newHighlight = dyStringNew(0);
dyStringPrintf(newHighlight, "%s", hgp->singlePos->highlight);
struct dyString *newPosNames = dyStringNew(0);
dyStringPrintf(newPosNames, "%s", hgp->singlePos->name);
for (hgp = hgpList->next; hgp != NULL; hgp = hgp->next)
{
if (!hgp->singlePos ||
(final && !(sameString(final->singlePos->chrom, hgp->singlePos->chrom))))
{
jsonWriteStringf(jw, "error", "collapsing hgps for hits on different chromsomes, database: '%s', searchTerm: '%s'", db, searchTerm);
return NULL;
}
else
{
dyStringPrintf(newHighlight, "|%s", hgp->singlePos->highlight);
dyStringPrintf(newPosNames, ",%s", hgp->singlePos->name);
final->posCount += hgp->posCount;
if (final->singlePos->chromStart > hgp->singlePos->chromStart)
final->singlePos->chromStart = hgp->singlePos->chromStart;
if (final->singlePos->chromEnd < hgp->singlePos->chromEnd)
final->singlePos->chromEnd = hgp->singlePos->chromEnd;
}
}
final->singlePos->highlight = dyStringCannibalize(&newHighlight);
final->singlePos->name = dyStringCannibalize(&newPosNames);
return final;
}
struct hgPositions *genomePosCJ(struct jsonWrite *jw,
char *db, char *spec, char **retChromName,
int *retWinStart, int *retWinEnd, struct cart *cart, struct searchCategory *categories, boolean categorySearch)
/* Search for positions in genome that match user query.
* Return an hgp unless there is a problem. hgp->singlePos will be set if a single
* position matched.
* Otherwise display list of positions, put # of positions in retWinStart,
* and return NULL. */
{
char *hgAppName = "cartJson";
struct hgPositions *hgp = NULL;
char *chrom = NULL;
int start = BIGNUM;
int end = 0;
boolean measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
char *terms[16];
int termCount = chopByChar(cloneString(spec), ';', terms, ArraySize(terms));
boolean multiTerm = (termCount > 1);
struct hgPositions *hgpList = NULL; // if a multiTerm search we need to collapse the hgps together
int i = 0;
for (i = 0; i < termCount; i++)
{
trimSpaces(terms[i]);
if (isEmpty(terms[i]))
continue;
hgp = hgPositionsFind(db, terms[i], "", hgAppName, cart, multiTerm, measureTiming, categories);
if (hgp == NULL || hgp->posCount == 0)
{
jsonWriteStringf(jw, "error",
"Sorry, couldn't locate %s in %s %s", htmlEncode(terms[i]),
trackHubSkipHubName(hOrganism(db)), hFreezeDate(db));
if (multiTerm)
jsonWriteStringf(jw, "error",
"%s not uniquely determined -- can't do multi-position search.",
terms[i]);
*retWinStart = 0;
return NULL;
}
if (hgp->singlePos != NULL)
{
if (!categorySearch && chrom != NULL && !sameString(chrom, hgp->singlePos->chrom))
{
jsonWriteStringf(jw, "error",
"Sites occur on different chromosomes: %s, %s.",
chrom, hgp->singlePos->chrom);
return NULL;
}
chrom = hgp->singlePos->chrom;
if (hgp->singlePos->chromStart < start)
start = hgp->singlePos->chromStart;
if (hgp->singlePos->chromEnd > end)
end = hgp->singlePos->chromEnd;
if (multiTerm)
{
slAddHead(&hgpList, hgp);
}
}
else
{
hgPositionsJson(jw, db, hgp, cart);
if (multiTerm && !categorySearch && hgp->posCount != 1)
{
jsonWriteStringf(jw, "error",
"%s not uniquely determined (%d locations) -- "
"can't do multi-position search.",
terms[i], hgp->posCount);
return NULL;
}
break;
}
}
if (hgp != NULL)
{
*retChromName = chrom;
*retWinStart = start;
*retWinEnd = end;
}
if (multiTerm && hgpList != NULL)
{
return collapseHgpList(hgpList, jw, spec, db);
}
return hgp;
}
static void changePosition(struct cartJson *cj, char *newPosition)
/* Update position in cart, after performing lookup if necessary.
* Usually we don't report what we just changed, but since we might modify it,
* print out the final value. */
{
char *db = cartString(cj->cart, "db");
char *chrom = NULL;
int start=0, end=0;
struct hgPositions *hgp = genomePosCJ(cj->jw, db, newPosition, &chrom, &start, &end, cj->cart, NULL, TRUE);
// If it resolved to a single position, update the cart; otherwise the app can
// present the error (or list of matches) to the user.
if (hgp && hgp->singlePos)
{
char newPosBuf[128];
safef(newPosBuf, sizeof(newPosBuf), "%s:%d-%d", chrom, start+1, end);
cartSetString(cj->cart, "position", newPosBuf);
jsonWriteString(cj->jw, "position", newPosBuf);
}
else
// Search failed; restore position from cart
jsonWriteString(cj->jw, "position", cartUsualString(cj->cart, "position", hDefaultPos(db)));
}
static void changePositionHandler(struct cartJson *cj, struct hash *paramHash)
/* Update position in cart, after performing lookup if necessary.
* Usually we don't report what we just changed, but since we might modify it,
* print out the final value. */
{
char *newPosition = cartJsonRequiredParam(paramHash, "newValue", cj->jw, "changePosition");
if (newPosition)
changePosition(cj, newPosition);
}
static void printGeneSuggestTrack(struct cartJson *cj, char *db)
/* Get the gene track used by hgSuggest for db (defaulting to cart db), or null if
* there is none for this assembly. */
{
if (isEmpty(db))
db = cartString(cj->cart, "db");
char *track = NULL;
if (! trackHubDatabase(db))
track = assemblyGeneSuggestTrack(db);
jsonWriteString(cj->jw, "geneSuggestTrack", track);
}
static void getGeneSuggestTrack(struct cartJson *cj, struct hash *paramHash)
/* Get the gene track used by hgSuggest for db (defaulting to cart db), or null if
* there is none for this assembly. */
{
char *db = cartJsonOptionalParam(paramHash, "db");
printGeneSuggestTrack(cj, db);
}
static void getVar(struct cartJson *cj, struct hash *paramHash)
/* Print out the requested cart var(s). varString may be a comma-separated list.
* If a var is a list variable, prints out a list of values for that var. */
{
char *varString = cartJsonRequiredParam(paramHash, "var", cj->jw, "get");
if (! varString)
return;
struct slName *varList = slNameListFromComma(varString), *var;
for (var = varList; var != NULL; var = var->next)
{
if (cartListVarExists(cj->cart, var->name))
{
// Use cartOptionalSlNameList and return a list:
struct slName *valList = cartOptionalSlNameList(cj->cart, var->name);
jsonWriteSlNameList(cj->jw, var->name, valList);
slFreeList(&valList);
}
else
{
// Regular single-value variable (or not in the cart):
char *val = cartOptionalString(cj->cart, var->name);
jsonWriteString(cj->jw, var->name, val);
}
}
slFreeList(&varList);
}
INLINE boolean nameIsTdbField(char *name)
/* Return TRUE if name is a tdb->{field}, e.g. "track" or "shortLabel" etc. */
{
static char *tdbFieldNames[] =
{ "track", "table", "shortLabel", "longLabel", "type", "priority", "grp", "parent",
"subtracks", "visibility" };
return (stringArrayIx(name, tdbFieldNames, ArraySize(tdbFieldNames)) >= 0);
}
INLINE boolean fieldOk(char *field, struct hash *fieldHash)
/* Return TRUE if fieldHash is NULL or field exists in fieldHash. */
{
return (fieldHash == NULL || hashLookup(fieldHash, field));
}
static void writeTdbSimple(struct jsonWrite *jw, struct trackDb *tdb, struct hash *fieldHash)
/* Write JSON for the non-parent/child fields of tdb */
{
if (fieldOk("track", fieldHash))
jsonWriteString(jw, "track", tdb->track);
if (fieldOk("table", fieldHash))
jsonWriteString(jw, "table", tdb->table);
if (fieldOk("shortLabel", fieldHash))
jsonWriteString(jw, "shortLabel", tdb->shortLabel);
if (fieldOk("longLabel", fieldHash))
jsonWriteString(jw, "longLabel", tdb->longLabel);
if (fieldOk("type", fieldHash))
jsonWriteString(jw, "type", tdb->type);
if (fieldOk("priority", fieldHash))
jsonWriteDouble(jw, "priority", tdb->priority);
if (fieldOk("grp", fieldHash))
jsonWriteString(jw, "grp", tdb->grp);
// NOTE: if you add a new field here, then also add it to nameIsTdbField above.
if (tdb->settingsHash)
{
struct hashEl *hel;
struct hashCookie cookie = hashFirst(tdb->settingsHash);
while ((hel = hashNext(&cookie)) != NULL)
{
if (! nameIsTdbField(hel->name) && fieldOk(hel->name, fieldHash))
jsonWriteString(jw, hel->name, (char *)hel->val);
}
if (fieldOk("noGenome", fieldHash))
{
if ((hel = hashLookup(tdb->settingsHash, "tableBrowser")) != NULL)
{
if (startsWithWord("noGenome", (char *)(hel->val)))
jsonWriteBoolean(jw, "noGenome", TRUE);
}
}
}
}
static int trackDbViewCmp(const void *va, const void *vb)
/* *** I couldn't find anything like this in hgTracks or hui, but clearly views end up
* *** being ordered alphabetically somehow. Anyway...
* Compare two trackDbs, first by view if both have a view setting, then the usual way
* (by priority, then by shortLabel). */
{
const struct trackDb *a = *((struct trackDb **)va);
const struct trackDb *b = *((struct trackDb **)vb);
// The casts are necessary here unless one adds const to trackDbSetting decl
char *viewA = trackDbSetting((struct trackDb *)a, "view");
char *viewB = trackDbSetting((struct trackDb *)b, "view");
int diff = 0;
if (isNotEmpty(viewA) && isNotEmpty(viewB))
diff = strcmp(viewA, viewB);
if (diff != 0)
return diff;
return trackDbCmp(va, vb);
}
static struct jsonWrite *rTdbToJw(struct trackDb *tdb, struct hash *fieldHash,
struct hash *excludeTypesHash, int depth, int maxDepth)
/* Recursively build and return a new jsonWrite object with JSON for tdb and its children,
* or NULL if tdb or all children have been filtered out by excludeTypesHash.
* If excludeTypesHash is non-NULL, omit any tracks/views/subtracks with type in excludeTypesHash.
* If fieldHash is non-NULL, include only the field names indexed in fieldHash. */
{
if (maxDepth >= 0 && depth > maxDepth)
return NULL;
boolean doSubtracks = (tdb->subtracks && fieldOk("subtracks", fieldHash));
// If excludeTypesHash is given and tdb is a leaf track/subtrack, look up the first word
// of tdb->type in excludeTypesHash; if found, return NULL.
if (excludeTypesHash && !doSubtracks)
{
char typeCopy[PATH_LEN];
safecpy(typeCopy, sizeof(typeCopy), tdb->type);
if (hashLookup(excludeTypesHash, firstWordInLine(typeCopy)))
return NULL;
}
boolean gotSomething = !doSubtracks;
struct jsonWrite *jwNew = jsonWriteNew();
jsonWriteObjectStart(jwNew, NULL);
writeTdbSimple(jwNew, tdb, fieldHash);
if (tdb->parent && fieldOk("parent", fieldHash))
{
// We can't link to an object in JSON and better not recurse here or else infinite loop.
if (tdbIsSuperTrackChild(tdb))
{
// Supertracks have been omitted from fullTrackList, so add the supertrack object's
// non-parent/child info here.
jsonWriteObjectStart(jwNew, "parent");
writeTdbSimple(jwNew, tdb->parent, fieldHash);
jsonWriteObjectEnd(jwNew);
}
else
// Just the name so we don't have infinite loops.
jsonWriteString(jwNew, "parent", tdb->parent->track);
}
if (doSubtracks)
{
jsonWriteListStart(jwNew, "subtracks");
slSort(&tdb->subtracks, trackDbViewCmp);
struct trackDb *subTdb;
for (subTdb = tdb->subtracks; subTdb != NULL; subTdb = subTdb->next)
{
struct jsonWrite *jwSub = rTdbToJw(subTdb, fieldHash, excludeTypesHash, depth+1, maxDepth);
if (jwSub)
{
gotSomething = TRUE;
jsonWriteAppend(jwNew, NULL, jwSub);
jsonWriteFree(&jwSub);
}
}
jsonWriteListEnd(jwNew);
}
jsonWriteObjectEnd(jwNew);
if (! gotSomething)
// All children were excluded; clean up and null out jwNew.
jsonWriteFree(&jwNew);
return jwNew;
}
static struct hash *hashFromCommaString(char *commaString)
/* Return a hash that stores words in comma-separated string, or NULL if string is NULL or empty. */
{
struct hash *hash = NULL;
if (isNotEmpty(commaString))
{
hash = hashNew(0);
char *words[1024];
int i, wordCount = chopCommas(commaString, words);
for (i = 0; i < wordCount; i++)
hashStoreName(hash, words[i]);
}
return hash;
}
static struct hash *hashTracksByGroup(struct trackDb *trackList)
/* Hash group names to lists of tracks in those groups; sort each list by trackDb priority. */
{
struct hash *hash = hashNew(0);
struct trackDb *tdb;
for (tdb = trackList; tdb != NULL; tdb = tdb->next)
{
struct hashEl *hel = hashLookup(hash, tdb->grp);
struct slRef *slr = slRefNew(tdb);
if (hel)
slAddHead(&(hel->val), slr);
else
hashAdd(hash, tdb->grp, slr);
}
struct hashCookie cookie = hashFirst(hash);
struct hashEl *hel;
while ((hel = hashNext(&cookie)) != NULL)
slSort(&hel->val, trackDbRefCmp);
return hash;
}
int trackDbRefCmpShortLabel(const void *va, const void *vb)
/* Do trackDbCmpShortLabel on list of references as opposed to actual trackDbs. */
{
const struct slRef *aRef = *((struct slRef **)va);
const struct slRef *bRef = *((struct slRef **)vb);
struct trackDb *a = aRef->val, *b = bRef->val;
return trackDbCmpShortLabel(&a, &b);
}
static struct slRef *sortedAllTracks(struct trackDb *trackList)
/* Return an slRef list containing all tdbs in track list, sorted by shortLabel. */
{
struct slRef *trackRefList = NULL;
struct trackDb *tdb;
for (tdb = trackList; tdb != NULL; tdb = tdb->next)
slAddHead(&trackRefList, slRefNew(tdb));
slSort(&trackRefList, trackDbRefCmpShortLabel);
return trackRefList;
}
static boolean writeGroupedTrack(struct jsonWrite *jw, char *name, char *label,
struct hash *fieldHash, struct hash *excludeTypesHash,
int maxDepth, struct slRef *tdbRefList)
/* If tdbRefList is empty after excluding tracks/views/subtracks whose types are
* in excludeTypesHash, then return FALSE and write nothing. Otherwise write a group
* and its tracks/views/subtracks and return TRUE. */
{
// Make a new jsonWrite object in case this group turns out to have no children after filtering.
struct jsonWrite *jwNew = jsonWriteNew();
jsonWriteObjectStart(jwNew, NULL);
jsonWriteString(jwNew, "name", name);
jsonWriteString(jwNew, "label", label);
jsonWriteListStart(jwNew, "tracks");
boolean gotSomething = FALSE;
struct slRef *tdbRef;
for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next)
{
struct trackDb *tdb = tdbRef->val;
// First see if there are any tracks to show for this group:
struct jsonWrite *jwTrack = rTdbToJw(tdb, fieldHash, excludeTypesHash, 1, maxDepth);
if (jwTrack)
{
gotSomething = TRUE;
jsonWriteAppend(jwNew, NULL, jwTrack);
jsonWriteFree(&jwTrack);
}
}
if (gotSomething)
{
// Group has at least one track, so append it to jw.
jsonWriteListEnd(jwNew);
jsonWriteObjectEnd(jwNew);
jsonWriteAppend(jw, NULL, jwNew);
}
jsonWriteFree(&jwNew);
return gotSomething;
}
void cartJsonGetGroupedTrackDb(struct cartJson *cj, struct hash *paramHash)
/* Translate trackDb list (only a subset of the fields) into JSON array of track group objects;
* each group contains an array of track objects that may have subtracks. Send it in a wrapper
* object that includes the database from which it was taken; it's possible that by the time
* this reaches the client, the user might have switched to a new db. */
{
struct jsonWrite *jw = cj->jw;
struct trackDb *fullTrackList = NULL;
struct grp *fullGroupList = NULL;
struct errCatch *errCatch = errCatchNew();
if (errCatchStart(errCatch))
{
cartTrackDbInit(cj->cart, &fullTrackList, &fullGroupList, /* useAccessControl=*/TRUE);
}
errCatchEnd(errCatch);
if (errCatch->gotError)
{
warn("%s", errCatch->message->string);
jsonWriteObjectStart(jw, "groupedTrackDb");
jsonWriteString(jw, "db", cartString(cj->cart, "db"));
jsonWriteListStart(jw, "groupedTrackDb");
jsonWriteListEnd(jw);
jsonWriteObjectEnd(jw);
return;
}
errCatchFree(&errCatch);
struct hash *groupedTrackRefList = hashTracksByGroup(fullTrackList);
// If the optional param 'fields' is given, hash the field names that should be returned.
char *fields = cartJsonOptionalParam(paramHash, "fields");
struct hash *fieldHash = hashFromCommaString(fields);
char *excludeTypes = cartJsonOptionalParam(paramHash, "excludeTypes");
struct hash *excludeTypesHash = hashFromCommaString(excludeTypes);
// Also check for optional parameter 'maxDepth':
int maxDepth = -1;
char *maxDepthStr = cartJsonOptionalParam(paramHash, "maxDepth");
if (isNotEmpty(maxDepthStr))
maxDepth = atoi(maxDepthStr);
jsonWriteObjectStart(jw, "groupedTrackDb");
jsonWriteString(jw, "db", cartString(cj->cart, "db"));
jsonWriteListStart(jw, "groupedTrackDb");
int nonEmptyGroupCount = 0;
struct grp *grp;
for (grp = fullGroupList; grp != NULL; grp = grp->next)
{
// QuickLift remaps tracks from another assembly on the fly for display, but the
// underlying data is in the source assembly's coordinates, so output queries against
// the destination assembly don't make sense. Hide QuickLift groups from the UI.
if (startsWith("QuickLift", grp->label))
continue;
struct slRef *tdbRefList = hashFindVal(groupedTrackRefList, grp->name);
if (writeGroupedTrack(jw, grp->name, grp->label, fieldHash, excludeTypesHash,
maxDepth, tdbRefList))
{
nonEmptyGroupCount++;
}
}
if (nonEmptyGroupCount == 0)
{
// Catch-all for assembly hubs that don't declare groups for their tracks: add All Tracks
struct slRef *allTracks = sortedAllTracks(fullTrackList);
(void)writeGroupedTrack(jw, "allTracks", "All Tracks", fieldHash, excludeTypesHash,
maxDepth, allTracks);
}
jsonWriteListEnd(jw);
jsonWriteObjectEnd(jw);
}
static char *hAssemblyDescription(char *db)
/* Return a string containing db's description.html, or NULL if not found. */
//#*** LIBIFY: Code lifted from hgFind.c's hgPositionsHelpHtml.
{
char *htmlPath = hHtmlPath(db);
char *htmlString = NULL;
if (htmlPath != NULL)
{
if (fileExists(htmlPath))
readInGulp(htmlPath, &htmlString, NULL);
else if (startsWith("http://" , htmlPath) ||
startsWith("https://", htmlPath) ||
startsWith("ftp://" , htmlPath))
{
struct lineFile *lf = udcWrapShortLineFile(htmlPath, NULL, 256*1024);
char *fetched = lineFileReadAll(lf);
lineFileClose(&lf);
/* This one came in over the network from a hub, so print only what we allow. */
htmlString = htmlSanitize(fetched);
freeMem(fetched);
}
}
return htmlString;
}
static void getAssemblyInfo(struct cartJson *cj, struct hash *paramHash)
/* Return useful things from dbDb (or track hub) and assembly description html (possibly NULL).
* If db param is NULL, use db from cart. */
{
char *db = cartJsonOptionalParam(paramHash, "db");
if (db == NULL)
db = cartString(cj->cart, "db");
jsonWriteString(cj->jw, "db", db);
jsonWriteString(cj->jw, "commonName", hGenome(db));
jsonWriteString(cj->jw, "scientificName", hScientificName(db));
jsonWriteString(cj->jw, "dbLabel", hFreezeDate(db));
jsonWriteString(cj->jw, "assemblyDescription", hAssemblyDescription(db));
}
static void getHasCustomTracks(struct cartJson *cj, struct hash *paramHash)
/* Tell whether cart has custom tracks for db. If db param is NULL, use db from cart. */
{
char *db = cartJsonOptionalParam(paramHash, "db");
if (db == NULL)
db = cartString(cj->cart, "db");
jsonWriteBoolean(cj->jw, "hasCustomTracks", customTracksExistDb(cj->cart, db, NULL));
}
static void getIsSpecialHost(struct cartJson *cj, struct hash *paramHash)
/* Tell whether we're on a development host, preview, gsid etc. */
{
jsonWriteBoolean(cj->jw, "isPrivateHost", hIsPrivateHost());
jsonWriteBoolean(cj->jw, "isBetaHost", hIsBetaHost());
jsonWriteBoolean(cj->jw, "isBrowserbox", hIsBrowserbox());
jsonWriteBoolean(cj->jw, "isPreviewHost", hIsPreviewHost());
}
static void getHasHubTable(struct cartJson *cj, struct hash *paramHash)
/* Tell whether central database has a hub table (i.e. server can do hubs). */
{
jsonWriteBoolean(cj->jw, "hasHubTable", hubConnectTableExists());
}
static void setIfUnset(struct cartJson *cj, struct hash *paramHash)
/* For each name in paramHash, if that cart variable doesn't already have a non-empty
* value, set it to the value. */
{
struct hashCookie cookie = hashFirst(paramHash);
struct hashEl *hel;
while ((hel = hashNext(&cookie)) != NULL)
{
if (isEmpty(cartOptionalString(cj->cart, hel->name)))
{
char *val = jsonStringVal((struct jsonElement *)(hel->val), hel->name);
if (val)
cartSetString(cj->cart, hel->name, val);
}
}
}
//static void jsonWriteValueLabel(struct jsonWrite *jw, char *value, char *label)
/* Assuming we're already in an object, write out value and label tags & strings.
{
jsonWriteString(jw, "value", value);
jsonWriteString(jw, "label", label);
}
*/
//static void printCladeOrgDbTree(struct jsonWrite *jw)
/* Print out the tree of clades, organisms and dbs as JSON. Each node has value and label
* for menu options; clade nodes and org nodes also have children and default.
{
jsonWriteListStart(jw, "cladeOrgDb");
struct slPair *clade, *cladeOptions = hGetCladeOptions();
struct dbDb *centralDbDbList = hDbDbList();
for (clade = cladeOptions; clade != NULL; clade = clade->next)
{
jsonWriteObjectStart(jw, NULL);
jsonWriteValueLabel(jw, clade->name, clade->val);
jsonWriteListStart(jw, "children");
struct slPair *org, *orgOptions = hGetGenomeOptionsForClade(clade->name);
for (org = orgOptions; org != NULL; org = org->next)
{
jsonWriteObjectStart(jw, NULL);
jsonWriteValueLabel(jw, org->name, org->val);
jsonWriteListStart(jw, "children");
struct dbDb *dbDb, *dbDbList;
if (isHubTrack(org->name))
dbDbList = trackHubGetDbDbs(clade->name);
else
dbDbList = centralDbDbList;
for (dbDb = dbDbList; dbDb != NULL; dbDb = dbDb->next)
{
if (sameString(org->name, dbDb->genome))
{
jsonWriteObjectStart(jw, NULL);
jsonWriteValueLabel(jw, dbDb->name, dbDb->description);
jsonWriteString(jw, "defaultPos", dbDb->defaultPos);
jsonWriteObjectEnd(jw);
}
}
jsonWriteListEnd(jw); // children (dbs)
jsonWriteString(jw, "default", trimSpaces(hDefaultDbForGenome(org->name)));
jsonWriteObjectEnd(jw); // org
}
jsonWriteListEnd(jw); // children (orgs)
jsonWriteString(jw, "default", trimSpaces(hDefaultGenomeForClade(clade->name)));
jsonWriteObjectEnd(jw); // clade
}
jsonWriteListEnd(jw);
}
*/
static void getCladeOrgDbPos(struct cartJson *cj, struct hash *paramHash)
/* Get cart's current clade, org, db, position and geneSuggest track. */
{
jsonWriteObjectStart(cj->jw, "cladeOrgDb");
//printCladeOrgDbTree(cj->jw);
char *db = cartString(cj->cart, "db");
jsonWriteString(cj->jw, "db", db);
char *org = cartUsualString(cj->cart, "org", hGenome(db));
jsonWriteString(cj->jw, "org", org);
char *clade = cartUsualString(cj->cart, "clade", hClade(org));
jsonWriteString(cj->jw, "clade", clade);
jsonWriteObjectEnd(cj->jw);
char *position = cartOptionalString(cj->cart, "position");
if (isEmpty(position))
position = hDefaultPos(db);
jsonWriteString(cj->jw, "position", position);
printGeneSuggestTrack(cj, db);
}
static void getStaticHtml(struct cartJson *cj, struct hash *paramHash)
/* Read HTML text from a relative path under browser.documentRoot and
* write it as an encoded JSON string */
{
char *tag = cartJsonOptionalParam(paramHash, "tag");
if (isEmpty(tag))
tag = "html";
char *file = cartJsonRequiredParam(paramHash, "file", cj->jw, "getStaticHtml");
char *html = hFileContentsOrWarning(file);
jsonWriteString(cj->jw, tag, html);
}
void cartJsonRegisterHandler(struct cartJson *cj, char *command, CartJsonHandler *handler)
/* Associate handler with command; when javascript sends command, handler will be executed. */
{
hashAdd(cj->handlerHash, command, handler);
}
struct cartJson *cartJsonNew(struct cart *cart)
/* Allocate and return a cartJson object with default handlers.
* cart must have "db" set already. */
{
struct cartJson *cj;
AllocVar(cj);
cj->handlerHash = hashNew(0);
cj->jw = jsonWriteNew();
cj->cart = cart;
cartJsonRegisterHandler(cj, "getCladeOrgDbPos", getCladeOrgDbPos);
cartJsonRegisterHandler(cj, "changePosition", changePositionHandler);
cartJsonRegisterHandler(cj, "get", getVar);
cartJsonRegisterHandler(cj, "getGroupedTrackDb", cartJsonGetGroupedTrackDb);
cartJsonRegisterHandler(cj, "getAssemblyInfo", getAssemblyInfo);
cartJsonRegisterHandler(cj, "getGeneSuggestTrack", getGeneSuggestTrack);
cartJsonRegisterHandler(cj, "getHasCustomTracks", getHasCustomTracks);
cartJsonRegisterHandler(cj, "getIsSpecialHost", getIsSpecialHost);
cartJsonRegisterHandler(cj, "getHasHubTable", getHasHubTable);
cartJsonRegisterHandler(cj, "setIfUnset", setIfUnset);
cartJsonRegisterHandler(cj, "getStaticHtml", getStaticHtml);
return cj;
}
void cartJsonFree(struct cartJson **pCj)
/* Close **pCj's contents and nullify *pCj. */
{
if (*pCj == NULL)
return;
struct cartJson *cj = *pCj;
jsonWriteFree(&cj->jw);
hashFree(&cj->handlerHash);
freez(pCj);
}
static void doOneCommand(struct cartJson *cj, char *command,
struct jsonElement *paramObject)
/* Dispatch command by name, checking for required parameters. */
{
CartJsonHandler *handler = hashFindVal(cj->handlerHash, command);
if (handler)
{
struct hash *paramHash = jsonObjectVal(paramObject, command);
handler(cj, paramHash);
}
else
{
jsonWriteStringf(cj->jw, "error",
"cartJson: unrecognized command '%s'", command);
return;
}
}
static int commandCmp(const void *pa, const void *pb)
/* Comparison function to put "change" commands ahead of other commands. */
{
struct slPair *cmdA = *((struct slPair **)pa);
struct slPair *cmdB = *((struct slPair **)pb);
boolean aIsChange = startsWith("change", cmdA->name);
boolean bIsChange = startsWith("change", cmdB->name);
if (aIsChange && !bIsChange)
return -1;
if (!aIsChange && bIsChange)
return 1;
return strcmp(cmdA->name, cmdB->name);
}
// Accumulate warnings so they can be JSON-ified:
static struct dyString *dyWarn = NULL;
static void cartJsonVaWarn(char *format, va_list args)
/* Save warnings for later. */
{
dyStringVaPrintf(dyWarn, format, args);
}
boolean cartJsonIsNoWarns()
/* Return TRUE if there are no warnings present */
{
return dyWarn && dyStringLen(dyWarn) == 0;
}
void cartJsonPrintWarnings(struct jsonWrite *jw)
/* If there are warnings, write them out as JSON: */
{
if (dyWarn && dyStringLen(dyWarn) > 0)
jsonWriteString(jw, "warning", dyWarn->string);
}
static void cartJsonAbort()
/* Print whatever warnings we have accumulated and exit. */
{
if (dyWarn)
puts(dyWarn->string);
exit(0);
}
void cartJsonPushErrHandlers()
/* Push warn and abort handlers for errAbort. */
{
if (dyWarn == NULL)
dyWarn = dyStringNew(0);
else
dyStringClear(dyWarn);
pushWarnHandler(cartJsonVaWarn);
pushAbortHandler(cartJsonAbort);
}
void cartJsonPopErrHandlers()
/* Pop warn and abort handlers for errAbort. */
{
popWarnHandler();
popAbortHandler();
}
void cartJsonExecute(struct cartJson *cj)
/* Get commands from cgi, print Content-type, execute commands, print results as JSON. */
{
cartJsonPushErrHandlers();
-puts("Content-Type:text/javascript\n");
+cgiPrintContentType("text/javascript");
// Initialize response JSON object:
jsonWriteObjectStart(cj->jw, NULL);
// Always send back hgsid:
jsonWriteString(cj->jw, cartSessionVarName(), cartSessionId(cj->cart));
char *commandJson = cgiOptionalString(CARTJSON_COMMAND);
if (commandJson)
{
struct errCatch *errCatch = errCatchNew();
if (errCatchStart(errCatch))
{
struct jsonElement *commandObj = jsonParse(commandJson);
struct hash *commandHash = jsonObjectVal(commandObj, "commandObj");
// change* commands need to go first! Really we need an ordered map type here...
// for now, just make a list and sort to put change commands at the front.
struct slPair *commandList = NULL, *cmd;
struct hashCookie cookie = hashFirst(commandHash);
struct hashEl *hel;
while ((hel = hashNext(&cookie)) != NULL)
slAddHead(&commandList, slPairNew(hel->name, hel->val));
slSort(&commandList, commandCmp);
for (cmd = commandList; cmd != NULL; cmd = cmd->next)
doOneCommand(cj, cmd->name, (struct jsonElement *)cmd->val);
}
errCatchEnd(errCatch);
if (errCatch->gotError)
{
jsonWritePopToLevel(cj->jw, 1);
jsonWriteString(cj->jw, "error", errCatch->message->string);
}
errCatchReWarn(errCatch);
errCatchFree(&errCatch);
}
cartJsonPrintWarnings(cj->jw);
jsonWriteObjectEnd(cj->jw);
puts(cj->jw->dy->string);
cartJsonPopErrHandlers();
}
char *cartJsonDumpJson(struct cartJson *cj)
/* Return the string that has been built up so far or an empty string */
{
if (cj != NULL && cj->jw != NULL && dyStringLen(cj->jw->dy) > 0)
return cj->jw->dy->string;
else
return "";
}