fba8a467a16d0c62f1e21e61e21cf668bacf2ff7 chmalee Wed Jun 7 12:37:07 2023 -0700 htmlEncode error messages coming back when uploading a custom track, refs #31338 diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c index d785cca..dc122a2 100644 --- src/hg/hgCustom/hgCustom.c +++ src/hg/hgCustom/hgCustom.c @@ -1,1449 +1,1447 @@ /* hgCustom - Custom track management CGI. */ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "obscure.h" #include "linefile.h" #include "hash.h" #include "cart.h" #include "cheapcgi.h" #include "web.h" #include "htmshell.h" #include "hdb.h" #include "hui.h" #include "hCommon.h" #include "customTrack.h" #include "customFactory.h" #include "portable.h" #include "errCatch.h" #include "knetUdc.h" #include "udc.h" #include "net.h" #include "jsHelper.h" #include <signal.h> #include "trackHub.h" #include "botDelay.h" #include "chromAlias.h" static long loadTime = 0; static boolean issueBotWarning = FALSE; #define delayFraction 0.25 /* same as hgTracks */ void usage() /* Explain usage and exit. */ { errAbort( "hgCustom - Custom track management CGI\n" "usage:\n" " hgCustom <CGI settings>\n" ); } /* DON'T EDIT THIS -- use CGI param "&measureTiming=." */ static boolean measureTiming = FALSE; #define TEXT_ENTRY_ROWS 7 #define TEXT_ENTRY_COLS 73 #define CONFIG_ENTRY_ROWS 3 #define SAVED_LINE_COUNT 50 /* CGI variables */ #define hgCt "hgct_" /* prefix for all control variables; these are removed at end! */ #define hgCtNoRemove "hgctNoRemove_" /* these are shared with other modules */ #define hgCtDataText CT_CUSTOM_TEXT_ALT_VAR #define hgCtDataFile CT_CUSTOM_FILE_VAR #define hgCtDataFileName CT_CUSTOM_FILE_NAME_VAR #define hgCtDocText CT_CUSTOM_DOC_TEXT_VAR #define hgCtDocFile CT_CUSTOM_DOC_FILE_VAR #define hgCtTable CT_SELECTED_TABLE_VAR #define hgCtUpdatedTable CT_UPDATED_TABLE_VAR /* misc */ #define hgCtUpdatedTrack "hgct_updatedTrack" #define hgCtDeletePrefix "hgct_del" #define hgCtRefreshPrefix "hgct_refresh" #define hgCtConfigLines "hgct_configLines" #define hgCtNavDest hgCtNoRemove "navDest" /* commands */ #define hgCtDo hgCt "do_" /* prefix for all commands */ #define hgCtDoAdd hgCtDo "add" #define hgCtDoDelete hgCtDo "delete" #define hgCtDoDeleteSet hgCtDo "delete_set" #define hgCtDoDeleteClr hgCtDo "delete_clr" #define hgCtDoRefresh hgCtDo "refresh" #define hgCtDoRefreshSet hgCtDo "refresh_set" #define hgCtDoRefreshClr hgCtDo "refresh_clr" #define hgCtDoGenomeBrowser hgCtDo "gb" #define hgCtDoTableBrowser hgCtDo "tb" #ifdef PROGRESS_METER #define hgCtDoProgress hgCtDo "progress" #endif /* Global variables */ struct cart *cart; struct hash *oldVars = NULL; char *excludeVars[] = {"Submit", "submit", "SubmitFile", "ContinueWithWarn", NULL}; char *database = NULL; char *organism = NULL; struct customTrack *ctList = NULL; void makeClearButton(char *field) /* UI button that clears a text field */ { char id[256]; safef(id, sizeof id, "%s_clear", field); char javascript[1024]; safef(javascript, sizeof javascript, "document.mainForm.%s.value = '';", field); cgiMakeOnClickButton(id, javascript, " Clear "); } void addIntro() /* display overview and help message for "add" screen */ { puts(" Data must be formatted in\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigBed.html'>bigBed</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/barChart.html'>bigBarChart</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigChain.html'>bigChain</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigGenePred.html'>bigGenePred</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/interact.html'>bigInteract</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigLolly.html'>bigLolly</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigMaf.html'>bigMaf</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigPsl.html'>bigPsl</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigWig.html'>bigWig</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bam.html'>BAM</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/barChart.html'>barChart</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/vcf.html'>VCF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format1'>BED</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format1.7'>BED detail</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bedgraph.html'>bedGraph</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format13'>broadPeak</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/cram.html'>CRAM</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format3'>GFF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format4'>GTF</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hic.html'>hic</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/interact.html'>interact</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format5'>MAF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format12'>narrowPeak</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format10'>Personal Genome SNP,</A>\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format2'>PSL</A>,\n" " or <A TARGET=_BLANK HREF='../goldenPath/help/wiggle.html'>WIG</A>\n" " formats.<br>\n" " <ul>\n" " <li>You can paste just the URL to the file, without a \"track\" line, for bigBed, bigWig, bigGenePred, BAM and VCF.<br></li>" " <li>To configure the display, set\n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#TRACK'>track</A>\n" " and" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#BROWSER'>browser</A>\n" " line attributes as described in the \n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html'>User's Guide</A>.<br>\n" " Examples are\n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#EXAMPLE1'>here</A>.\n" " If you do not have web-accessible data storage available, please see the\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html#Hosting'>Hosting</A> section of the Track Hub Help documentation.\n<br><br></li>" " </ul>\n" " Please note a much more efficient way to load data is to use\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html'>Track Hubs</A>, which are loaded\n" " from the <A TARGET=_BLANK HREF='hgHubConnect'>Track Hubs Portal</A> found in the menu under My Data.\n" ); } void addCustomForm(struct customTrack *ct, char *err, boolean warnOnly) /* display UI for adding custom tracks by URL or pasting data */ { char *dataUrl = NULL; char buf[1024]; boolean gotClade = FALSE; boolean isUpdateForm = FALSE; if (ct) { isUpdateForm = TRUE; dataUrl = ctDataUrl(ct); } else /* add form needs clade for assembly menu */ gotClade = hGotClade(); jsIncludeFile("jquery.js", NULL); jsIncludeFile("hgCustom.js", NULL); jsIncludeFile("utils.js", NULL); jsIncludeFile("ajax.js", NULL); /* main form */ printf("<FORM ACTION=\"%s\" METHOD=\"%s\" " " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\" id='mainForm'>\n", hgCustomName(), cartUsualString(cart, "formMethod", "POST")); jsOnEventById("submit", "mainForm", "$('input[name=Submit]').attr('disabled', 'disabled');"); cartSaveSession(cart); if (!isUpdateForm) { /* Print clade, genome and assembly */ /* NOTE: this uses an additional, hidden form (orgForm), below */ char *onChangeDb = "document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();"; char *onChangeOrg = "document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();"; char *onChangeClade = "document.orgForm.clade.value = document.mainForm.clade.options[document.mainForm.clade.selectedIndex].value; document.orgForm.org.value = 0; document.orgForm.db.value = 0; document.orgForm.submit();"; puts("<TABLE BORDER=0>\n"); if (gotClade) { puts("<TR><TD>clade\n"); printCladeListHtml(hOrganism(database), "change", onChangeClade); puts(" "); puts("genome\n"); printGenomeListForCladeHtml(database, "change", onChangeOrg); } else { puts("<TR><TD>genome\n"); printGenomeListHtml(database, "change", onChangeOrg); } puts(" "); puts("assembly\n"); printAssemblyListHtml(database, "change", onChangeDb); char *description = hFreezeFromDb(database); if ((description != NULL) && ! stringIn(database, description)) { puts(" "); printf("[%s]", trackHubSkipHubName(database)); } puts("</TD></TR></TABLE>\n"); } /* intro text */ puts("<P>"); if (isUpdateForm) puts("Update your custom track configuration, data, and/or documentation."); else puts("Display your own data as custom annotation tracks in the browser."); addIntro(); puts("<P>"); /* row for error message */ if (isNotEmpty(err)) { - char *fullErrString = replaceChars(err, "\n", "<br>\n"); printf("<P><B> <span style='color:%s; font-style:italic;'>" - "%s</span><P>%s</B><P>", warnOnly ? "ORANGE" : "RED", warnOnly ? "Warning" : "Error", fullErrString); - freeMem(fullErrString); + "%s</span><P>%s</B><P>", warnOnly ? "ORANGE" : "RED", warnOnly ? "Warning" : "Error", htmlEncode(err)); /* send two lines of the message to the apache error log also: */ char *tmpString = replaceChars(err, "\n", " "); fprintf(stderr, "hgCustom load error: %s\n", tmpString); freeMem(tmpString); } cgiSimpleTableStart(); /* first rows for update form are for track and browser line entry */ if (isUpdateForm) { /* row for instructions */ cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); if (dataUrl) puts("Configuration:"); else { puts("Edit configuration:"); } cgiTableFieldEnd(); cgiTableField(" "); puts("<TD ALIGN='RIGHT'>"); if (warnOnly) { cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); printf(" "); jsInline( "$('textarea').change(function() {\n" " $('#ContinueWithWarn').hide();\n" "});\n"); } cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); cgiTableField(" "); cgiTableRowEnd(); /* row for text entry box */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ cgiMakeTextAreaDisableable(hgCtConfigLines, cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)), CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else { cgiMakeTextArea(hgCtConfigLines, cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)), CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS); } cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); cgiTableFieldEnd(); cgiTableRowEnd(); } /* next row - label entry for file upload */ cgiSimpleTableRowStart(); if (isUpdateForm) { /* update existing */ /* extra space */ cgiSimpleTableRowStart(); puts("<TD STYLE='padding-top:9';\"></TD>"); cgiTableRowEnd(); if (dataUrl) cgiTableField("Data:"); else cgiTableField("Paste in replacement data:"); } else cgiTableField("Paste URLs or data:"); if (isUpdateForm && dataUrl) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDataFile); cgiTableFieldEnd(); jsInline( "$(\"[name='hgt.customFile']\").change(function(ev) { \n" " var fname = ev.target.files[0].name; \n" " var ext = fname.split('.').pop().toLowerCase(); \n" " var warnExts = ['bigbed', 'bb', 'bam', 'bigwig', 'bw', 'jpeg', 'pdf', 'jpg', 'png', 'hic', 'cram'];\n" " if (warnExts.indexOf(ext) >= 0) {\n" " alert('You are trying to upload a binary file on this page, but the Genome Browser server needs access to binary files via the internet.'+" " ' Therefore, you will need to store the files on a web server, then paste the URLs to them on this page, or upload a text file with \"track\" lines '+" " ' and configuration settings that point to the file URLs. Please read the documentation'+" " ' referenced at the top of this page or contact us for more information.');\n" " $(\"[name='hgt.customFile']\")[0].value = '';" " }\n" "});\n" ); } if (!isUpdateForm) { cgiSimpleTableFieldStart(); if (warnOnly) { cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); printf(" "); jsInline( "$('textarea').change(function() {\n" " $('#ContinueWithWarn').hide();\n" "});\n"); } cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); } cgiTableRowEnd(); /* next row - text entry box for data, and clear button */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ safef(buf, sizeof buf, "Replace data at URL: %s", ctDataUrl(ct)); cgiMakeTextAreaDisableable(hgCtDataText, buf, TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else { int rows = (isUpdateForm ? TEXT_ENTRY_ROWS - CONFIG_ENTRY_ROWS : TEXT_ENTRY_ROWS); cgiMakeTextArea(hgCtDataText, cartUsualString(cart, hgCtDataText, ""), rows, TEXT_ENTRY_COLS); } cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); if (!(isUpdateForm && dataUrl)) { printf("<span id='loadingMsg'></span>\n"); makeClearButton(hgCtDataText); } cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); cgiTableFieldEnd(); cgiTableRowEnd(); /* extra space */ cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); cgiDown(0.7); cgiTableFieldEnd(); cgiTableRowEnd(); /* next row - label for description text entry */ cgiSimpleTableRowStart(); cgiTableField("Optional track documentation: "); if (isUpdateForm && ctHtmlUrl(ct)) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDocFile); cgiTableFieldEnd(); } cgiTableRowEnd(); /* next row - text entry for description, and clear button(s) */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (ct && ctHtmlUrl(ct)) { safef(buf, sizeof buf, "Replace doc at URL: %s", dataUrl); cgiMakeTextAreaDisableable(hgCtDocText, buf, TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else { cgiMakeTextArea(hgCtDocText, cartUsualString(cart, hgCtDocText, ""), TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); makeClearButton(hgCtDocText); cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); } cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); /* help text at bottom of screen - link for HTML description template */ puts("Click <A HREF=\"../goldenPath/help/ct_description.txt\" TARGET=_blank>here</A> for an HTML document template that may be used for Genome Browser track descriptions."); if (isUpdateForm) { /* hidden variables to identify track */ cgiMakeHiddenVar(hgCtUpdatedTable, ct->tdb->track); char buf[512]; char *shortLabel = htmlEncode(ct->tdb->shortLabel); char *longLabel = htmlEncode(ct->tdb->longLabel); safef(buf, sizeof buf, "track name='%s' description='%s'", shortLabel, longLabel); char *trackLine = htmlEncode(ctOrigTrackLine(ct)); cgiMakeHiddenVar(hgCtUpdatedTrack, trackLine ? trackLine : buf); freeMem(trackLine); freeMem(shortLabel); freeMem(longLabel); } else { /* hidden form to handle clade/genome/assembly dropdown. * This is at end of page for layout reasons (preserve vertical space) */ puts("</FORM>"); printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); if (gotClade) printf("<INPUT TYPE=\"HIDDEN\" NAME=\"clade\" VALUE=\"\">\n"); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"hgct_do_add\" VALUE=\"1\">\n"); } puts("</FORM>"); cgiDown(0.9); } void tableHeaderFieldStart(int columns) { /* print table column header with white text on black background */ printf("<TD COLSPAN=%d ALIGN='CENTER' BGCOLOR='#536ED3'>", columns); } void tableHeaderField(char *label, char *description) { /* print table column header with white text on black background */ puts("<TD ALIGN='CENTER' BGCOLOR='#536ED3' "); if (description) printf("TITLE='%s'", description); printf("><B>%s</B></TD> ", wrapWhiteFont(label)); } void showCustomTrackList(struct customTrack *ctList, int numCts) /* print table of custom tracks with controls */ { struct customTrack *ct; char buf[256]; char *pos = NULL; char *dataUrl; int colSpan = 4; /* handle 'set all' and 'clr all' (won't be used if user has javascript enabled). */ boolean setAllDelete = FALSE; boolean setAllUpdate = FALSE; if (cartVarExists(cart, hgCtDoDeleteSet)) setAllDelete = TRUE; if (cartVarExists(cart, hgCtDoRefreshSet)) setAllUpdate = TRUE; /* determine which columns to display (avoid empty columns) */ int updateCt = 0, itemCt = 0, posCt = 0, errCt = 0; for (ct = ctList; ct != NULL; ct = ct->next) { if (ctDataUrl(ct)) updateCt++; if (ctItemCount(ct) > 0) itemCt++; if (ctInitialPosition(ct) || ctFirstItemPos(ct)) posCt++; if (ct->networkErrMsg) errCt++; } hTableStart(); cgiSimpleTableRowStart(); tableHeaderField("Name", "Short track identifier"); tableHeaderField("Description", "Long track identifier"); tableHeaderField("Type", "Data format of track"); tableHeaderField("Doc", "HTML track description"); if (itemCt) { tableHeaderField("Items", "Count of discrete items in track"); colSpan++; } if (posCt) { tableHeaderField("Pos"," Go to genome browser at default track position or first item"); colSpan++; } if (errCt) { tableHeaderField("Error"," Error in custom track"); colSpan++; } boolean showAllButtons = FALSE; if (numCts > 3) showAllButtons = TRUE; tableHeaderFieldStart(showAllButtons ? 2 : 1); cgiMakeButtonWithMsg(hgCtDoDelete, "delete", "Remove custom track"); cgiTableFieldEnd(); /* add column with Update button if any custom tracks are updateable */ if (updateCt) { tableHeaderFieldStart(showAllButtons ? 2 : 1); cgiMakeButtonWithMsg(hgCtDoRefresh, "update", "Refresh from data URL"); cgiTableFieldEnd(); } cgiTableRowEnd(); int butCount=0; for (ct = ctList; ct != NULL; ct = ct->next) { /* Name field */ char *shortLabel = htmlEncode(ct->tdb->shortLabel); if ((ctDataUrl(ct) && ctHtmlUrl(ct)) || sameString(ct->tdb->type, "chromGraph")) printf("<TR><TD>%s</A></TD>", shortLabel); else { char *cgiName = cgiEncode(ct->tdb->track); printf("<TR><TD><A TITLE='Update custom track: %s' HREF='%s?%s&%s=%s'>%s</A></TD>", shortLabel, hgCustomName(),cartSidUrlString(cart), hgCtTable, cgiName, shortLabel); freeMem(cgiName); } freeMem(shortLabel); /* Description field */ char *longLabel = htmlEncode(ct->tdb->longLabel); printf("<TD>%s</TD>", longLabel); freeMem(longLabel); /* Type field */ printf("<TD>%s</TD>", ctInputType(ct)); /* Doc field */ printf("<TD ALIGN='CENTER'>%s</TD>", isNotEmpty(ct->tdb->html) ? "Y" : " "); /* Items field */ if (itemCt) { int count = ctItemCount(ct); if (count > 0) printf("<TD ALIGN='CENTER'>%d</TD>", count); else puts("<TD> </TD>"); } /* Pos field; indicates initial position for the track, * or first element */ if (posCt) { pos = ctInitialPosition(ct); if (!pos) pos = ctFirstItemPos(ct); if (pos) { char *chrom = cloneString(pos); chopSuffixAt(chrom, ':'); if (hgOfficialChromName(database, chrom)) printf("<TD><A HREF='%s?%s&position=%s&hgTracksConfigPage=notSet' TITLE=%s>%s:</A></TD>", hgTracksName(), cartSidUrlString(cart),pos, pos, chrom); else puts("<TD> </TD>"); } else puts("<TD> </TD>"); } if (errCt) { if (ct->networkErrMsg) { char id[256]; safef(id, sizeof id, "_%d", butCount); printf("\n<TD><A href='#' id='%s'>Show</A></TD>\n", id); jsOnEventByIdF("click", id, "alert('%s');return false;", javaScriptLiteralEncode(ct->networkErrMsg)); } else puts("<TD> </TD>"); } /* Delete checkboxes */ printf("<TD COLSPAN=%d ALIGN=CENTER>", showAllButtons ? 2 : 1); safef(buf, sizeof(buf), "%s_%s", hgCtDeletePrefix, ct->tdb->track); cgiMakeCheckBoxMore(buf, setAllDelete, "class='deleteCheckbox'"); puts("</TD>"); /* Update checkboxes */ if (updateCt) { printf("<TD COLSPAN=%d ALIGN=CENTER>", showAllButtons ? 2 : 1); safef(buf, sizeof(buf), "%s_%s", hgCtRefreshPrefix, ct->tdb->track); if ((dataUrl = ctDataUrl(ct)) != NULL) { char more[2048]; safef(more, sizeof(more), "class='updateCheckbox' title='refresh data from: %s'", dataUrl); cgiMakeCheckBoxMore(buf, setAllUpdate, more); } else puts(" "); puts("</TD>"); } puts("</TR>\n"); } if (showAllButtons) { cgiSimpleTableRowStart(); printf("<TD COLSPAN=%d ALIGN='RIGHT'>check all / clear all </TD>", colSpan); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoDeleteSet, "+", "Select all for deletion", "$('.deleteCheckbox').attr('checked', true); return false;"); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoDeleteClr, "-", "Clear all for deletion", "$('.deleteCheckbox').attr('checked', false); return false;"); cgiTableFieldEnd(); if (updateCt) { cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoRefreshSet, "+", "Select all for update", "$('.updateCheckbox').attr('checked', true); return false;"); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoRefreshClr, "-", "Clear all for update", "$('.updateCheckbox').attr('checked', false); return false;"); cgiTableFieldEnd(); } cgiTableRowEnd(); } hTableEnd(); } struct dbDb *getCustomTrackDatabases() /* Get list of databases having custom tracks for this user. * Dispose of this with dbDbFreeList. */ { struct dbDb *dbList = NULL, *dbDb; char *db; /* Get list of assemblies with custom tracks */ struct hashEl *hels = cartFindPrefix(cart, CT_FILE_VAR_PREFIX); struct hashEl *hel = NULL; for (hel = hels; hel != NULL; hel = hel->next) { /* TODO: chop actual prefix */ db = chopPrefixAt(cloneString(hel->name), '_'); /* TODO: check if file exists, if not remove ctfile_ var */ dbDb = hDbDb(db); if (dbDb) slAddTail(&dbList, dbDb); } return dbList; } static struct slPair *makeOtherCgiValsAndLabels() /* Return {value, label} pairs with other CGIs the user might wish to jump to. */ { struct slPair *valsAndLabels = slPairNew(hgTracksName(), "Genome Browser"); slAddHead(&valsAndLabels, slPairNew(hgTablesName(), "Table Browser")); slAddHead(&valsAndLabels, slPairNew(hgVaiName(), "Variant Annotation Integrator")); slAddHead(&valsAndLabels, slPairNew(hgIntegratorName(), "Data Integrator")); slReverse(&valsAndLabels); return valsAndLabels; } static void makeOtherCgiForm(char *pos) /* Make a form for navigating to other CGIs. */ { struct slPair *valsAndLabels = makeOtherCgiValsAndLabels(); // Default to the first CGI in the menu. char *defaultCgi = valsAndLabels->name; char *selected = cartUsualString(cart, hgCtNavDest, defaultCgi); printf("<FORM STYLE=\"margin-bottom:0;\" METHOD=\"GET\" NAME=\"navForm\" ID=\"navForm\"" " ACTION=\"%s\">\n", selected); cartSaveSession(cart); if (pos) cgiMakeHiddenVar("position", pos); printf("view in "); // Construct a menu of destination CGIs puts(cgiMakeSingleSelectDropList(hgCtNavDest, valsAndLabels, selected, NULL, NULL, "change", "var newVal = $('#navSelect').val(); $('#navForm').attr('action', newVal);", NULL, "navSelect")); cgiMakeButton("submit", "go to first annotation"); puts(" <input type='submit' name='submit' id='submitGoBack' value='return to current position'>"); jsOnEventByIdF("click", "submitGoBack", "$('#navForm > [name=position]').remove()"); puts("</FORM>"); } static void manageCustomForm(char *warnMsg) /* list custom tracks and display checkboxes so user can select for delete */ { struct dbDb *dbList = getCustomTrackDatabases(); struct dbDb *dbDb = NULL; /* add this database to the list, as it may have no custom * tracks, but we still want to see it in the menu */ slAddTail(&dbList, hDbDb(database)); slReverse(&dbList); /* remove duplicate entry for this database, if any */ for (dbDb = dbList->next; dbDb != NULL; dbDb = dbDb->next) if (sameString(dbDb->name, database)) slRemoveEl(&dbList, dbDb); boolean assemblyMenu = FALSE; if (slCount(dbList) > 1) assemblyMenu = TRUE; if (assemblyMenu) { /* hidden form to handle genome/assembly dropdown */ printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); puts("</FORM>"); } /* the main form contains a table of all tracks, with checkboxes to delete */ printf("<FORM ACTION=\"%s\" METHOD=\"%s\" NAME=\"mainForm\">\n", hgCustomName(), cartUsualString(cart, "formMethod", "POST")); cartSaveSession(cart); if (assemblyMenu) { /* Print clade, genome and assembly */ char *onChangeDb = "document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();"; char *onChangeOrg = "document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();"; puts("<TABLE BORDER=0>\n"); puts("<TR><TD>genome\n"); printSomeGenomeListHtml(database, dbList, "change", onChangeOrg); puts(" "); puts("assembly\n"); printSomeAssemblyListHtml(database, dbList, "change", onChangeDb); puts(" "); printf("[%s]", database); puts("</TD></TR></TABLE><P>\n"); } else { char *assemblyName = hFreezeDateOpt(database); if (assemblyName == NULL) assemblyName = "default"; printf("<B>genome:</B> %s <B>assembly:</B> %s [%s]\n", organism, assemblyName, database); } if (measureTiming && (loadTime > 0)) printf("\n<BR>load time: %ld ms<BR>\n", loadTime); /* place for warning messages to appear */ if (isNotEmpty(warnMsg)) { char *encoded = htmlEncode(warnMsg); printf("<P><B> %s", encoded); freeMem(encoded); } /* count up number of custom tracks for this genome */ int numCts = slCount(ctList); cgiSimpleTableStart(); cgiSimpleTableRowStart(); if (numCts) { puts("<TD VALIGN=\"TOP\">"); showCustomTrackList(ctList, numCts); } else puts("<TD VALIGN=\"TOP\"><B><EM>No custom tracks for this genome:<B></EM> "); puts("</TD>"); /* end mainForm; navigation menu has its own form. */ puts("</FORM>"); puts("<TD VALIGN=\"TOP\">"); puts("<TABLE BORDER=0>"); /* determine if there's a navigation position for this screen */ char *pos = NULL; if (ctList) { pos = ctInitialPosition(ctList); if (!pos) pos = ctFirstItemPos(ctList); } puts("<TR><TD>"); makeOtherCgiForm(pos); puts("</TD></TR>"); /* button to add custom tracks */ puts("<TR><TD>"); printf("<INPUT TYPE=SUBMIT NAME=\"addTracksButton\" ID=\"addTracksButton\" VALUE=\"%s\" " "STYLE=\"margin-top: 5px\" >\n", "add custom tracks"); // This submits mainForm with a hidden input that tells hgCustom to show add tracks page: jsOnEventByIdF("click", "addTracksButton", "var $form = $(\"form[name='mainForm']\"); " "$form.append(\"<input name='%s' type='hidden'>\"); " "$form.submit();" , hgCtDoAdd); puts("</TD></TR>"); puts("</TABLE>"); puts("</TD>"); cgiTableRowEnd(); cgiTableEnd(); // This vertically aligns the 'add tracks' button with the other-CGI select jsInline( "function fitUnder($el1, $el2) { " " var off1 = $el1.offset(); " " var off2 = $el2.offset(); " " off2.left = off1.left; " " $el2.offset(off2);" " $el2.width($el1.width()); " "};" "$(document).ready(function () { fitUnder($('#navSelect'), $('#addTracksButton')); });\n" ); cartSetString(cart, "hgta_group", "user"); } void helpCustom() /* display documentation */ { webNewSection("Loading Custom Tracks"); char *browserVersion; if (btIE == cgiClientBrowser(&browserVersion, NULL, NULL) && *browserVersion < '8') puts("<span>"); else puts("<span style='position:relative; top:-1em;'>"); webIncludeHelpFile("customTrackLoad", FALSE); puts("</span>"); } void doBrowserLines(struct slName *browserLines, char **retErr) /* parse variables from browser lines into the cart */ { char *err = NULL; struct slName *bl; for (bl = browserLines; bl != NULL; bl = bl->next) { char *words[96]; int wordCount; wordCount = chopLine(bl->name, words); if (wordCount > 1) { char *command = words[1]; if (sameString(command, "hide") || sameString(command, "dense") || sameString(command, "pack") || sameString(command, "squish") || sameString(command, "full")) { if (wordCount > 2) { int i; for (i=2; i<wordCount; ++i) { char *s = words[i]; if (sameWord(s, "all")) { cartSetString(cart, "hgt.visAllFromCt", command); } else { char buf[256]; safef(buf, sizeof buf, "hgtct.%s", s); cartSetString(cart, buf, command); } } } } else if (sameString(command, "position")) { char *chrom = NULL; int start = 0, end = 0; if (wordCount < 3) { err = "Expecting 3 words in browser position line"; break; } if (!hgParseChromRange(database, words[2], &chrom, &start, &end) || start < 0 || end > hChromSize(database, chrom)) { err ="Invalid browser position (use chrN:123-456 format)"; break; } cartSetString(cart, "position", words[2]); } } } if (retErr) *retErr = err; } #ifdef PROGRESS_METER static void progressMeter() { printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"hgct_do_add\" VALUE=\"1\">\n"); puts("</FORM>"); } static void doProgress(char *err) /* display progress meter to show loading process */ { cartWebStart(cart, database, "Custom Track loading progress meter"); progressMeter(); // addCustomForm(NULL, err); helpCustom(); cartWebEnd(cart); } #endif static void webBotWarning() /* display the overuse warning message in the javaScript warning text box * html output has already started at this point, just need to add the * warning handler and setup the warning javaScript box, warnings after * this will go to that text box */ { pushWarnHandler(webVaWarn); htmlWarnBoxSetup(stdout); char *ip = getenv("REMOTE_ADDR"); botDelayMessage(ip, botDelayMillis); } void doAddCustom(char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { cartWebStart(cart, database, "Add Custom Tracks"); addCustomForm(NULL, err, warnOnly); helpCustom(); if (issueBotWarning) webBotWarning(); cartWebEnd(cart); } void doUpdateCustom(struct customTrack *ct, char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { char *longLabel = htmlEncode(ct->tdb->longLabel); cartWebStart(cart, database, "Update Custom Track: %s [%s]", longLabel, database); freeMem(longLabel); cartSetString(cart, hgCtDocText, ct->tdb->html); addCustomForm(ct, err, warnOnly); helpCustom(); cartWebEnd(cart); } static void doManageCustom(char *warnMsg) /* display form for deleting & updating custom tracks. * Include warning message, if any */ { cartWebStart(cart, database, "Manage Custom Tracks"); jsIncludeFile("jquery.js", NULL); manageCustomForm(warnMsg); webNewSection("Managing Custom Tracks"); webIncludeHelpFile("customTrackManage", FALSE); if (issueBotWarning) webBotWarning(); cartWebEnd(cart); } char *fixNewData(struct cart *cart) /* append a newline to incoming data, to keep custom preprocessor happy */ { char *customText = cartUsualString(cart, hgCtDataText, ""); if (isNotEmpty(customText)) { struct dyString *ds = dyStringNew(0); dyStringPrintf(ds, "%s\n", customText); customText = dyStringCannibalize(&ds); cartSetString(cart, hgCtDataText, customText); } return customText; } char *replacedTracksMsg(struct customTrack *replacedCts) /* make warning message listing replaced tracks */ { struct customTrack *ct; if (!slCount(replacedCts)) return NULL; struct dyString *dsWarn = dyStringNew(0); dyStringAppend(dsWarn, "Replaced: "); for (ct = replacedCts; ct != NULL; ct = ct->next) { if (ct != replacedCts) /* not the first */ dyStringAppend(dsWarn, ", "); dyStringAppend(dsWarn, ct->tdb->shortLabel); } return dyStringCannibalize(&dsWarn); } void doDeleteCustom() /* remove custom tracks from list based on cart variables */ { struct customTrack *ct; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtDeletePrefix, ct->tdb->track); if (cartBoolean(cart, var)) slRemoveEl(&ctList, ct); } } void doRefreshCustom(char **warnMsg) /* reparse custom tracks from URLs based on cart variables */ { struct customTrack *ct; struct customTrack *replacedCts = NULL; struct customTrack *refreshCts = NULL; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtRefreshPrefix, ct->tdb->track); if (cartUsualBoolean(cart, var, FALSE)) { struct customTrack *nextCt = NULL, *urlCt = NULL; struct customTrack *urlCts = customFactoryParse(database, ctDataUrl(ct), FALSE, NULL); for (urlCt = urlCts; urlCt != NULL; urlCt = nextCt) { nextCt = urlCt->next; if (sameString(ct->tdb->track, urlCt->tdb->track)) slAddHead(&refreshCts, urlCt); } } } ctList = customTrackAddToList(ctList, refreshCts, &replacedCts, FALSE); if (warnMsg) *warnMsg = replacedTracksMsg(replacedCts); customTrackHandleLift(database, ctList); } void addWarning(struct dyString *ds, char *msg) /* build up a warning message from parts */ { if (!msg) return; if (isNotEmpty(ds->string)) dyStringAppend(ds, ". "); dyStringAppend(ds, msg); } char *saveLines(char *text, int max) /* save lines from input, up to 'max'. * Prepend with comment, if truncated */ { if (!text) return NULL; char buf[128]; int count = 0; char *line; boolean truncated = FALSE; struct dyString *ds = dyStringNew(0); safef(buf, sizeof buf, "# Displaying first %d lines of data", max); struct lineFile *lf = lineFileOnString("saved custom text", TRUE, text); while (lineFileNext(lf, &line, NULL)) { if (startsWith(buf, line)) continue; if (++count > max) { truncated = TRUE; break; } dyStringAppend(ds, line); dyStringAppend(ds, "\n"); } if (truncated) { struct dyString *dsNew = dyStringNew(0); dyStringPrintf(dsNew, "%s\n%s", buf, dyStringCannibalize(&ds)); return dyStringCannibalize(&dsNew); } return (dyStringCannibalize(&ds)); } struct customTrack *ctFromList(struct customTrack *ctList, char *track) /* return custom track from list */ { struct customTrack *ct = NULL; for (ct = ctList; ct != NULL; ct = ct->next) if (sameString(track, ct->tdb->track)) return ct; return NULL; } boolean customTrackHasConfig(char *text) /* determine if there are track or browser lines in text */ { text = skipLeadingSpaces(text); return startsWith("track ", text) || startsWith("browser ", text); } int timerCounter; #define TIMER_INTERVAL 10 static void timer(int sig) { // Per HTML 4.01 spec (http://www.w3.org/TR/html401/struct/global.html#h-7.1): // // White space (spaces, newlines, tabs, and comments) may appear before or after each section [including the DOCTYPE]. // // So we print out comments periodically to keep this process from being killed by apache or the user's web browser. printf("<!-- processing (%d seconds) -->\n", timerCounter++ * TIMER_INTERVAL); fflush(stdout); alarm(TIMER_INTERVAL); } void doMiddle(struct cart *theCart) /* create web page */ { char *ctFileName = NULL; struct slName *browserLines = NULL; struct customTrack *replacedCts = NULL; char *err = NULL, *warnMsg = NULL; char *selectedTable = NULL; struct customTrack *ct = NULL; boolean ctUpdated = FALSE; char *initialDb = NULL; long thisTime = clock1000(); cart = theCart; measureTiming = isNotEmpty(cartOptionalString(cart, "measureTiming")); initialDb = cloneString(cartUsualString(cart, "db", "")); getDbAndGenome(cart, &database, &organism, oldVars); chromAliasSetup(database); customFactoryEnableExtraChecking(TRUE); knetUdcInstall(); if (udcCacheTimeout() < 300) udcSetCacheTimeout(300); if (sameString(initialDb, "0")) { /* when an organism is selected from the custom track management page, * set the database to be the default only if it has custom tracks. * Otherwise, pick an assembly for that organism that does have custom tracks. */ struct dbDb *dbDb, *dbDbs = getCustomTrackDatabases(); char *dbWithCts = NULL; for (dbDb = dbDbs; dbDb != NULL; dbDb = dbDb->next) { if (sameString(database, dbDb->name)) break; if (sameString(organism, dbDb->organism)) { if (!dbWithCts) dbWithCts = cloneString(dbDb->name); } } if (dbWithCts) // set the database for the selected organism to an assembly that { // has custom tracks database = dbWithCts; cartSetString(cart, "db", database); } } if (cartVarExists(cart, hgCtDoAdd)) doAddCustom(NULL, FALSE); #ifdef PROGRESS_METER else if (cartVarExists(cart, hgCtDoProgress)) { doProgress(NULL); } #endif else if (cartVarExists(cart, hgCtTable)) { /* update track */ /* need to clone the hgCtTable value, as the ParseCart will remove the variable */ selectedTable = cloneString(cartString(cart, hgCtTable)); if (isNotEmpty(selectedTable)) { ctList = customTracksParseCart(database, cart, NULL, NULL); ct = ctFromList(ctList, selectedTable); } if (ct) doUpdateCustom(ct, NULL, FALSE); else doAddCustom(NULL, FALSE); } else { /* get new and existing custom tracks from cart and decide what to do */ // setup a timer to periodically print out something to stdout to make sure apache or the web browser doesn't time us out (see redmine #3002). // e.g. see http://stackoverflow.com/questions/5547166/how-to-avoid-cgi-timeout struct sigaction *act; AllocVar(act); act->sa_handler = timer; act->sa_flags = SA_RESTART; sigaction(SIGALRM, act, NULL); alarm(TIMER_INTERVAL); boolean warnOnly = FALSE; char *customText = fixNewData(cart); /* save input so we can display if there's an error */ char *savedCustomText = saveLines(cloneString(customText), SAVED_LINE_COUNT); char *trackConfig = cartOptionalString(cart, hgCtConfigLines); char *savedConfig = cloneString(trackConfig); struct dyString *dsWarn = dyStringNew(0); char *fileName = cartOptionalString(cart, hgCtDataFileName); boolean hasData = (isNotEmpty(customText) || isNotEmpty(fileName)); if (cartVarExists(cart, hgCtUpdatedTrack) && hasData) { /* from 'update' screen */ /* prepend config to data for parser */ struct dyString *dsTrack = dyStringNew(0); if (!trackConfig) trackConfig = cartOptionalString(cart, hgCtUpdatedTrack); char *fileContents = NULL; if (isNotEmpty(fileName)) { if (customTrackIsCompressed(fileName)) fileContents = "Compressed files not supported for data update"; else fileContents = cartOptionalString(cart, hgCtDataFile); customText = fileContents; } /* check for duplicate track config in config and data entry */ if (customTrackHasConfig(trackConfig) && customTrackHasConfig(customText)) { if (startsWith(trackConfig, customText)) trackConfig = ""; else customText = "Duplicate track configuration found - remove track and browser lines from Configuration box or from Data"; } dyStringPrintf(dsTrack, "%s\n%s\n", trackConfig, customText); customText = dyStringCannibalize(&dsTrack); cartSetString(cart, hgCtDataText, customText); if (isNotEmpty(fileContents)) { /* already handled file */ cartRemove(cart, hgCtDataFile); cartRemove(cart, hgCtDataFileName); } } warnOnly = FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); } if (isNotEmpty(catch->message->string)) { addWarning(dsWarn, catch->message->string); } errCatchFree(&catch); /* exclude special setting used by table browser to indicate * db assembly for error-handling purposes only */ char *db = NULL; if (trackConfig && (db = stringIn("db=", trackConfig)) != NULL) { db += 3; char *nextTok = skipToSpaces(db); if (!nextTok) nextTok = strchr(db, 0); db = cloneStringZ(db,nextTok-db); stripChar(db, '\''); stripChar(db, '"'); if (!sameString(db,database)) err = "Invalid configuration found - remove db= or return it to its original value. "; } if (cartVarExists(cart, hgCtUpdatedTrack) && !hasData) { /* update custom track config and doc, but not data*/ selectedTable = cartOptionalString(cart, hgCtUpdatedTable); if (selectedTable) { ct = ctFromList(ctList, selectedTable); if (ct) { struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) { customTrackUpdateFromConfig(ct, database, trackConfig, &browserLines); ctUpdated = TRUE; } errCatchEnd(catch); if (isNotEmpty(catch->message->string)) err = catTwoStrings(emptyForNull(err), catch->message->string); errCatchFree(&catch); } } } addWarning(dsWarn, replacedTracksMsg(replacedCts)); doBrowserLines(browserLines, &warnMsg); addWarning(dsWarn, warnMsg); if (err && !(warnOnly && cartVarExists(cart, "ContinueWithWarn"))) { char *selectedTable = NULL; cartSetString(cart, hgCtDataText, savedCustomText); cartSetString(cart, hgCtConfigLines, savedConfig); if ((selectedTable= cartOptionalString(cart, hgCtUpdatedTable)) != NULL) { ct = ctFromList(ctList, selectedTable); doUpdateCustom(ct, err, warnOnly); } else doAddCustom(err, warnOnly); cartRemovePrefix(cart, hgCt); return; } if (cartVarExists(cart, hgCtDoDelete)) { doDeleteCustom(); ctUpdated = TRUE; } if (cartVarExists(cart, hgCtDoRefresh)) { doRefreshCustom(&warnMsg); addWarning(dsWarn, warnMsg); ctUpdated = TRUE; } if (ctUpdated || ctConfigUpdate(ctFileName)) { customTracksSaveCart(database, cart, ctList); /* refresh ctList again to pickup remote resource error state */ warnOnly=FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); addWarning(dsWarn, catch->message->string); } errCatchFree(&catch); } warnMsg = dyStringCannibalize(&dsWarn); if (measureTiming) { long lastTime = clock1000(); loadTime = lastTime - thisTime; } if (ctList || cartVarExists(cart, hgCtDoDelete)) doManageCustom(warnMsg); else doAddCustom(warnMsg, warnOnly); } cartRemovePrefix(cart, hgCt); cartRemove(cart, CT_CUSTOM_TEXT_VAR); } int main(int argc, char *argv[]) /* Process command line. */ { long enteredMainTime = clock1000(); issueBotWarning = earlyBotCheck(enteredMainTime, "hgCustom", delayFraction, 0, 0, "html"); htmlPushEarlyHandlers(); oldVars = hashNew(10); cgiSpoof(&argc, argv); cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars); cgiExitTime("hgCustom", enteredMainTime); return 0; }