1f954876a8847da234e0c89b3b43390b40c6ca4e chmalee Tue Feb 25 10:45:31 2025 -0800 Special handle the parentDir setting during uploads by cgi-encoding the components of parentDir but leaving the slash characters in place. This allows hubtools uploads to preserve the filesystem layout on the host on our end, but prevent writing of files outside of the users hubspace directory. Any '..' characters in the parentDir are simply ignored. This commit also fixes some other hubtools related UI bugs, like preventing infinite recursion when doing a row select, displaying file names unencoded, and fixing a bug when deleting subdirectories, refs #31058 diff --git src/hg/hgHubConnect/hooks/pre-finish.c src/hg/hgHubConnect/hooks/pre-finish.c index 76880b13893..29c15dc897c 100644 --- src/hg/hgHubConnect/hooks/pre-finish.c +++ src/hg/hgHubConnect/hooks/pre-finish.c @@ -57,31 +57,31 @@ { struct errCatch *errCatch = errCatchNew(0); if (errCatchStart(errCatch)) { // the variables for the row entry for this file, some can be NULL char *userName = NULL; char *dataDir = NULL, *userDataDir = NULL; char *fileName = NULL; long long fileSize = 0; char *fileType = NULL; char *db = NULL; char *location = NULL; char *reqLm = NULL; time_t lastModified = 0; boolean isHubToolsUpload = FALSE; - char *parentDir = NULL; + char *parentDir = NULL, *encodedParentDir = NULL; struct lineFile *lf = lineFileStdin(FALSE); char *request = lineFileReadAll(lf); struct jsonElement *req = jsonParse(request); fprintf(stderr, "Hook request:\n"); jsonPrintToFile(req, NULL, stderr, 0); char *reqCookie= jsonQueryString(req, "", "Event.HTTPRequest.Header.Cookie[0]", NULL); if (reqCookie) { setenv("HTTP_COOKIE", reqCookie, 0); } fprintf(stderr, "reqCookie='%s'\n", reqCookie); userName = getUserName(); if (!userName) { @@ -90,59 +90,60 @@ userName = userNameForApiKey(apiKey); if (!userName) errAbort("You are not logged in. Please navigate to My Data -> My Sessions and log in or create an account."); } fprintf(stderr, "userName='%s'\n'", userName); // NOTE: All Upload.MetaData values are strings fileName = cgiEncodeFull(jsonQueryString(req, "", "Event.Upload.MetaData.fileName", NULL)); fileSize = jsonQueryInt(req, "", "Event.Upload.Size", 0, NULL); fileType = jsonQueryString(req, "", "Event.Upload.MetaData.fileType", NULL); db = jsonQueryString(req, "", "Event.Upload.MetaData.genome", NULL); reqLm = jsonQueryString(req, "", "Event.Upload.MetaData.lastModified", NULL); lastModified = sqlLongLong(reqLm) / 1000; // yes Javascript dates are in millis char *hubtoolsStr = jsonQueryString(req, "", "Event.Upload.MetaData.hubtools", NULL); if (hubtoolsStr) isHubToolsUpload = sameString(hubtoolsStr, "TRUE") || sameString(hubtoolsStr, "true"); - parentDir = cgiEncodeFull(jsonQueryString(req, "", "Event.Upload.MetaData.parentDir", NULL)); + parentDir = jsonQueryString(req, "", "Event.Upload.MetaData.parentDir", NULL); fprintf(stderr, "parentDir = '%s'\n", parentDir); fflush(stderr); // strip out plain leading '.' and '/' components // middle '.' components are dealt with later if (startsWith("./", parentDir) || startsWith("/", parentDir)) parentDir = skipBeyondDelimit(parentDir, '/'); fprintf(stderr, "parentDir = '%s'\n", parentDir); fflush(stderr); char *tusFile = jsonQueryString(req, "", "Event.Upload.Storage.Path", NULL); if (fileName == NULL) { errAbort("No Event.Upload.fileName setting"); } else if (tusFile == NULL) { errAbort("No Event.Path setting"); } else { char *tusInfo = catTwoStrings(tusFile, ".info"); userDataDir = dataDir = getDataDir(userName); struct dyString *newFile = dyStringNew(0); // if parentDir provided we are throwing the files in there if (parentDir) { - if (!endsWith(parentDir, "/")) - parentDir = catTwoStrings(parentDir, "/"); - dataDir = catTwoStrings(dataDir, parentDir); + encodedParentDir = encodePath(parentDir); + if (!endsWith(encodedParentDir, "/")) + encodedParentDir = catTwoStrings(encodedParentDir, "/"); + dataDir = catTwoStrings(dataDir, encodedParentDir); } dyStringPrintf(newFile, "%s%s", dataDir, fileName); fprintf(stderr, "moving %s to %s\n", tusFile, dyStringContents(newFile)); // TODO: check if file exists or not and let user choose to overwrite // and re-call this hook, for now just exit if the file exists if (fileExists(dyStringContents(newFile))) { errAbort("file '%s' exists already, not overwriting", dyStringContents(newFile)); } else { // set our mysql table location: location = dyStringContents(newFile); // the directory may not exist yet @@ -169,40 +170,40 @@ // to the mysql table and return to the client that we were successful if (exitStatus == 0) { // create a hub for this upload, which can be edited later struct hubSpace *row = NULL; AllocVar(row); row->userName = userName; row->fileName = fileName; row->fileSize = fileSize; row->fileType = fileType; row->creationTime = NULL; // automatically handled by mysql row->lastModified = sqlUnixTimeToDate(&lastModified, TRUE); row->db = db; row->location = location; row->md5sum = md5HexForFile(row->location); - row->parentDir = parentDir ? parentDir : ""; + row->parentDir = encodedParentDir ? encodedParentDir : ""; if (!isHubToolsUpload) { - createNewTempHubForUpload(reqId, row, userDataDir, parentDir); + createNewTempHubForUpload(reqId, row, userDataDir, encodedParentDir); fprintf(stderr, "added hub.txt and hubSpace row for hub for file: '%s'\n", fileName); fflush(stderr); } // first make the parentDir rows makeParentDirRows(row->userName, sqlDateToUnixTime(row->lastModified), row->db, row->parentDir, userDataDir); - row->parentDir = hubNameFromPath(parentDir); + row->parentDir = hubNameFromPath(encodedParentDir); addHubSpaceRowForFile(row); fprintf(stderr, "added hubSpace row for file '%s'\n", fileName); fflush(stderr); } } if (errCatch->gotError) { rejectUpload(response, errCatch->message->string); exitStatus = 1; } errCatchEnd(errCatch); } // always print a response no matter what jsonPrintToFile(response, NULL, stdout, 0); return exitStatus;