d34f2504803125515dc89955a60ad8366bca732e
max
  Tue Jun 24 02:53:18 2025 -0700
captcha exceptions trigger error log line, and moving curl code into lib/curlWrap.c, refs #35790

diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index 7b5c6214570..ce298cca279 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -26,32 +26,31 @@
 #endif /* GBROWSE */
 #include "hgMaf.h"
 #include "hui.h"
 #include "geoMirror.h"
 #include "hubConnect.h"
 #include "trackHub.h"
 #include "cgiApoptosis.h"
 #include "customComposite.h"
 #include "regexHelper.h"
 #include "windowsToAscii.h"
 #include "jsonWrite.h"
 #include "verbose.h"
 #include "genark.h"
 #include "quickLift.h"
 #include "botDelay.h"
-
-#include <curl/curl.h>
+#include "curlWrap.h"
 
 static char *sessionVar = "hgsid";	/* Name of cgi variable session is stored in. */
 static char *positionCgiName = "position";
 
 DbConnector cartDefaultConnector = hConnectCart;
 DbDisconnect cartDefaultDisconnector = hDisconnectCart;
 static boolean cartDidContentType = FALSE;
 
 struct slPair *httpHeaders = NULL; // A list of headers to output before the content-type
 
 static void hashUpdateDynamicVal(struct hash *hash, char *name, void *val)
 /* Val is a dynamically allocated (freeMem-able) entity to put
  * in hash.  Override existing hash item with that name if any.
  * Otherwise make new hash item. */
 {
@@ -1441,74 +1440,30 @@
 else
     {
     char *url = genarkUrl(db);
 
     if (url != NULL)
         {
         cartSetString(cart, "genome", db);
         cartAddString(cart, "hubUrl", url);
         cartRemove(cart, "db");
         }
     else if (!hDbIsActive(db))
 	errAbort("Can not find database '%s'", db);
     }
 }
 
-// ------ libify this in the next release ----
-//
-struct curlString {
-    char *ptr;
-    size_t len;
-};
-void init_string(struct curlString *s) {
-    s->len = 0;
-    s->ptr = malloc(1);
-    s->ptr[0] = '\0';
-}
-
-size_t writefunc(void *ptr, size_t size, size_t nmemb, void *userData) {
-    struct curlString *s = (struct curlString *)userData;
-    size_t new_len = s->len + size * nmemb;
-    s->ptr = realloc(s->ptr, new_len + 1);
-    memcpy(s->ptr + s->len, ptr, size * nmemb);
-    s->ptr[new_len] = '\0';
-    s->len = new_len;
-    return size * nmemb;
-}
-
-char* curlPostUrl(char *url, char *data)
-/* post data to URL and return as string. Must be freed. */
-{
-CURL *curl = curl_easy_init();
-if (!curl) 
-    errAbort("Cannot init curl library");
-
-struct curlString response;
-init_string(&response);
-
-curl_easy_setopt(curl, CURLOPT_URL, url);
-curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
-curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
-curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
-curl_easy_perform(curl);
-curl_easy_cleanup(curl);
-
-char *resp = cloneString(response.ptr);
-free(response.ptr);
-return resp;
-}
-
 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);
     freez(&reply);
     return res;
@@ -1552,32 +1507,35 @@
 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\n", agent);
         return TRUE;
         }
+    }
 
 return FALSE;
 }
 
 void forceUserIdOrCaptcha(struct cart* cart, char *userId, boolean userIdFound, boolean fromCommandLine)
 /* print captcha is user did not sent a valid hguid cookie or a valid
  * cloudflare token. Allow certain IPs and user-agents. */
 {
 if (fromCommandLine || isEmpty(cfgOption(CLOUDFLARESITEKEY)))
     return;
 
 // no captcha for our own QA scripts running on a server with our IP address
 if (botException())
     return;
 
@@ -1598,35 +1556,36 @@
     cartRemove(cart, "token");
     return;
 }
 
 printCaptcha();
 }
 
 void cartRemove(struct cart *cart, char *var);
 
 struct cart *cartNew(char *userId, char *sessionId,
                      char **exclude, struct hash *oldVars)
 /* Load up cart from user & session id's.  Exclude is a null-terminated list of
  * strings to not include */
 {
 cgiApoptosisSetup();
-if (cfgOptionBooleanDefault("showEarlyErrors", TRUE))
+if (cfgOptionBooleanDefault("showEarlyErrors", FALSE))
     errAbortSetDoContentType(TRUE);
 
 if (cfgOptionBooleanDefault("suppressVeryEarlyErrors", FALSE))
     htmlSuppressErrors();
+
 setUdcCacheDir();
 
 netSetTimeoutErrorMsg("A connection timeout means that either the server is offline or its firewall, the UCSC firewall or any router between the two blocks the connection.");
 
 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);