922f56ae7feb0e0b3b8a5ffe31b9384d604440b2
chmalee
  Fri Sep 26 12:10:39 2025 -0700
Do not allow plus or dash chars in hubspace filenames or hub names to prevent url encoding issues, refs Jairo email

diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js
index 26e3bfa5663..7096a131b6b 100644
--- src/hg/js/hgMyData.js
+++ src/hg/js/hgMyData.js
@@ -160,32 +160,33 @@
     let handleSuccess = function(req) {
         apiKeyInstr.style.display = "none";
         document.getElementById("spinner").remove();
         let generateDiv = document.getElementById("generateDiv");
         generateDiv.style.display = "block";
         let revokeDiv = document.getElementById("revokeDiv");
         revokeDiv.style.display = "none";
     };
 
     let cartData = {revokeApiKey: {}};
     cart.setCgiAndUrl(fileListEndpoint);
     cart.send(cartData, handleSuccess);
     cart.flush();
 }
 
-const fileNameRegex = /[0-9a-zA-Z._\-+]+/g; // allowed characters in file names
-const parentDirRegex = /[0-9a-zA-Z._\-+]+/g; // allowed characters in hub names
+const fileNameRegex = /[0-9a-zA-Z._]+/g; // allowed characters in file names
+const fileNameFixRegex = /[^0-9a-zA-Z_]+/g; // '.' get replaced to underbars in trackHub.c. Also any files uploaded from hubtools that may have weird chars need to be escaped
+const parentDirRegex = /[0-9a-zA-Z._]+/g; // allowed characters in hub names
 
 function getTusdEndpoint() {
     // this variable is set by hgHubConnect and comes from hg.conf value
     return tusdEndpoint;
 }
 
 let uppyOptions = {
     trigger: ".uploadButton",
     showProgressDetails: true,
     note: "The UCSC Genome Browser is not a HIPAA compliant data store. Do not upload patient information or other sensitive data files here, as anyone with the URL can view them.",
     meta: {"genome": null, "fileType": null},
     restricted: {requiredMetaFields: ["genome"]},
     closeModalOnClickOutside: true,
     closeAfterFinish: true,
     theme: 'auto',
@@ -285,36 +286,36 @@
     },
 };
 
 // make our Uppy instance:
 const uppy = new Uppy.Uppy({
     debug: true,
     allowMultipleUploadBatches: false,
     onBeforeUpload: (files) => {
         // set all the fileTypes and genomes from their selects
         let doUpload = true;
         let thisQuota = 0;
         for (let [key, file] of Object.entries(files)) {
             let fileNameMatch = file.meta.name.match(fileNameRegex);
             let parentDirMatch = file.meta.parentDir.match(parentDirRegex);
             if (!fileNameMatch || fileNameMatch[0] !== file.meta.name) {
-                uppy.info(`Error: File name has special characters, please rename file: ${file.meta.name} to only include alpha-numeric characters, period, dash, underscore or plus.`, 'error', 5000);
+                uppy.info(`Error: File name has special characters, please rename file: ${file.meta.name} to only include alpha-numeric characters, period, or underscore.`, 'error', 5000);
                 doUpload = false;
                 continue;
             }
             if (!parentDirMatch || parentDirMatch[0] !== file.meta.parentDir) {
-                uppy.info(`Error: Hub name has special characters, please rename hub: ${file.meta.parentDir} for file: ${file.meta.name} to only include alpha-numeric characters, period, dash, underscore, or plus.`, 'error', 5000);
+                uppy.info(`Error: Hub name has special characters, please rename hub: ${file.meta.parentDir} for file: ${file.meta.name} to only include alpha-numeric characters, period, or underscore.`, 'error', 5000);
                 doUpload = false;
                 continue;
             }
             if (!file.meta.genome) {
                 uppy.info(`Error: No genome selected for file ${file.meta.name}!`, 'error', 5000);
                 doUpload = false;
                 continue;
             }
             if  (!file.meta.fileType) {
                 uppy.info(`Error: File type not supported, file: ${file.meta.name}!`, 'error', 5000);
                 doUpload = false;
                 continue;
             }
             // check if this hub already exists and the genome is different from what was
             // just selected, if so, make the user create a new hub
@@ -548,34 +549,34 @@
         });
         this.uppy.on("dashboard:file-edit-start", (file) => {
             autocompletes[`${file.name}DbInput`] = false;
         });
 
         this.uppy.on("dashboard:file-edit-complete", (file) => {
             // check the filename and hubname metadata and warn the user
             // to edit them if they are wrong. unfortunately I cannot
             // figure out how to force the file card to re-toggle
             // and jump back into the editor from here
             if (file) {
                 let fileNameMatch = file.meta.name.match(fileNameRegex);
                 let parentDirMatch = file.meta.parentDir.match(parentDirRegex);
                 const dash = uppy.getPlugin("Dashboard");
                 if (!fileNameMatch || fileNameMatch[0] !== file.meta.name) {
-                    uppy.info(`Error: File name has special characters, please rename file: '${file.meta.name}' to only include alpha-numeric characters, period, dash, underscore or plus.`, 'error', 5000);
+                    uppy.info(`Error: File name has special characters, please rename file: '${file.meta.name}' to only include alpha-numeric characters, period, or underscore.`, 'error', 5000);
                 }
                 if (!parentDirMatch || parentDirMatch[0] !== file.meta.parentDir) {
-                    uppy.info(`Error: Hub name has special characters, please rename hub: '${file.meta.parentDir}' to only include alpha-numeric characters, period, dash, underscore, or plus.`, 'error', 5000);
+                    uppy.info(`Error: Hub name has special characters, please rename hub: '${file.meta.parentDir}' to only include alpha-numeric characters, period, or underscore.`, 'error', 5000);
                 }
             }
         });
     }
     uninstall() {
         // not really used because we aren't ever uninstalling the uppy instance
         this.uppy.off("file-added");
     }
 }
 
 var hubCreate = (function() {
     let uiState = { // our object for keeping track of the current UI and what to do
         userUrl: "", // the web accesible path where the uploads are stored for this user
         hubNameDefault: "",
         isLoggedIn: "",
@@ -676,34 +677,33 @@
 
     function viewInGenomeBrowser(fname, ftype, genome, hubName) {
         // redirect to hgTracks with this track open in the hub
         if (typeof uiState.userUrl !== "undefined" && uiState.userUrl.length > 0) {
             if (ftype in extensionMap) {
                 // TODO: tusd should return this location in it's response after
                 // uploading a file and then we can look it up somehow, the cgi can
                 // write the links directly into the html directly for prev uploaded files maybe?
                 let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid() + "&db=" + genome + "&hubUrl=" + uiState.userUrl + cgiEncode(hubName) + "/hub.txt&" + trackHubFixName(fname) + "=pack";
                 window.location.assign(url);
                 return false;
             }
         }
     }
 
-    const regex = /[^A-Za-z0-9_-]/g;
     function trackHubFixName(trackName) {
-        // replace everything but alphanumeric, underscore and dash with underscore
-        return encodeURIComponent(trackName).replaceAll(regex, "_");
+        // replace everything but alphanumeric and underscore with underscore
+        return encodeURIComponent(trackName.replaceAll(fileNameFixRegex, "_"));
     }
 
     // helper object so we don't need to use an AbortController to update
     // the data this function is using
     let selectedData = {};
     function viewAllInGenomeBrowser(ev) {
         // redirect to hgTracks with these tracks/hubs open
         let data = selectedData;
         if (typeof uiState.userUrl !== "undefined" && uiState.userUrl.length > 0) {
             let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid();
             let genome; // may be multiple genomes in list, just redirect to the first one
                         // TODO: this should probably raise an alert to click through
             let hubsAdded = {};
             _.forEach(data, (d) => {
                 if (!genome) {