diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index 0ee53d3879..4df00cfd38 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.hc.client5.http.utils.URIUtils; import org.eclipse.jetty.util.URIUtil; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Contract; @@ -49,12 +50,12 @@ import org.labkey.test.selenium.EphemeralWebElement; import org.labkey.test.util.CodeMirrorHelper; import org.labkey.test.util.Crawler; -import org.labkey.test.util.OptionalFeatureHelper; import org.labkey.test.util.Ext4Helper; import org.labkey.test.util.ExtHelper; import org.labkey.test.util.LabKeyExpectedConditions; import org.labkey.test.util.LogMethod; import org.labkey.test.util.LoggedParam; +import org.labkey.test.util.OptionalFeatureHelper; import org.labkey.test.util.PasswordUtil; import org.labkey.test.util.RelativeUrl; import org.labkey.test.util.TestLogger; @@ -3916,7 +3917,6 @@ public String getUrlParam(String paramName, boolean decode) if (paramValue != null && decode) { - paramValue = paramValue.replace("+", "%20"); paramValue = URLDecoder.decode(paramValue, StandardCharsets.UTF_8); } diff --git a/src/org/labkey/test/tests/ContainerContextTest.java b/src/org/labkey/test/tests/ContainerContextTest.java index e7b1c66da0..ac1cac44a4 100644 --- a/src/org/labkey/test/tests/ContainerContextTest.java +++ b/src/org/labkey/test/tests/ContainerContextTest.java @@ -37,7 +37,6 @@ import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.util.DataRegionTable; -import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.Ext4Helper; import org.labkey.test.util.LogMethod; import org.labkey.test.util.Maps; @@ -510,7 +509,7 @@ private void verifySimpleModuleTables( for (int i = 0; i < max; i++) { - String workbookContainer = EscapeUtil.encode(getProjectName()) + "/" + workbookIds[i]; + String workbookContainer = getProjectName() + "/" + workbookIds[i]; String href; String expectedHref; @@ -560,7 +559,7 @@ private void verifySimpleModuleTables( if (parentRowIds[i] != null && !parentRowIds[i].equals("") && parentDetailsAction != null) { String parentTestWorkbookId = rowIdToWorkbookId.get(parentRowIds[i]); - String parentTestContainer = EscapeUtil.encode(getProjectName()) + "/" + parentTestWorkbookId; + String parentTestContainer = getProjectName() + "/" + parentTestWorkbookId; expectedHref = WebTestHelper.buildRelativeUrl("query", parentTestContainer, parentDetailsAction, Maps.of("schemaName", "vehicle", "query.queryName", "EmissionTest", "RowId", parentRowIds[i])); diff --git a/src/org/labkey/test/tests/filecontent/FileContentDownloadTest.java b/src/org/labkey/test/tests/filecontent/FileContentDownloadTest.java index 7ed87c2283..615426db31 100644 --- a/src/org/labkey/test/tests/filecontent/FileContentDownloadTest.java +++ b/src/org/labkey/test/tests/filecontent/FileContentDownloadTest.java @@ -15,6 +15,7 @@ */ package org.labkey.test.tests.filecontent; +import org.eclipse.jetty.util.URIUtil; import org.jetbrains.annotations.Nullable; import org.junit.Before; import org.junit.BeforeClass; @@ -25,7 +26,6 @@ import org.labkey.test.TestTimeoutException; import org.labkey.test.categories.Daily; import org.labkey.test.categories.FileBrowser; -import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.FileBrowserHelper; import org.labkey.test.util.PortalHelper; @@ -186,7 +186,7 @@ public void testRenderAsRedirect() signOut(); // Test that renderAs can be observed through a login log("Test renderAs through login and ensure that page is rendered inside of server UI"); - beginAt("files/" + EscapeUtil.encode(getProjectName()) + "/%40files/" + EscapeUtil.encode(folderName) + "/" + textFile.getName() + "?renderAs=INLINE"); + beginAt("files/" + URIUtil.encodePath(getProjectName() + "/@files/" + folderName + "/" + textFile.getName()) + "?renderAs=INLINE"); assertTitleContains("Sign In"); // If this succeeds, then page has been rendered in frame diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 40e779262d..4d99a190f0 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -685,7 +685,8 @@ public void testCustomViews() assertTextPresent("query not found"); log("Test exporting a nonexistent list returns a 404"); - String exportUrl = "/" + EscapeUtil.encode(PROJECT_VERIFY) + "/query-exportRowsTsv.view?schemaName=lists&query.queryName=" + EscapeUtil.encode(LIST_NAME_COLORS); + String exportUrl = WebTestHelper.buildURL("query", PROJECT_VERIFY, "exportRowsTsv", + Map.of("schemaName", "lists", "query.queryName", LIST_NAME_COLORS)); beginAt(exportUrl); assertEquals("Incorrect response code", 404, getResponseCode()); assertTextPresent("The specified query '%s' does not exist in schema '%s'".formatted(LIST_NAME_COLORS, "lists")); @@ -1562,7 +1563,8 @@ public void customizeURLTest() createList("B", Bcolumns, Bdata); createList("A", Acolumns, Adata); - beginAt("/query/" + EscapeUtil.encode(PROJECT_VERIFY) + "/executeQuery.view?schemaName=lists&query.queryName=A"); + beginAt(WebTestHelper.buildURL("query", PROJECT_VERIFY, "executeQuery", + Map.of("schemaName", "lists", "query.queryName", "A"))); pushLocation(); { diff --git a/src/org/labkey/test/util/Crawler.java b/src/org/labkey/test/util/Crawler.java index c2319e0f79..8a698455aa 100644 --- a/src/org/labkey/test/util/Crawler.java +++ b/src/org/labkey/test/util/Crawler.java @@ -1357,7 +1357,7 @@ private void testInjection(URL start) //noinspection SuspiciousListRemoveInLoop injectParams.remove(i); String xss = (random.nextInt()%2)==0 ? injectScriptBlock : injectAttributeScript; - xss = URLEncoder.encode(xss, StandardCharsets.US_ASCII); + xss = URLEncoder.encode(xss, StandardCharsets.UTF_8); String paramMalicious = key + "=" + xss; String queryMalicious = paramMalicious + "&" + queryStringFromEntries(injectParams); String urlMalicious = base + "?" + queryMalicious; diff --git a/src/org/labkey/test/util/EscapeUtil.java b/src/org/labkey/test/util/EscapeUtil.java index a07bdc3f48..753f8bc07e 100644 --- a/src/org/labkey/test/util/EscapeUtil.java +++ b/src/org/labkey/test/util/EscapeUtil.java @@ -16,6 +16,7 @@ package org.labkey.test.util; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.util.URIUtil; import java.net.URLDecoder; import java.net.URLEncoder; @@ -69,16 +70,26 @@ static public String jsString(String s) return js.toString(); } + /** + * Encode a string to be used as a URL query key or value + * @param s to be encoded + * @return encoded value or empty string if provided string was `null` + * @apiNote Use {@link URIUtil#encodePath(String)} for URL paths + */ public static String encode(String s) { - if (s == null) - return ""; - return URLEncoder.encode(s, StandardCharsets.UTF_8).replace("+", "%20"); + return s == null ? "" : URLEncoder.encode(s, StandardCharsets.UTF_8); } + /** + * Decode a string extracted from a URL query + * @param s to be decoded + * @return decoded value or empty string if provided string was `null` + * @apiNote Use {@link URIUtil#decodePath(String)} for URL paths + */ public static String decode(String s) { - return null==s ? "" : URLDecoder.decode(s, StandardCharsets.UTF_8); + return null == s ? "" : URLDecoder.decode(s, StandardCharsets.UTF_8); } public static String fieldKeyEncodePart(String str) diff --git a/src/org/labkey/test/util/ListHelper.java b/src/org/labkey/test/util/ListHelper.java index 4b4b096f6c..dc32ae0a11 100644 --- a/src/org/labkey/test/util/ListHelper.java +++ b/src/org/labkey/test/util/ListHelper.java @@ -326,7 +326,8 @@ public void goToList(String listName) public void beginAtList(String projectName, String listName) { - beginAt("/query/" + EscapeUtil.encode(projectName) + "/executeQuery.view?schemaName=lists&query.queryName=" + listName); + beginAt(WebTestHelper.buildURL("query", projectName, "executeQuery", + Map.of("schemaName", "lists", "query.queryName", listName))); } public void verifyListData(List columns, String[][] data, DeferredErrorCollector checker) diff --git a/src/org/labkey/test/util/URLBuilder.java b/src/org/labkey/test/util/URLBuilder.java index 86851031ac..dc067e4067 100644 --- a/src/org/labkey/test/util/URLBuilder.java +++ b/src/org/labkey/test/util/URLBuilder.java @@ -107,7 +107,7 @@ public URLBuilder setQuery(Map query) public URLBuilder setAppResourcePath(Object... pathParts) { List encodedParts = Arrays.stream(pathParts).map(Objects::requireNonNull).map(String::valueOf) - .map(EscapeUtil::encode).collect(Collectors.toList()); + .map(URIUtil::encodePath).collect(Collectors.toList()); setFragment("/" + String.join("/", encodedParts)); return this; } diff --git a/src/org/labkey/test/util/core/webdav/WebDavUrlFactory.java b/src/org/labkey/test/util/core/webdav/WebDavUrlFactory.java index ce62bbb667..e25d06206e 100644 --- a/src/org/labkey/test/util/core/webdav/WebDavUrlFactory.java +++ b/src/org/labkey/test/util/core/webdav/WebDavUrlFactory.java @@ -17,7 +17,7 @@ import org.apache.commons.lang3.StringUtils; -import org.labkey.test.util.EscapeUtil; +import org.eclipse.jetty.util.URIUtil; import static org.labkey.test.util.core.webdav.WebDavUtils.buildBaseWebDavUrl; import static org.labkey.test.util.core.webdav.WebDavUtils.buildBaseWebfilesUrl; @@ -33,13 +33,12 @@ protected WebDavUrlFactory(String baseUrl) public String getPath(String relativePath) { - return baseUrl + StringUtils.stripStart(relativePath, "/"); + return baseUrl + URIUtil.encodePath(StringUtils.strip(relativePath, "/")); } public String getPath(String relativeParent, String fileName) { - String encodedParent = EscapeUtil.encode(relativeParent).replace("%2F", "/"); - return getPath(StringUtils.stripEnd(encodedParent, "/") + "/" + EscapeUtil.encode(fileName)); + return getPath(relativeParent) + "/" + URIUtil.encodePath(fileName); } public static WebDavUrlFactory pipelineUrlFactory(String containerPath)