905b9cb05eeaca7f2dcda42fc6abdb95a2d2da7f
max
  Wed Sep 9 05:29:28 2026 -0700
no captcha for a command-line CGI run, and version the detailsScript module URL

Two small fixes to things noticed while adding the scatterPlot plot type.

A CGI run from the command line got the Cloudflare Turnstile challenge page
instead of the output the caller asked for, which makes "./hgc db=hg38 g=x" -
the quickest way to see what a CGI emits - useless without a hand-made hg.conf.
There is no browser to solve a captcha in that situation. printCaptcha() now
returns early when cgiWasSpoofed(). That flag cannot be set from an HTTP
request: cgiFromCommandLine() returns early and leaves it FALSE whenever the
web server has set REQUEST_METHOD. Checked that a plain argument-style run is
now clean, that a run which fakes the web environment with QUERY_STRING still
gets the captcha, and that an HTTP request behaves exactly as the unmodified
binary does.

The detailsScript module was loaded from a hardcoded import('../js/hgc.X.js'),
bypassing webTimeStampedLinkToResource(), so it was the one script on the page
with no ?v=<mtime>. That is the mechanism that flushes a browser's cache when
the CGI version changes and that keeps a mirror from pairing an old static file
with new CGIs, and without it a cached module could be handed newer bedDetails
JSON than it was written for. Now built through the helper, which also fixes the
already-shipped histogram type. The helper errAborts on a missing file and the
plot type comes from a hub, so a plot type with no module installed falls back to
the plain path: a silent failed import as before, rather than one bad hub setting
taking down the whole details page.

refs #35415

diff --git src/hg/hgc/bigBedClick.c src/hg/hgc/bigBedClick.c
index fdac6c12423..0eb19d930e3 100644
--- src/hg/hgc/bigBedClick.c
+++ src/hg/hgc/bigBedClick.c
@@ -716,35 +716,58 @@
                         jsonWriteObjectEnd(jw);
                         }
                     }
                 jsonWriteObjectEnd(jw);
                 }
             jsonWriteListEnd(jw);
             }
 
         jsonWriteObjectEnd(jw);  // scripts
         jsonWriteObjectEnd(jw);  // root
 
         // Emit as inline JavaScript
         struct dyString *ds = dyStringNew(1024);
         dyStringPrintf(ds, "var bedDetails = %s;\n", jw->dy->string);
 
-        // Dynamically import and call each plot type's module
+        // Dynamically import and call each plot type's module. The URL carries
+        // ?v=<mtime>, as every other js file does, so that a browser cannot serve a
+        // cached module against newer bedDetails JSON and a mirror cannot pair an old
+        // module with new CGIs. webTimeStampedLinkToResource() errAborts on a missing
+        // file and plotType comes from a hub, so a plotType with no module installed
+        // falls back to the plain path: that leaves a silent failed import as before,
+        // rather than taking the whole details page down over one bad hub setting.
         for (hel = helList; hel != NULL; hel = hel->next)
+            {
+            char modFile[PATH_LEN];
+            safef(modFile, sizeof modFile, "hgc.%s.js", hel->name);
+            char fallBack[PATH_LEN];
+            safef(fallBack, sizeof fallBack, "../js/%s", modFile);
+            char *modUrl = fallBack;
+            char *docRoot = hDocumentRoot();
+            if (docRoot != NULL)
+                {
+                char onDisk[PATH_LEN];
+                safef(onDisk, sizeof onDisk, "%s/js/%s", docRoot, modFile);
+                if (fileExists(onDisk))
+                    modUrl = webTimeStampedLinkToResource(modFile, FALSE);
+                }
             dyStringPrintf(ds, "$(document).ready(function() {\n"
-                "  import('../js/hgc.%s.js').then(function(mod) { mod.%s(bedDetails); });\n"
-                "});\n", hel->name, hel->name);
+                "  import('%s').then(function(mod) { mod.%s(bedDetails); });\n"
+                "});\n", modUrl, hel->name);
+            if (modUrl != fallBack)
+                freeMem(modUrl);
+            }
 
         jsInline(dyStringCannibalize(&ds));
         jsonWriteFree(&jw);
         hashElFreeList(&helList);
         hashFree(&plotTypeHash);
         }
     }
 if (!found)
     {
     printf("No item %s starting at %d\n", emptyForNull(item), start);
     }
 lmCleanup(&lm);
 bbiFileClose(&bbi);
 }