7c0082ff8679670a4ba25c181c8af06ae25e0773
max
  Fri Sep 18 07:35:08 2026 -0700
docs update

diff --git src/hg/lib/geoMirror.c src/hg/lib/geoMirror.c
index 996dc803799..595a01b75ea 100644
--- src/hg/lib/geoMirror.c
+++ src/hg/lib/geoMirror.c
@@ -1,389 +1,322 @@
 /* geoMirror - support geographic based mirroring (e.g. euro and asia nodes) */
 
 /* 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 "geoMirror.h"
 #include "hgConfig.h"
 #include "internet.h"
 
 /* geographic server (mirror) support
 
    Customize 'Mirrors' drop-down menu based on current server.  If the server
    is a UCSC-sponsored site (USA/Ca or European), show this in drop-down menu
    with a checkmark, and allow user to change server.  Based on design notes here:
         http://genomewiki.ucsc.edu/genecats/index.php/Euronode
 
    NOTE: Uses hgcentral.gbNode table
    to populate menu items and locate redirects.  This implementation uses the
    spec from above wiki page:
         browser.node=1 -> US server (genome.ucsc.edu)
         browser.node=2 -> European server (genome-euro.ucsc.edu)
         browser.node=3 -> Asian server (genome-asia.ucsc.edu)
 
 
    For Testing, use browser.geoSuffix: e.g.
         browser.geoSuffix=Test
    will cause it to use in hgcentral the tables  gbNodeTest and geoIpNodeTest instead.
    See the genomewiki link above for more documentation on testing.
 
 */
 
 
 boolean geoMirrorEnabled()
 {
 // return TRUE if this site has geographic mirroring turned on
 return geoMirrorNode() != NULL;
 }
 
 char *geoMirrorNode()
 {
 // return which geo mirror node this is (or NULL if geo mirroring is turned off)
 return cfgOption("browser.node");
 }
 
 
 int geoMirrorDefaultNode4(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char query[1024];
 bits32 ip = 0;
 int defaultNode = 1;
 
 if (isIpv6Address(ipStr))  // ipv6 which we do not handle yet. TODO
     return defaultNode;  // at least tolerate IPV6 remote addr
 
 internetDottedQuadToIp(ipStr, &ip);
 
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 
 // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient;
 // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
 // this is accomplished by "<= ipEnd" in the sql query. 
 // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
 // If so, we may wish to remove the loop below since that was added by Larry and reformulate
 // it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
 
 sqlSafef(query, sizeof query, 
     "select ipStart, ipEnd, node from geoIpNode%s where %u >= ipStart and %u <= ipEnd order by ipStart desc limit 1"
     , geoSuffix, ip, ip);
 char **row;
 struct sqlResult *sr = sqlGetResult(centralConn, query);
 
 if ((row = sqlNextRow(sr)) != NULL)
     {
     uint ipStart = sqlUnsigned(row[0]);
     uint ipEnd = sqlUnsigned(row[1]);
     if (ipStart <= ip && ipEnd >= ip)
         {
         defaultNode = sqlSigned(row[2]);
         }
     }
 sqlFreeResult(&sr);
 
 return defaultNode;
 }
  
 int geoMirrorDefaultNode6(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char query[1024];
 char newIpStr[NI_MAXHOST];
 struct in6_addr ip;
 ZeroVar(&ip);
 char ipHex[33];
 
 int defaultNode = 1;
 
 if (isIpv6Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s", ipStr);
 else if (isIpv4Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:"
 else 
     {
     warn("Unexpected strange ip address string: %s", ipStr);
     return defaultNode;
     }
 
 if (!internetIpStringToIp6(newIpStr, &ip))
     errAbort("internetIpStringToIp6 failed for %s", ipStr);
 
 ip6AddrToHexStr(&ip, ipHex, sizeof ipHex);
 
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 
 // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient;
 // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
 // this is accomplished by "<= ipEnd" in the sql query. 
 // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
 // If so, we may wish to remove the loop below since that was added by Larry and reformulate
 // it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
 
 sqlSafef(query, sizeof query, 
     "select ipStart, ipEnd, node from geoIpNode6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1"
     , geoSuffix, ipHex, ipHex);
 char **row;
 struct sqlResult *sr = sqlGetResult(centralConn, query);
 
 if ((row = sqlNextRow(sr)) != NULL)
     {
     struct in6_addr ipStart;
     ip6AddrCopy((struct in6_addr *)row[0], &ipStart);
 
     struct in6_addr ipEnd;
     ip6AddrCopy((struct in6_addr *)row[1], &ipEnd  );
 
     if (
 	(ip6AddrCmpBits(&ipStart, &ip) <= 0)
 	&&
 	(ip6AddrCmpBits(&ipEnd  , &ip) >= 0)
        )
         {
         defaultNode = sqlSigned(row[2]);
         }
     }
 sqlFreeResult(&sr);
 
 return defaultNode;
 }
 
 int geoMirrorDefaultNode(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 char fullTableName[1024];
 safef(fullTableName, sizeof fullTableName, "%s%s", "geoIpNode6", geoSuffix);
 int defaultNode = 1;
 if (sqlTableExists(centralConn, fullTableName))
     {
     defaultNode = geoMirrorDefaultNode6(centralConn, ipStr);
     }
 else
     {
     defaultNode = geoMirrorDefaultNode4(centralConn, ipStr);
     }
 return defaultNode;
 }
 
 char *geoMirrorCountry6(struct sqlConnection *centralConn, char *ipStr)
 /* Return 2 letter country code for given IP. user has already checked table geoIpCountry6 exists.
  * Return error string otherwise. Free the response string. */
 {
 char query[1024];
 char newIpStr[NI_MAXHOST];
 struct in6_addr ip;
 ZeroVar(&ip);
 char ipHex[33];
 
 char response[256];
 safef(response, sizeof response, "not found");
 
 if (isIpv6Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s", ipStr);
 else if (isIpv4Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:"
 else 
     {
     warn("Unexpected strange ip address string: %s", ipStr);
     safef(response, sizeof response, "Unexpected strange ip address string: %s", ipStr);
     return cloneString(response);
     }
 
 if (!internetIpStringToIp6(newIpStr, &ip))
     errAbort("internetIpStringToIp6 failed for %s", ipStr);
 
 ip6AddrToHexStr(&ip, ipHex, sizeof ipHex);
 
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 
 // We (sort-of) assume no overlaps in geoIpCountry6 table, so we can use limit 1 to make query very efficient;
 // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
 // this is accomplished by "<= ipEnd" in the sql query. 
 // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
 // If so, we may wish to remove the loop below since that was added by Larry and reformulate
 // it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
 
 sqlSafef(query, sizeof query, 
     "select ipStart, ipEnd, countryId from geoIpCountry6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1"
     , geoSuffix, ipHex, ipHex);
 char **row;
 struct sqlResult *sr = sqlGetResult(centralConn, query);
 
 if ((row = sqlNextRow(sr)) != NULL)
     {
     struct in6_addr ipStart;
     ip6AddrCopy((struct in6_addr *)row[0], &ipStart);
 
     struct in6_addr ipEnd;
     ip6AddrCopy((struct in6_addr *)row[1], &ipEnd  );
 
     if (
 	(ip6AddrCmpBits(&ipStart, &ip) <= 0)
 	&&
 	(ip6AddrCmpBits(&ipEnd  , &ip) >= 0)
        )
         {
         safef(response, sizeof response, "%s", row[2]);
         }
     }
 sqlFreeResult(&sr);
 
 return cloneString(response);
 }
 
-struct geoNode
-/* One row of hgcentral gbNode. */
-    {
-    struct geoNode *next;
-    char *node;         /* the browser.node number, as text */
-    char *domain;       /* e.g. genome-euro.ucsc.edu */
-    char *shortLabel;   /* e.g. European Server */
-    };
-
-static char *geoMirrorThisHost()
-/* The host name this request came in under, without any port, or NULL when there is none (the
- * command line).  Not freed: it comes from the environment. */
-{
-static char host[256];
-char *httpHost = getenv("HTTP_HOST");
-if (isEmpty(httpHost))
-    return NULL;
-safecpy(host, sizeof host, httpHost);
-char *colon = strchr(host, ':');            // HTTP_HOST carries the port when it is not 80/443
-if (colon != NULL)
-    *colon = '\0';
-return host;
-}
-
-static struct geoNode *geoMirrorSelf(struct geoNode *nodeList)
-/* Which of the gbNode rows is the server answering this request?  The host the visitor typed
- * decides it whenever that host is one of the nodes, so a machine serving a node other than the
- * one browser.node names -- a sandbox, or two nodes behind one apache -- does not take itself for
- * its own peer.  browser.node is the fallback, for a host that is in no gbNode row at all
- * (hgwdev.gi.ucsc.edu rather than genome-test.gi.ucsc.edu, a bare IP, the command line). */
-{
-struct geoNode *node;
-char *myHost = geoMirrorThisHost();
-if (isNotEmpty(myHost))
-    for (node = nodeList; node != NULL; node = node->next)
-        if (sameWord(myHost, node->domain))
-            return node;
-char *myNode = geoMirrorNode();
-for (node = nodeList; node != NULL; node = node->next)
-    if (sameString(node->node, myNode))
-        return node;
-return NULL;
-}
-
-static void geoNodeFreeList(struct geoNode **pList)
-/* Free a list of geoNode. */
-{
-struct geoNode *node, *next;
-for (node = *pList; node != NULL; node = next)
-    {
-    next = node->next;
-    freeMem(node->node);
-    freeMem(node->domain);
-    freeMem(node->shortLabel);
-    freeMem(node);
-    }
-*pList = NULL;
-}
-
 static struct slPair *geoMirrorNodeList(boolean wantSelf)
 /* Return gbNode as pairs of name=shortLabel, val=domain, ordered by node: either every node but
  * this one (wantSelf FALSE) or only this one (wantSelf TRUE). */
 {
 if (!geoMirrorEnabled())
     return NULL;
+char *myNode = geoMirrorNode();
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 char query[256];
 sqlSafef(query, sizeof query, "SELECT node, domain, shortLabel from gbNode%s order by node",
          geoSuffix);
 struct sqlConnection *conn = hConnectCentral();
 struct sqlResult *sr = sqlGetResult(conn, query);
-struct geoNode *nodeList = NULL, *node;
+struct slPair *nodes = NULL;
 char **row = NULL;
 while ((row = sqlNextRow(sr)) != NULL)
     {
-    AllocVar(node);
-    node->node = cloneString(row[0]);
-    node->domain = cloneString(row[1]);
-    node->shortLabel = cloneString(row[2]);
-    slAddHead(&nodeList, node);
+    if (sameString(row[0], myNode) != wantSelf)
+        continue;
+    slPairAdd(&nodes, row[2], cloneString(row[1]));
     }
 sqlFreeResult(&sr);
 hDisconnectCentral(&conn);
-slReverse(&nodeList);
-struct geoNode *self = geoMirrorSelf(nodeList);
-struct slPair *nodes = NULL;
-for (node = nodeList; node != NULL; node = node->next)
-    if ((node == self) == wantSelf)
-        slPairAdd(&nodes, node->shortLabel, cloneString(node->domain));
-geoNodeFreeList(&nodeList);
 slReverse(&nodes);
 return nodes;
 }
 
 struct slPair *geoMirrorThisNode()
 /* Return this node (browser.node) as a single pair of name=shortLabel, val=domain, or NULL when
  * geo mirroring is off or gbNode has no row for it.  slPairFreeValsAndList when done. */
 {
 return geoMirrorNodeList(TRUE);
 }
 
 struct slPair *geoMirrorOtherNodes()
 /* Return the other geo mirror nodes, as pairs of name=shortLabel, val=domain, ordered by node.
  * The node this CGI is running on (browser.node) is left out.  Returns NULL when geo mirroring
  * is off or this is the only node.  slPairFreeValsAndList when done. */
 {
 return geoMirrorNodeList(FALSE);
 }
 
 char *geoMirrorMenu()
 /* Create customized geoMirror menu string for substitution of  into 
  * <!-- OPTIONAL_MIRROR_MENU --> in htdocs/inc/globalNavBar.inc 
  * Reads hgcentral geo tables and hg.conf settings. 
  * Free the returned string when done. */
 {
 struct dyString *dy = dyStringNew(0);  // by default replacment is just an empty string.
 if (geoMirrorEnabled())
     {
     dyStringAppend(dy, "<li id=\"geoMirrorMenu\" class=\"noHighlight\"><hr></li>\n");
     // Geo mirror functionality (e.g. in nav bar)
     char *myNode = geoMirrorNode();
     /* hgcentral.gbNode table so UI can share w/ browser GEO mirror redirect code */
     char **row = NULL;
     struct sqlConnection *conn = hConnectCentral();  // after hClade since it access hgcentral too
     // get the gbNode table
     char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
     char query[256];
     sqlSafef(query, sizeof query, "SELECT node, domain, shortLabel from gbNode%s order by node", geoSuffix);
     struct sqlResult *sr = sqlGetResult(conn, query);
     while ((row = sqlNextRow(sr)) != NULL)
 	{
 	char *node = row[0];
 	char *domain = row[1];
 	char *shortLabel = row[2];
         dyStringPrintf(dy, "<li id=\"server%s\"", node);
         if (sameString(node, myNode))
             dyStringAppend(dy, " class=\"noHighlight\"");
         dyStringAppend(dy, ">\n");
         dyStringAppend(dy, "<img alt=\"X\" width=\"16\" height=\"16\" style=\"float:left;");
         if (!sameString(node, myNode))
             dyStringAppend(dy, "visibility:hidden;");
         dyStringAppend(dy, "\" src=\"../images/greenChecksmCtr.png\">\n");
         if (!sameString(node, myNode))
             dyStringPrintf(dy, "<a href=\"http://%s/cgi-bin/hgGateway?redirect=manual\">", domain);
         dyStringPrintf(dy, "%s", shortLabel);
         if (!sameString(node, myNode))
             dyStringAppend(dy, "</a>");
         dyStringAppend(dy, "</li>\n");
 
 	}
     sqlFreeResult(&sr);
     hDisconnectCentral(&conn);
     }
 return dyStringCannibalize(&dy);
 }