ca23d9f5054fc863beb18d7ef4919b550b2f8d9c
chmalee
  Mon Jun 1 10:45:37 2026 -0700
Allow dot in genome names in hubspace uploads, refs #37665

diff --git src/hg/hgHubConnect/hooks/pre-finish.c src/hg/hgHubConnect/hooks/pre-finish.c
index 1a5ade8b314..ca5ba1323a8 100644
--- src/hg/hgHubConnect/hooks/pre-finish.c
+++ src/hg/hgHubConnect/hooks/pre-finish.c
@@ -93,36 +93,38 @@
             }
         fprintf(stderr, "userName='%s'\n", userName);
         // NOTE: All Upload.MetaData values are strings
         // Check multiple possible metadata keys for filename (Uppy sends 'filename' and 'name' by default,
         // our JS code also sets 'fileName' - try all to handle resumed uploads with old metadata)
         char *rawFileName = jsonQueryString(req, "", "Event.Upload.MetaData.fileName", NULL);
         if (!rawFileName)
             rawFileName = jsonQueryString(req, "", "Event.Upload.MetaData.filename", NULL);
         if (!rawFileName)
             rawFileName = jsonQueryString(req, "", "Event.Upload.MetaData.name", NULL);
         fileName = rawFileName ? cgiEncodeFull(rawFileName) : 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);
         // Blocks newline injection into the synthesized hub.txt.
+        // The allowed character class must match sanitizeGenomeName() in
+        // src/hg/js/hgMyData.js.
         if (db && db[0])
             {
             char *p;
             for (p = db; *p; p++)
-                if (!(isalnum((unsigned char)*p) || *p == '_' || *p == '-'))
-                    errAbort("Invalid genome name '%s': only letters, digits, '_' and '-' are allowed", db);
+                if (!(isalnum((unsigned char)*p) || *p == '_' || *p == '-' || *p == '.'))
+                    errAbort("Invalid genome name '%s': only letters, digits, '.', '_' and '-' are allowed", db);
             }
         reqLm = jsonQueryString(req, "", "Event.Upload.MetaData.lastModified", NULL);
         if (reqLm)
             lastModified = sqlLongLong(reqLm) / 1000; // yes Javascript dates are in millis
         else
             lastModified = time(NULL); // fallback to current time if not provided
         parentDir = jsonQueryString(req, "", "Event.Upload.MetaData.parentDir", NULL);
         fprintf(stderr, "parentDir = '%s'\n", parentDir ? parentDir : "(null)");
         // strip out plain leading '.' and '/' components
         // middle '.' components are dealt with later
         if (parentDir && (startsWith("./", parentDir) || startsWith("/", parentDir)))
             parentDir = skipBeyondDelimit(parentDir, '/');
         tusFile = jsonQueryString(req, "", "Event.Upload.Storage.Path", NULL);
         tusInfo = jsonQueryString(req, "", "Event.Upload.Storage.InfoPath", NULL);
         if (fileName == NULL)