6002bdaa995565598fecea8123804878062db7b1
jcasper
  Wed Jun 10 15:16:05 2026 -0700
Do sessionDb and userDb load after checking captcha so that invalid bot
traffic doesn't run up the id counts, refs #37739

diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index 8bc70610a10..f3a741988e9 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -1542,31 +1542,31 @@
     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 */
 {
 return botExceptionUserAgent();
 }
 
-void forceUserIdOrCaptcha(struct cart* cart, char *userId, boolean userIdFound, boolean fromCommandLine)
+void forceUserIdOrCaptcha(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 in a CGI run. Can happen if cartNew() is called somewhere else in a CGI a second time
 if (captchaCheckDone)
     return;
 
 captchaCheckDone = TRUE;
 
 if (fromCommandLine || isEmpty(cfgOption(CLOUDFLARESITEKEY)))
     return;
 
 // no captcha for our own QA scripts running on a server with our IP address
@@ -1596,31 +1596,31 @@
     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)
 {
     if (isValidToken(token))
         {
-        cartRemove(cart, "token");
+        cgiVarExclude("token");
         // Drop any IP-tracking rows for this hguid so a legitimate user
         // who roams networks isn't repeatedly captcha-gated.
         if (cfgOptionBooleanDefault("hguidIpTracking.enabled", FALSE) && isNotEmpty(userId))
             {
             unsigned long userIdNum = cartDbParseId(userId, NULL);
             if (userIdNum != 0)
                 {
                 struct sqlConnection *conn = hConnectCentralNoCache();
                 char *table = cfgOptionDefault("hguidIpTracking.table", "hguidIpAccess");
                 char query[256];
                 sqlSafef(query, sizeof(query),
                          "DELETE FROM %s WHERE userId=%lu", table, userIdNum);
                 sqlUpdate(conn, query);
                 sqlDisconnect(&conn);
                 }
@@ -1670,43 +1670,49 @@
 /* Load up cart from user & session id's.  Exclude is a null-terminated list of
  * strings to not include */
 {
 genericCgiSetup();
 
 struct cart *cart;
 struct sqlConnection *conn = cartDefaultConnector();
 char *ex;
 boolean userIdFound = FALSE, sessionIdFound = FALSE;
 
 AllocVar(cart);
 cart->hash = newHash(12);
 cart->exclude = newHash(7);
 cart->userId = userId;
 cart->sessionId = sessionId;
-cart->userInfo = loadDb(conn, userDbTable(), userId, &userIdFound);
 
 boolean fromCli = cgiWasSpoofed(); // QA runs our CGIs from the command line and we debug from there
 
-forceUserIdOrCaptcha(cart, userId, userIdFound, fromCli);
-// Load sessionDb info *after* forceUserIdOrCaptcha.  loadDb will create a new record if it doesn't
-// find a matching one, and we don't need bot traffic filling our sessionDb table with junk.
-cart->sessionInfo = loadDb(conn, sessionDbTable(), sessionId, &sessionIdFound);
+boolean isValidHguid(char *cookieUserId); // external import from botDelay.c
+userIdFound = isValidHguid(userId);
+
+forceUserIdOrCaptcha(userId, userIdFound, fromCli);
+
+// Load userDb and sessionDb info *after* forceUserIdOrCaptcha.  loadDb will create a new record
+// if it doesn't find a matching one, and we don't need bot traffic filling our tables with junk
 
 // we rely on the cookie being validated later, so if user requested to reset the cookie settings
 // load the settings after the captcha has been checked
 if ( cgiOptionalString("ignoreCookie") != NULL )
     cart->userInfo = loadDb(conn, userDbTable(), NULL, &userIdFound);
+else
+    cart->userInfo = loadDb(conn, userDbTable(), userId, &userIdFound);
+
+cart->sessionInfo = loadDb(conn, sessionDbTable(), sessionId, &sessionIdFound);
 
 if (sessionIdFound)
     cartParseOverHash(cart, cart->sessionInfo->contents);
 else if (userIdFound)
     cartParseOverHash(cart, cart->userInfo->contents);
 else
     {
     char *defaultCartContents = getDefaultCart(conn);
     cartParseOverHash(cart, defaultCartContents);
     }
 char when[1024];
 safef(when, sizeof(when), "open %s %s", userId, sessionId);
 cartTrace(cart, when, conn);
 
 loadCgiOverHash(cart, oldVars);
@@ -1775,30 +1781,31 @@
             struct hashEl *hel;
             if ((hel = hashLookup(oldVars,"db")) != NULL)
                 hel->val = "none";
             else
                 hashAdd(oldVars, "db", "none");
             }
         cartSetString(cart,"db", newDatabase);
         }
     }
 
 if (exclude != NULL)
     {
     while ((ex = *exclude++))
 	cartExclude(cart, ex);
     }
+cartRemove(cart, "token"); // cleaning up captcha token if it slipped into the cart
 
 cartDefaultDisconnector(&conn);
 
 if (didSessionLoad)
     cartHideDefaultTracks(cart);
 return cart;
 }
 
 
 
 static void updateOne(struct sqlConnection *conn,
 	char *table, struct cartDb *cdb, char *contents, int contentSize)
 /* Update cdb in database. */
 {
 struct dyString *dy = dyStringNew(4096);