2197f6d5208aff4c48ccbe42e61a116d988ac392
max
  Tue May 19 08:23:54 2026 -0700
hubApi: add /blat endpoint with apiKey gating, format=hgblat, and known-agent bypass

New src/hg/hubApi/blat.c implements /blat/<type> (dna, protein, transRna,
transDna, guess) backed by the same gfServer logic as hgBlat.  Key details:

- Requires an apiKey for rate-limiting; botException() and
botExceptionUserAgent() exempt IPs/user-agents in hg.conf (same
policy as captcha bypass elsewhere in the browser stack).
- Invalid apiKey returns a clean JSON 403 rather than an HTML 500
(pre-validated in hubApi.c main() before hgBotDelayTimeFrac runs).
- Extra bot-delay fraction (default 0.3, 10x hubApi default) is
configurable via hubApi.blatDelayFraction in hg.conf.
- format=text/psl  -> PSL text; format=hgblat -> byte-for-byte
hgBlat?output=json shape; jsonOutputArrays=1 -> hubApi envelope
with arrays (parallel to getData behaviour); default -> objects.
- botExceptionUserAgent() carved out of cart.c's static
isUserAgentException() into botDelay.c so non-cart callers can use it.
- Cross-reference comments added in hgBlat.c and blat.c noting the
shared logic so fixes get applied to both.

refs #36315

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index 99340caa55b..f37ac597263 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -1539,49 +1539,31 @@
     puts("</head><body>");
     puts("<style>body, h1, h2, h3, h4, h5, h6  { font-family: Helvetica, Arial, sans-serif; }</style>\n");
     puts("<h4>The Genome Browser is protecting itself from bots. This will just take a few seconds.</h4>");
     puts("<small>To make programmatic queries, see our FAQ: https://genome.ucsc.edu/FAQ/FAQdownloads.html#CAPTCHA.</small>");
     puts("");
     puts("<script src='https://challenges.cloudflare.com/turnstile/v0/api.js?onload=showWidget' async defer></script>");
     puts("<div id='myWidget'></div>");
     puts("</body></html>");
     sqlCleanupAll(); // we are wondering about hanging connections, so just in case, close them.
     exit(0);
 }
 
 static boolean isUserAgentException()
 /* return true if HTTP user-agent is in list of exceptions in hg.conf */
 {
-char *agent = cgiUserAgent();
-if (!agent)
-    return FALSE;
-
-struct slName *excStrs = cfgValsWithPrefix("noCaptchaAgent.");
-if (!excStrs)
-    return FALSE;
-
-struct excReStr;
-for (struct slName *sl = excStrs;  sl != NULL;  sl = sl->next)
-    {
-    if (regexMatch(agent, sl->name))
-        {
-        fprintf(stderr, "CAPTCHAPASS %s matches %s\n", agent, sl->name);
-        return TRUE;
-        }
-    }
-
-return FALSE;
+return botExceptionUserAgent();
 }
 
 void forceUserIdOrCaptcha(struct cart* cart, char *userId, boolean userIdFound, boolean fromCommandLine)
 /* print captcha if user did not sent a valid hguid cookie or a valid
  * cloudflare token. Allow certain IPs and user-agents. */
 {
 static boolean captchaCheckDone = FALSE;
 
 // No need to do this again. Can happen if cartNew() is called somewhere else in a CGI
 if (captchaCheckDone)
     return;
 
 captchaCheckDone = TRUE;
 
 if (fromCommandLine || isEmpty(cfgOption(CLOUDFLARESITEKEY)))