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);