007346754d9616c97219877c3731714eaf62c1dc
chmalee
  Tue Jun 24 11:51:27 2025 -0700
When encoding characters in a URL, we only want to encode characters that have not already been encoded, refs #35925

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 35b0ffa8fbb..6492729e5ea 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -1165,37 +1165,42 @@
             retVal += "&";
         }
         var val = varHash[aVar];
         if (typeof(val) === 'string'
         && val.length >= 2
         && val.indexOf('[') === 0
         && val.lastIndexOf(']') === (val.length - 1)) {
             var vals = val.substr(1,val.length - 2).split(',');
             /* jshint loopfunc: true */// function inside loop works and replacement is awkward.
             $(vals).each(function (ix) {
                 if (ix > 0)
                     retVal += "&";
                 retVal += aVar + "=" + encodeURIComponent(this);
             });
         } else {
-            let decoded = decodeURIComponent(val);
-            if (decoded.length < val.length) {
-                // val was already encoded, don't re-encode
-                retVal += aVar + "=" + val;
-            } else {
-                retVal += aVar + "=" + encodeURIComponent(val);
-            }
+            // sometimes val is already encoded or partially encoded
+            // the CGI cannot know if user input is double encoded
+            // so test for already encoded characters here and only
+            // encode what we need to
+            retVal += aVar + "=" + val.replace(/(%[0-9A-Fa-f]{2})+|[^%]+/g, (match) => {
+                if (/%[0-9A-Fa-f]{2}/.test(match)) {
+                    // Already percent-encoded, leave as-is
+                    return match;
+                }
+                // Encode unencoded parts
+                return encodeURIComponent(match);
+            });
         }
     }
     return retVal;
 }
 
 function getAllVarsAsUrlData(obj)  // DEAD CODE?
 {
 // Returns a string in the form of var1=val1&var2=val2... for all inputs and selects in an obj
 // If obj is undefined then obj is document!
     return varHashToQueryString(getAllVars(obj));
 }
 
 /*
 function popupBox(popit, content, popTitle)
 {