f95037b8f6bb5cad8f0cbd16067e4ebc8cdecaf9
max
  Mon Jul 14 12:00:52 2025 -0700
fixing captcha problem, refs #36057

diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index 8c9c9195e56..a12d1a9b6b1 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -1460,31 +1460,31 @@
     }
 }
 
 boolean isValidToken(char *token)
 /* send https req to cloudflare, check if the token that we got from the captcha is really the one made by cloudflare */
 {
     char *url = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
     char *secret = cfgVal("cloudFlareSecretKey");
     if (!secret)
         errAbort("'cloudFlareSecretKey' must be set in hg.conf if cloudflare is activated in hg.conf");
 
     char data[3000]; // cloudflare token is at most 2000 bytes
     safef(data, sizeof(data), "secret=%s&response=%s", secret, token);
     char *reply = curlPostUrl(url, data);
 
-    boolean res = startsWith("{\"success\":true", reply);
+    boolean res = strstr(reply, "\"success\":true") != NULL;
     freez(&reply);
     return res;
 }
 
 #define CLOUDFLARESITEKEY "cloudFlareSiteKey"
 
 void printCaptcha() 
 /* print an html page that shows the captcha and on success, reloads the page with the token added as token=x */
 {
     char *cfSiteKey = cfgVal(CLOUDFLARESITEKEY);
     if (!cfSiteKey)
         return;
 
     puts("Content-Type:text/html\n"); // puts outputs one newline. Header requires two newlines.
     puts("<html><head>");
@@ -1556,35 +1556,50 @@
     return;
 
 // certain user agents are allowed to use the website without a captcha
 if (isUserAgentException())
     return;
 
 // Do not show a captcha if we have a valid cookie 
 // but for debugging, it's nice to be able to force the captcha
 if (userId && userIdFound && !cgiOptionalString("captcha"))
     return;
 
 // when the captcha is solved, our JS code does a full page-reload, no AJAX. That saves us one round-trip.
 // After the reload, the new page URL has the captcha token in the URL argument list, so now we need to validate it
 // and remove it from the cart
 char *token = cgiOptionalString("token");
-if (token && isValidToken(token))
+if (token)
+{ 
+    if (isValidToken(token))
         {
         cartRemove(cart, "token");
         return;
         }
+    else
+        {
+        puts("Content-Type: text/html\n");
+        puts("<html><body>Internal captcha error: Cloudflare rejected the captcha token. "
+                "Something is not working internally, we are very sorry. You can try reloading the page. "
+                "If this problem persists, send an email to genome-www@soe.ucsc.edu and we will "
+                "look into it as quickly as we can in the PST timezone. You can use any internet browser "
+                "where you have used the genome browser before, but not from this internet browser. "
+                "You can try our mirror sites, "
+                "genome-euro.ucsc.edu or genome-asia.ucsc.edu, while we are working on a solution.</body></html>");
+        exit(0);
+        }
+}
 
 printCaptcha();
 }
 
 void cartRemove(struct cart *cart, char *var);
 
 static void genericCgiSetup()
 /* Run steps that all CGIs must do that unrelated to the cart: timeout, logging setup, UDC.
  */
 {
 static boolean genericSetupDone = FALSE;
 
 // do this only once per execution
 if (genericSetupDone)
     return;