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/blatServers.c src/hg/lib/blatServers.c index d1c23eee321..067b9ec475b 100644 --- src/hg/lib/blatServers.c +++ src/hg/lib/blatServers.c @@ -1,146 +1,231 @@ /* blatServers.c was originally generated by the autoSql program, which also * generated blatServers.h and blatServers.sql. This module links the database and * the RAM representation of objects. */ #include "common.h" #include "linefile.h" #include "dystring.h" #include "jksql.h" +#include "hdb.h" +#include "hgConfig.h" +#include "trackHub.h" #include "blatServers.h" char *blatServersCommaSepFieldNames = "db,host,port,isTrans,canPcr,dynamic"; void blatServersStaticLoad(char **row, struct blatServers *ret) /* Load a row from blatServers table into ret. The contents of ret will * be replaced at the next call to this function. */ { ret->db = row[0]; ret->host = row[1]; ret->port = sqlSigned(row[2]); ret->isTrans = sqlSigned(row[3]); ret->canPcr = sqlSigned(row[4]); ret->dynamic = sqlSigned(row[5]); } struct blatServers *blatServersLoad(char **row) /* Load a blatServers from row fetched with select * from blatServers * from database. Dispose of this with blatServersFree(). */ { struct blatServers *ret; AllocVar(ret); ret->db = cloneString(row[0]); ret->host = cloneString(row[1]); ret->port = sqlSigned(row[2]); ret->isTrans = sqlSigned(row[3]); ret->canPcr = sqlSigned(row[4]); ret->dynamic = sqlSigned(row[5]); return ret; } struct blatServers *blatServersLoadAll(char *fileName) /* Load all blatServers from a whitespace-separated file. * Dispose of this with blatServersFreeList(). */ { struct blatServers *list = NULL, *el; struct lineFile *lf = lineFileOpen(fileName, TRUE); char *row[6]; while (lineFileRow(lf, row)) { el = blatServersLoad(row); slAddHead(&list, el); } lineFileClose(&lf); slReverse(&list); return list; } struct blatServers *blatServersLoadAllByChar(char *fileName, char chopper) /* Load all blatServers from a chopper separated file. * Dispose of this with blatServersFreeList(). */ { struct blatServers *list = NULL, *el; struct lineFile *lf = lineFileOpen(fileName, TRUE); char *row[6]; while (lineFileNextCharRow(lf, chopper, row, ArraySize(row))) { el = blatServersLoad(row); slAddHead(&list, el); } lineFileClose(&lf); slReverse(&list); return list; } struct blatServers *blatServersCommaIn(char **pS, struct blatServers *ret) /* Create a blatServers out of a comma separated string. * This will fill in ret if non-null, otherwise will * return a new blatServers */ { char *s = *pS; if (ret == NULL) AllocVar(ret); ret->db = sqlStringComma(&s); ret->host = sqlStringComma(&s); ret->port = sqlSignedComma(&s); ret->isTrans = sqlSignedComma(&s); ret->canPcr = sqlSignedComma(&s); ret->dynamic = sqlSignedComma(&s); *pS = s; return ret; } void blatServersFree(struct blatServers **pEl) /* Free a single dynamically allocated blatServers such as created * with blatServersLoad(). */ { struct blatServers *el; if ((el = *pEl) == NULL) return; freeMem(el->db); freeMem(el->host); freez(pEl); } void blatServersFreeList(struct blatServers **pList) /* Free a list of dynamically allocated blatServers's */ { struct blatServers *el, *next; for (el = *pList; el != NULL; el = next) { next = el->next; blatServersFree(&el); } *pList = NULL; } void blatServersOutput(struct blatServers *el, FILE *f, char sep, char lastSep) /* Print out blatServers. Separate fields with sep. Follow last field with lastSep. */ { if (sep == ',') fputc('"',f); fprintf(f, "%s", el->db); if (sep == ',') fputc('"',f); fputc(sep,f); if (sep == ',') fputc('"',f); fprintf(f, "%s", el->host); if (sep == ',') fputc('"',f); fputc(sep,f); fprintf(f, "%d", el->port); fputc(sep,f); fprintf(f, "%d", el->isTrans); fputc(sep,f); fprintf(f, "%d", el->canPcr); fputc(sep,f); fprintf(f, "%d", el->dynamic); fputc(lastSep,f); } /* -------------------------------- End autoSql Generated Code -------------------------------- */ +struct blatServerParams *findBlatServer(char *db, boolean isTrans) +/* Return gfServer connection parameters for the given assembly, or NULL if + * none is configured. Handles both hub and non-hub assemblies. */ +{ +struct blatServerParams *st; +AllocVar(st); + +if (trackHubDatabase(db)) + { + char *host = NULL, *port = NULL, *genomeDataDir = NULL; + if (!trackHubGetBlatParams(db, isTrans, &host, &port, &genomeDataDir)) + { + freez(&st); + return NULL; + } + st->db = cloneString(db); + st->genome = cloneString(hGenome(db)); + st->host = host; + st->port = port; + st->isTrans = isTrans; + struct trackHubGenome *hg = trackHubGetGenome(db); + st->nibDir = cloneString(hg->twoBitPath); + char *slash = strrchr(st->nibDir, '/'); + if (slash != NULL) + *slash = 0; + if (genomeDataDir != NULL) + { + st->isDynamic = TRUE; + st->genomeDataDir = cloneString(genomeDataDir); + } + return st; + } + +struct sqlConnection *conn = hConnectCentral(); +char query[512]; +char dbActualName[64]; + +/* Accept either a db name or a description (hgBlat allows both). */ +sqlSafef(query, sizeof(query), "select name from dbDb where name = '%s'", db); +if (!sqlExists(conn, query)) + { + sqlSafef(query, sizeof(query), + "select name from dbDb where description = '%s'", db); + if (sqlQuickQuery(conn, query, dbActualName, sizeof(dbActualName)) != NULL) + db = dbActualName; + } + +char *blatServersTbl = cfgOptionDefault("blatServersTbl", "blatServers"); +boolean haveDynamic = sqlColumnExists(conn, blatServersTbl, "dynamic"); +sqlSafef(query, sizeof(query), + "select dbDb.name, dbDb.description, blatServers.host, blatServers.port, " + "dbDb.nibPath, %s " + "from dbDb, %s blatServers " + "where blatServers.isTrans = %d and dbDb.name = '%s' " + "and dbDb.name = blatServers.db", + (haveDynamic ? "blatServers.dynamic" : "0"), + blatServersTbl, isTrans, db); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +if (row == NULL) + { + sqlFreeResult(&sr); + hDisconnectCentral(&conn); + freez(&st); + return NULL; + } +st->db = cloneString(row[0]); +st->genome = cloneString(row[1]); +st->host = cloneString(row[2]); +st->port = cloneString(row[3]); +st->nibDir = hReplaceGbdbSeqDir(row[4], st->db); +st->isTrans = isTrans; +if (atoi(row[5])) + { + st->isDynamic = TRUE; + st->genomeDataDir = cloneString(st->db); + } +sqlFreeResult(&sr); +hDisconnectCentral(&conn); +return st; +} +