9ad04e0a0b06ec3c4f09ef1b6c3ce6be79b61c68
braney
Sun Aug 16 11:56:56 2026 -0700
cart: validate file names read back out of the cart
Several cart variables hold the name of a file the server created for a user.
Route them through one shared check, isServerUserFilePath(), which accepts the
trash directory, the session-data directories and myVariantsDataDir, and apply
it both where values enter the cart and where the file names are used.
A few of these variables may instead hold a remote URL. Those get their own
list and isServerUserFileOrUrl(), because the code that reads them chooses
between a fetch and a local open by looking for a protocol.
Consolidates two hand-rolled copies of the same test in blatShare.c and
customFactory.c, and drops the weaker private copy in sessionData.c.
Adds hg/utils/cartFileVarCatalog, a registry that scans the tree for a cart
value reaching a file call and reconciles what it finds against the lists in
cart.c, so a new one of these cannot be added without somebody noticing. Its
--reconcile is quiet enough for the nightly cron the other catalogs use, and it
is what turned up seven of the names now on those lists.
refs #37623
diff --git src/hg/hgTables/userRegions.c src/hg/hgTables/userRegions.c
index 74480fedf03..bd72b6ae8b1 100644
--- src/hg/hgTables/userRegions.c
+++ src/hg/hgTables/userRegions.c
@@ -1,231 +1,236 @@
/* identifiers - handle identifier lists: uploading, pasting,
* and restricting to just things on the list. */
/* Copyright (C) 2011 The Regents of the University of California
* See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
#include "common.h"
#include "linefile.h"
#include "hash.h"
#include "cheapcgi.h"
#include "htmshell.h"
#include "cart.h"
#include "jksql.h"
#include "trackDb.h"
#include "portable.h"
#include "hgTables.h"
#include "trashDir.h"
#include "hui.h"
#include "obscure.h"
#include "userRegions.h"
#include "web.h"
static int maxRegions = 1000;
static int maxErrors = 100;
void doSetUserRegionsAfterOpen(struct sqlConnection *conn)
/* Respond to set regions button. */
{
char *oldPasted = cartUsualString(cart, hgtaEnteredUserRegions, "");
char *db = cartOptionalString(cart, hgtaUserRegionsDb);
if (db && !sameString(db, database))
oldPasted = "";
hPrintf("
\n");
webIncludeHelpFile("hgTbUserRegionsHelp", FALSE);
}
void doSetUserRegions(struct sqlConnection *conn)
/* Respond to set regions button. */
{
htmlOpen("Enter region definition\n");
doSetUserRegionsAfterOpen(conn);
htmlClose();
}
static char *limitText(char *text)
/* read text string and limit to maxRegions actual data lines */
{
struct dyString *limitedText = dyStringNew(0);
/* Even if using FALSE for zTerm, lineFile still does a memmove when it hits the end
* and thus clobbers the string, so call lineFileOnString on a copy: */
char copy[strlen(text)+1];
safecpy(copy, sizeof(copy), text);
struct lineFile *lf = lineFileOnString("limitText", FALSE, copy);
char *lineStart = NULL;
int lineLength = 0;
int legitimateLineCount = 0;
while (legitimateLineCount < maxRegions && lineFileNext(lf, &lineStart, &lineLength))
{
char *s, c;
s = skipLeadingSpaces(lineStart);
c = s[0];
if (c != 0 && c != '#')
++legitimateLineCount;
dyStringAppendN(limitedText, lineStart, lineLength);
}
if ((legitimateLineCount == maxRegions) && lineFileNext(lf, &lineStart, &lineLength))
warn("WARNING: defined regions limit of %d definitions reached at line %d
\n",
maxRegions, lf->lineIx-1);
lineFileClose(&lf);
return (dyStringCannibalize(&limitedText));
}
static void cartRemoveUserRegions()
/* Remove all cart variables related to storage of user regions. */
{
cartRemove(cart, hgtaEnteredUserRegions);
cartRemove(cart, hgtaEnteredUserRegionFile);
cartRemove(cart, hgtaUserRegionsFile);
cartRemove(cart, hgtaUserRegionsDb);
cartRemove(cart, hgtaRegionType);
}
void doSubmitUserRegions(struct sqlConnection *conn)
/* Process submit in set regions page. */
{
char *idText = trimSpaces(cartString(cart, hgtaEnteredUserRegions));
char *userRegionFile = trimSpaces(cartString(cart, hgtaEnteredUserRegionFile));
htmlOpen("Table Browser (Region definitions)");
/* presence of fileName text overrides previously existing text area
* contents
*/
if (userRegionFile != NULL && userRegionFile[0] != 0)
{
idText = cloneString(userRegionFile);
cartRemove(cart, hgtaEnteredUserRegions);
cartRemove(cart, hgtaUserRegionsFile);
cartSetString(cart, hgtaEnteredUserRegions, idText);
}
char *lineLimitText = limitText(idText);
if ( (strlen(lineLimitText) > 0) && (strlen(lineLimitText) != strlen(idText)) )
{
idText = lineLimitText;
cartSetString(cart, hgtaEnteredUserRegions, lineLimitText);
}
else
freeMem(lineLimitText);
boolean success = TRUE;
if (isNotEmpty(idText))
{
int regionCount = 0;
char *warnText = NULL;
char *trashFileName = userRegionsParse(database, idText, maxRegions, maxErrors,
®ionCount, &warnText);
if (isNotEmpty(warnText))
{
success = FALSE;
warn("%s", warnText);
}
if (regionCount == 0)
{
success = FALSE;
warn("No valid regions found in input; see below for formatting instructions");
}
cartSetString(cart, hgtaUserRegionsDb, database);
cartSetString(cart, hgtaUserRegionsFile, trashFileName);
cartSetString(cart, hgtaRegionType, hgtaRegionTypeUserRegions);
if (strlen(idText) > 64 * 1024)
cartRemove(cart, hgtaEnteredUserRegions);
cartRemove(cart, hgtaEnteredUserRegionFile);
}
else
{
cartRemoveUserRegions();
}
if (success)
mainPageAfterOpen(conn);
else
doSetUserRegionsAfterOpen(conn);
htmlClose();
}
char *userRegionsFileName()
/* File name defined regions are in, or NULL if no such file. */
{
char *fileName = cartOptionalString(cart, hgtaUserRegionsFile);
char *db = cartOptionalString(cart, hgtaUserRegionsDb);
if (db && !sameString(database, db))
return NULL;
if (fileName == NULL)
return NULL;
+if (!isServerUserFilePath(fileName))
+ // Not a file we made, so don't read it and don't let the caller delete it. Leave the
+ // cart alone as well -- cartRemoveUserRegions() here would throw away a region list
+ // that the user can still fix by re-entering it.
+ return NULL;
if (fileExists(fileName))
return fileName;
else
{
cartRemoveUserRegions();
return NULL;
}
}
struct region *getUserRegions(char *fileName)
/* Get user defined regions from fileName. */
{
struct region *list = NULL, *region;
struct lineFile *lf;
char *words[4];
int wordCount;
lf = lineFileOpen(fileName, TRUE); /* TRUE == replace CR with 0 */
while (0 != (wordCount = lineFileChopNext(lf, words, ArraySize(words))))
{
AllocVar(region);
region->chrom = cloneString(words[0]);
region->start = atoi(words[1]);
region->end = atoi(words[2]);
if (wordCount > 3)
region->name = cloneString(words[3]);
else
region->name = NULL;
slAddHead(&list, region);
}
slReverse(&list);
lineFileClose(&lf);
return list;
}
void doClearSetUserRegionsText(struct sqlConnection *conn)
/* Respond to clear within user regions enter page. */
{
char *fileName = userRegionsFileName();
if (fileName != NULL)
remove(fileName);
cartRemoveUserRegions();
doSetUserRegions(conn);
}
void doClearUserRegions(struct sqlConnection *conn)
/* Respond to clear user regions button. */
{
char *fileName = userRegionsFileName();
htmlOpen("Table Browser (Cleared Region List)");
if (fileName != NULL)
remove(fileName);
cartRemoveUserRegions();
mainPageAfterOpen(conn);
htmlClose();
}