44ccfacbe3a3d4b300f80d48651c77837a4b571e galt Tue Apr 26 11:12:02 2022 -0700 SQL INJECTION Prevention Version 2 - this improves our methods by making subclauses of SQL that get passed around be both easy and correct to use. The way that was achieved was by getting rid of the obscure and not well used functions sqlSafefFrag and sqlDyStringPrintfFrag and replacing them with the plain versions of those functions, since these are not needed anymore. The new version checks for NOSQLINJ in unquoted %-s which is used to include SQL clauses, and will give an error the NOSQLINJ clause is not present, and this will automatically require the correct behavior by developers. sqlDyStringPrint is a very useful function, however because it was not enforced, users could use various other dyString functions and they operated without any awareness or checking for SQL correct use. Now those dyString functions are prohibited and it will produce an error if you try to use a dyString function on a SQL string, which is simply detected by the presence of the NOSQLINJ prefix. diff --git src/hg/hgc/virusClick.c src/hg/hgc/virusClick.c index d2800eb..e52cc1c 100644 --- src/hg/hgc/virusClick.c +++ src/hg/hgc/virusClick.c @@ -1,626 +1,626 @@ /* virusClick - hgc code for a prototype virus browser on the h1n1 genome */ /* Copyright (C) 2013 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "hgc.h" #include "virusClick.h" #include "hCommon.h" #include "hgConfig.h" #include "linefile.h" #include "dystring.h" #include "lsSnpPdbChimera.h" #include "jksql.h" #include "net.h" #include "trashDir.h" #include "htmshell.h" static void h1n1DownloadPdb(char *item, char *pdbUrl, struct tempName *tmpPdb) /* uncompress PDB to trash */ { int inFd = netOpenHttpExt(pdbUrl, "GET", NULL); int inFdRedir = 0; char *pdbUrlRedir = NULL; if (!netSkipHttpHeaderLinesHandlingRedirect(inFd, pdbUrl, &inFdRedir, &pdbUrlRedir)) errAbort("Unable to access predicted 3D structure file: %s", pdbUrl); if (pdbUrlRedir != NULL) { close(inFd); inFd = inFdRedir; freez(&pdbUrlRedir); } trashDirFile(tmpPdb, "hgct", item, ".pdb"); FILE *outFh = mustOpen(tmpPdb->forCgi, "w"); struct lineFile *inLf = lineFileDecompressFd(pdbUrl, TRUE, inFd); char *line; while (lineFileNext(inLf, &line, NULL)) { fputs(line, outFh); fputc('\n', outFh); } lineFileClose(&inLf); carefulClose(&outFh); } static void h1n1MkChimeraxTrashFullUrl(struct tempName *tmpPdb, char *fullUrl, int fullUrlSize) /* generate full URL for a chimerax file decompressed to trash */ { // FIXME: this is not generate (URL generation will not work on all web servers). // if this is kept, should address this. char *serverName = getenv("SERVER_NAME"); char *serverPort = getenv("SERVER_PORT"); char *scriptName = getenv("SCRIPT_NAME"); if ((serverName != NULL) && (serverPort != NULL) && (scriptName != NULL)) { // remote url safef(fullUrl, fullUrlSize, "http://%s", serverName); if (!sameString(serverPort, "80")) { safecat(fullUrl, fullUrlSize, ":"); safecat(fullUrl, fullUrlSize, serverPort); } safecat(fullUrl, fullUrlSize, scriptName); char *p = strrchr(fullUrl, '/'); if (p != NULL) *++p = '\0'; // drop cgi name, keeping directory safecat(fullUrl, fullUrlSize, tmpPdb->forHtml); } else { // local url safef(fullUrl, fullUrlSize, "file:///%s/%s", getCurrentDir(), tmpPdb->forHtml); } } static char *mkChimeraPyScript(char *scriptFile) /* create chimera python script from commands in scriptFile */ { struct dyString *buf = dyStringNew(0); dyStringAppend(buf, "from chimera import runCommand\n"); struct lineFile *lf = lineFileOpen(scriptFile, TRUE); char *line; while (lineFileNextReal(lf, &line)) dyStringPrintf(buf, "runCommand(\"%s\")\n", line); lineFileClose(&lf); return dyStringCannibalize(&buf); } static void mkChimerax(char *item, char *pdbUrl, char *scriptFile, struct tempName *chimerax) /* generate a chimerax file for downloading h1n1 PDB structure */ { // chimera doesn't handle compressed files via URL, so uncompress into the trash struct tempName tmpPdb; char usePdbUrl[PATH_LEN]; if (endsWith(pdbUrl, ".gz") || endsWith(pdbUrl, ".Z")) { h1n1DownloadPdb(item, pdbUrl, &tmpPdb); h1n1MkChimeraxTrashFullUrl(&tmpPdb, usePdbUrl, sizeof(usePdbUrl)); } else safecpy(usePdbUrl, sizeof(usePdbUrl), pdbUrl); char *pyScript = NULL; if (scriptFile != NULL) pyScript = mkChimeraPyScript(scriptFile); lsSnpPdbChimeraGenericLink(usePdbUrl, pyScript, "hgct", item, chimerax); freeMem(pyScript); } static void tempNameFromPrefix(struct tempName *name, struct tempName *prefix, char *suffix) /* build a tempName based on an existing tempName and a suffix */ { safecpy(name->forCgi, sizeof(name->forCgi), prefix->forCgi); safecat(name->forCgi, sizeof(name->forCgi), suffix); safecpy(name->forHtml, sizeof(name->forHtml), prefix->forHtml); safecat(name->forHtml, sizeof(name->forHtml), suffix); } static char *getH1n1StructDir() /* get location of h1n1 modeling directory */ { static char *dir = NULL; if (dir == NULL) { dir = cfgOption("gisaid.structDir"); if (dir == NULL) errAbort("gisaid.structDir not set in hg.conf"); } return dir; } static char *getH1n1StructUrl() /* get URL of h1n1 modeling directory */ { static char *url = NULL; if (url == NULL) { url = cfgOption("gisaid.structUrl"); if (url == NULL) errAbort("gisaid.structUrl not set in hg.conf"); } return url; } static boolean getH1n1Model(char *gene, char *pdbUrl) /* Find model PDB file URL, return false if does not exist. URL is * return in pdbUrl if it is not NULL */ { char relPath[PATH_LEN], pdbPath[PATH_LEN]; if (sameString(gene, "HA")) safef(relPath, sizeof(relPath), "%s/%s", gene, "consensus.uncleaved.D18-G519.pdb"); else if (sameString(gene, "NA")) safef(relPath, sizeof(relPath), "%s/%s", gene, "consensus.tetramer.V83-I467.pdb"); else return FALSE; if (pdbUrl != NULL) safef(pdbUrl, PATH_LEN, "%s/%s", getH1n1StructUrl(), relPath); safef(pdbPath, sizeof(pdbPath), "%s/%s", getH1n1StructDir(), relPath); return fileExists(pdbPath); } static void mkH1n1StructData(char *gene, char *idPairFile, char *highlightId, struct tempName *imageFile, struct tempName *chimeraScript) /* generate 3D structure files; difference highlighting is generate only idPairFile or * idPairFile, if specified, but not both. */ { struct tempName prefix; trashDirFile(&prefix, "hgct", gene, "tmp"); char idFile[PATH_LEN], idArg[PATH_LEN], logFile[PATH_LEN], cmd[2*PATH_LEN]; idArg[0] = '\0'; if ((idPairFile != NULL) || (highlightId != NULL)) { safef(idFile, sizeof(idFile), "%s.ids", prefix.forCgi); if (idPairFile != NULL) { // extract first column safef(cmd, sizeof(cmd), "cut -f 1 %s >%s", idPairFile, idFile); if (system(cmd) != 0) errAbort("extracting protein ids failed: %s", cmd); } else { FILE *fh = mustOpen(idFile, "w"); fprintf(fh, "%s\n", highlightId); carefulClose(&fh); } safef(idArg, sizeof(idArg), "--ids %s", idFile); } // dynamic_highlight.pl knows locations of model files safef(logFile, sizeof(logFile), "%s.log", prefix.forCgi); safef(cmd, sizeof(cmd), "%s/dynamic_highlight.pl --rasmol --chimera --protein %s --consensus 0602 %s --base %s >%s 2>&1", getH1n1StructDir(), gene, idArg, prefix.forCgi, logFile); if (system(cmd) != 0) errAbort("creation of 3D structure highlight files failed: %s", cmd); // output names are all predefined by script relative to prefix tempNameFromPrefix(imageFile, &prefix, "_highlight.jpg"); tempNameFromPrefix(chimeraScript, &prefix, "_highlight.cmd"); } static void showProtH1n1(char *item, char *geneSymbol) { char query2[256]; struct sqlResult *sr2; char **row2; struct sqlConnection *conn2 = hAllocConn(database); // char *subjId, *dnaSeqId; unused variable char *aaSeqId= NULL; char *gene=NULL; char cond_str[256]; char *predFN; char *homologID; char *SCOPdomain; char *chain; char goodSCOPdomain[40]; int first = 1; float eValue; char *chp; int homologCount; int gotPDBFile = 0; sqlSafef(query2, sizeof(query2), "select subjId, dnaSeqId, aaSeqId, gene from gisaidXref where dnaSeqId='%s'", item); sr2 = sqlMustGetResult(conn2, query2); row2 = sqlNextRow(sr2); if (row2 != NULL) { // subjId = strdup(row2[0]); unused variable // dnaSeqId = strdup(row2[1]); unused variable aaSeqId = strdup(row2[2]); gene = strdup(row2[3]); } else { errAbort("%s not found.", item); } sqlFreeResult(&sr2); printf("<H3>Protein Structure Analysis and Prediction</H3>"); printf("<B>Comparison to 1918 Flu Virus:</B> "); printf("<A HREF=\"%s/%s/%s/1918_%s.mutate", getH1n1StructUrl(), gene, aaSeqId, aaSeqId); printf("\" TARGET=_blank>%s</A><BR>\n", aaSeqId); printf("<B>Comparison to A H1N1 gene %s concensus:</B> ", gene); printf("<A HREF=\"%s/%s/%s/consensus_%s.mutate", getH1n1StructUrl(), gene, aaSeqId, aaSeqId); printf("\" TARGET=_blank>%s</A><BR>\n", aaSeqId); printf("<BR><B>3D Structure Prediction of %s concensus sequence (with variation of sequence %s highlighted):", geneSymbol, item); printf("<BR>PDB file:</B> "); char pdbUrl[PATH_LEN]; safef(pdbUrl, sizeof(pdbUrl), "%s/%s/decoys/%s.try1-opt3.pdb.gz", getH1n1StructUrl(), item, item); // Modeller stuff char modelPdbUrl[PATH_LEN]; if (getH1n1Model(gene, modelPdbUrl)) { struct tempName imageFile, chimeraScript, chimerax; mkH1n1StructData(gene, NULL, aaSeqId, &imageFile, &chimeraScript); mkChimerax(gene, modelPdbUrl, chimeraScript.forCgi, &chimerax); printf("<A HREF=\"%s\" TARGET=_blank>%s</A>, view with <A HREF=\"%s\">Chimera</A><BR>\n", modelPdbUrl, gene, chimerax.forHtml); printf("<TABLE>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><img src=\"%s\"></TD>", imageFile.forHtml); printf("</TR>\n"); printf("</TABLE>\n"); } return; gotPDBFile = 0; -sqlSafefFrag(cond_str, sizeof(cond_str), "proteinID='%s' and evalue <1.0e-5;", item); +sqlSafef(cond_str, sizeof(cond_str), "proteinID='%s' and evalue <1.0e-5;", item); printf("<TABLE>\n"); printf("<TR><TD ALIGN=\"center\">Front</TD>\n"); printf("<TD ALIGN=\"center\">Top</TD>\n"); printf("<TD ALIGN=\"center\">Side</TD>\n"); printf("</TR>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view1_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view2_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view3_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("</TR>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view1_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view2_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view3_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("</TR>\n"); printf("</TABLE>\n"); printf("<BR><B>Detailed results of SAM-T02:</B> "); printf("<A HREF=\"%s/%s/summary.html", getH1n1StructUrl(), item); printf("\" TARGET=_blank>%s</A><BR>\n", item); /* by pass the following additional processing for now, until two necessary tables are built */ hFreeConn(&conn2); return; if (sqlGetField(database, "protHomolog", "proteinID", cond_str) != NULL) { - sqlSafefFrag(cond_str, sizeof(cond_str), "proteinID='%s'", item); + sqlSafef(cond_str, sizeof(cond_str), "proteinID='%s'", item); predFN = sqlGetField(database, "protPredFile", "predFileName", cond_str); if (predFN != NULL) { printf("<A HREF=\"../SARS/%s/", item); /* printf("%s.t2k.undertaker-align.pdb\">%s</A><BR>\n", item,item); */ printf("%s\">%s</A><BR>\n", predFN,item); gotPDBFile = 1; } } if (!gotPDBFile) { printf("No high confidence level structure prediction available for this sequence."); printf("<BR>\n"); } printf("<B>3D Structure of Close Homologs:</B> "); homologCount = 0; strcpy(goodSCOPdomain, "dummy"); conn2= hAllocConn(database); sqlSafef(query2, sizeof(query2), "select homologID,eValue,SCOPdomain,chain from sc1.protHomolog where proteinID='%s' and evalue <= 0.01;", item); sr2 = sqlMustGetResult(conn2, query2); row2 = sqlNextRow(sr2); if (row2 != NULL) { while (row2 != NULL) { homologID = row2[0]; sscanf(row2[1], "%e", &eValue); SCOPdomain = row2[2]; chp = SCOPdomain+strlen(SCOPdomain)-1; while (*chp != '.') chp--; *chp = '\0'; chain = row2[3]; if (eValue <= 1.0e-10) strcpy(goodSCOPdomain, SCOPdomain); else { if (strcmp(goodSCOPdomain,SCOPdomain) != 0) goto skip; else if (eValue > 0.1) goto skip; } if (first) first = 0; else printf(", "); printf("<A HREF=\"http://www.rcsb.org/pdb/cgi/explore.cgi?job=graphics&pdbId=%s", homologID); if (strlen(chain) >= 1) printf("\"TARGET=_blank>%s(chain %s)</A>", homologID, chain); else printf("\"TARGET=_blank>%s</A>", homologID); homologCount++; skip: row2 = sqlNextRow(sr2); } } hFreeConn(&conn2); sqlFreeResult(&sr2); if (homologCount == 0) printf("None<BR>\n"); printf("<BR><B>Details:</B> "); printf("<A HREF=\"../SARS/%s/summary.html", item); printf("\" TARGET=_blank>%s</A><BR>\n", item); htmlHorizontalLine(); } void showSAM_h1n1(char *item) { char query2[256]; struct sqlResult *sr2; char **row2; struct sqlConnection *conn2 = hAllocConn(database); char cond_str[256]; char *predFN; char *homologID; char *SCOPdomain; char *chain; char goodSCOPdomain[40]; int first = 1; float eValue; char *chp; int homologCount; int gotPDBFile = 0; printf("<H3>Protein Structure Analysis and Prediction by "); printf("<A HREF=\"http://www.soe.ucsc.edu/research/compbio/SAM_T02/sam-t02-faq.html\""); printf(" TARGET=_blank>SAM-T02</A></H3>\n"); printf("<B>Multiple Alignment:</B> "); printf("<A HREF=\"%s/%s/summary.html#alignment", getH1n1StructUrl(), item); printf("\" TARGET=_blank>%s</A><BR>\n", item); printf("<B>Secondary Structure Predictions:</B> "); printf("<A HREF=\"%s/%s/summary.html#secondary-structure", getH1n1StructUrl(), item); printf("\" TARGET=_blank>%s</A><BR>\n", item); printf("<B>3D Structure Prediction (PDB file):</B> "); char pdbUrl[PATH_LEN]; safef(pdbUrl, sizeof(pdbUrl), "%s/%s/decoys/%s.try1-opt3.pdb.gz", getH1n1StructUrl(), item, item); struct tempName chimerax; mkChimerax(item, pdbUrl, NULL, &chimerax); printf("<A HREF=\"%s\" TARGET=_blank>%s</A>, view with <A HREF=\"%s\">Chimera</A><BR>\n", pdbUrl, item, chimerax.forHtml); gotPDBFile = 0; -sqlSafefFrag(cond_str, sizeof(cond_str), "proteinID='%s' and evalue <1.0e-5;", item); +sqlSafef(cond_str, sizeof(cond_str), "proteinID='%s' and evalue <1.0e-5;", item); printf("<TABLE>\n"); printf("<TR><TD ALIGN=\"center\">Front</TD>\n"); printf("<TD ALIGN=\"center\">Top</TD>\n"); printf("<TD ALIGN=\"center\">Side</TD>\n"); printf("</TR>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view1_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view2_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><img src=\"%s/%s/%s.undertaker-align.view3_200.jpg\"></TD>", getH1n1StructUrl(), item, item); printf("</TR>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view1_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view2_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("<TD ALIGN=\"center\"><A HREF=\"%s/%s/%s.undertaker-align.view3_500.jpg\">500x500</A></TD>", getH1n1StructUrl(), item, item); printf("</TR>\n"); printf("</TABLE>\n"); printf("<BR><B>Detailed results of SAM-T02:</B> "); printf("<A HREF=\"%s/%s/summary.html", getH1n1StructUrl(), item); printf("\" TARGET=_blank>%s</A><BR>\n", item); /* by pass the following additional processing for now, until two necessary tables are built */ hFreeConn(&conn2); return; if (sqlGetField(database, "protHomolog", "proteinID", cond_str) != NULL) { - sqlSafefFrag(cond_str, sizeof(cond_str), "proteinID='%s'", item); + sqlSafef(cond_str, sizeof(cond_str), "proteinID='%s'", item); predFN = sqlGetField(database, "protPredFile", "predFileName", cond_str); if (predFN != NULL) { printf("<A HREF=\"../SARS/%s/", item); /* printf("%s.t2k.undertaker-align.pdb\">%s</A><BR>\n", item,item); */ printf("%s\">%s</A><BR>\n", predFN,item); gotPDBFile = 1; } } if (!gotPDBFile) { printf("No high confidence level structure prediction available for this sequence."); printf("<BR>\n"); } printf("<B>3D Structure of Close Homologs:</B> "); homologCount = 0; strcpy(goodSCOPdomain, "dummy"); conn2= hAllocConn(database); sqlSafef(query2, sizeof(query2), "select homologID,eValue,SCOPdomain,chain from sc1.protHomolog where proteinID='%s' and evalue <= 0.01;", item); sr2 = sqlMustGetResult(conn2, query2); row2 = sqlNextRow(sr2); if (row2 != NULL) { while (row2 != NULL) { homologID = row2[0]; sscanf(row2[1], "%e", &eValue); SCOPdomain = row2[2]; chp = SCOPdomain+strlen(SCOPdomain)-1; while (*chp != '.') chp--; *chp = '\0'; chain = row2[3]; if (eValue <= 1.0e-10) strcpy(goodSCOPdomain, SCOPdomain); else { if (strcmp(goodSCOPdomain,SCOPdomain) != 0) goto skip; else if (eValue > 0.1) goto skip; } if (first) first = 0; else printf(", "); printf("<A HREF=\"http://www.rcsb.org/pdb/cgi/explore.cgi?job=graphics&pdbId=%s", homologID); if (strlen(chain) >= 1) printf("\"TARGET=_blank>%s(chain %s)</A>", homologID, chain); else printf("\"TARGET=_blank>%s</A>", homologID); homologCount++; skip: row2 = sqlNextRow(sr2); } } hFreeConn(&conn2); sqlFreeResult(&sr2); if (homologCount == 0) printf("None<BR>\n"); printf("<BR><B>Details:</B> "); printf("<A HREF=\"../SARS/%s/summary.html", item); printf("\" TARGET=_blank>%s</A><BR>\n", item); htmlHorizontalLine(); } void doH1n1Seq(struct trackDb *tdb, char *item) /* Show extra info for H1N1 Seq Annotations track. */ { struct sqlConnection *conn = hAllocConn(database); struct sqlResult *sr; char query[256]; char **row; char *geneSymbol=NULL; genericHeader(tdb, item); sqlSafef(query, sizeof query, "select seqId, geneSymbol, strain, islId from h1n1SeqXref where seqId = '%s'", item); sr = sqlGetResult(conn, query); if ((row = sqlNextRow(sr)) != NULL) { char *seqId, *strain, *islId; seqId = row[0]; geneSymbol = row[1]; strain = row[2]; islId = row[3]; printf("<B>Sequence ID: %s</B> <BR>", seqId); printf("<B>Gene: %s</B> <BR>", geneSymbol); printf("<B>Strain: %s</B> <BR>", strain); printf("<B>Isolate: </B> "); printf("<A HREF=\"../cgi-bin/gisaidSample?hgs_sample=%s&submit=Go\">%s</A>", islId, islId); } htmlHorizontalLine(); //showSAM_h1n1(item); showProtH1n1(item, geneSymbol); htmlHorizontalLine(); printTrackHtml(tdb); sqlFreeResult(&sr); hFreeConn(&conn); } void doH1n1Gene(struct trackDb *tdb, char *item) /* Show details page for H1N1 Genes and Regions annotations track. */ { struct sqlConnection *conn = hAllocConn(database); struct sqlResult *sr; char query[256]; char **row; char *chrom, *chromStart, *chromEnd; char *gene=NULL; genericHeader(tdb, item); gene = item; printf("<B>Gene: </B> %s\n<BR>", gene); sqlSafef(query, sizeof query, "select chrom, chromStart, chromEnd from h1n1Gene where name='%s';", gene); sr = sqlMustGetResult(conn, query); row = sqlNextRow(sr); if (row != NULL) { chrom = row[0]; chromStart = row[1]; chromEnd = row[2]; printPosOnChrom(chrom, atoi(chromStart), atoi(chromEnd), NULL, FALSE, item); } sqlFreeResult(&sr); hFreeConn(&conn); htmlHorizontalLine(); printf("<H3>Protein Structure Analysis and Prediction</H3>"); printf("<B>3D Structure Prediction of consensus sequence (with variations of all selected sequences highlighted):"); printf("<BR>PDB file:</B> "); char pdbUrl[PATH_LEN]; safef(pdbUrl, sizeof(pdbUrl), "%s/%s/decoys/%s.try1-opt3.pdb.gz", getH1n1StructUrl(), item, item); // Modeller stuff char modelPdbUrl[PATH_LEN]; if (getH1n1Model(gene, modelPdbUrl)) { char *selectFile = cartOptionalString(cart, gisaidAaSeqList); struct tempName imageFile, chimeraScript, chimerax; mkH1n1StructData(gene, selectFile, NULL, &imageFile, &chimeraScript); mkChimerax(gene, modelPdbUrl, chimeraScript.forCgi, &chimerax); printf("<A HREF=\"%s\" TARGET=_blank>%s</A>, view with <A HREF=\"%s\">Chimera</A><BR>\n", modelPdbUrl, gene, chimerax.forHtml); printf("<TABLE>\n"); printf("<TR>\n"); printf("<TD ALIGN=\"center\"><img src=\"%s\"></TD>", imageFile.forHtml); printf("</TR>\n"); printf("</TABLE>\n"); } htmlHorizontalLine(); printTrackHtml(tdb); sqlFreeResult(&sr); hFreeConn(&conn); }