From d079b91b00a137cccb1f69c9f5e576b8c761c84b Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 15:20:43 +1100 Subject: [PATCH 01/32] Add new utility method createTargetUrl in WebUtilities to centralise the logic for creating the URL for Targetable components. --- CHANGELOG.md | 3 + .../bordertech/wcomponents/WebUtilities.java | 49 ++ .../wcomponents/WebUtilities_Test.java | 427 ++++++++---------- 3 files changed, 236 insertions(+), 243 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ec31917..0b741a147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### API Changes ### Enhancements + +* Add new utility method createTargetUrl in WebUtilities to centralise the logic for creating the URL for Targetable components. + ### Bug Fixes ## 1.5.37 diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java index 512501bbe..0131b0653 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java @@ -428,6 +428,55 @@ public static String doubleDecodeBrackets(final String input) { return DOUBLE_DECODE_BRACKETS.translate(input); } + /** + * Create the URL for a targetable component. + * + * @param target the targetable component + * @param cacheKey the cacheKey or otherwise null + * @return the URL for the content of a targetable component + */ + public static String createTargetUrl(final Targetable target, final String cacheKey) { + return createTargetUrl(target, cacheKey, null); + } + + /** + * Create the URL for a targetable component with additional parameters. + * + * @param target the targetable component + * @param cacheKey the cacheKey or otherwise null + * @param additionalParams the additional parameters to include on url or otherwise null + * @return the URL for the content of a targetable component + */ + public static String createTargetUrl(final Targetable target, final String cacheKey, final Map additionalParams) { + + Environment env = target.getEnvironment(); + + Map parameters = env.getHiddenParameters(); + // Remove session token as this should not be exposed on GET URLs (CSRF Rules) + parameters.remove(Environment.SESSION_TOKEN_VARIABLE); + + // Add the target id + parameters.put(Environment.TARGET_ID, target.getTargetId()); + + if (Util.empty(cacheKey)) { + // Add some randomness to the URL to prevent caching + parameters.put(Environment.UNIQUE_RANDOM_PARAM, WebUtilities.generateRandom()); + } else { + // Add the cache key + parameters.put(Environment.CONTENT_CACHE_KEY, cacheKey); + // Remove step counter as not required for cached content + parameters.remove(Environment.STEP_VARIABLE); + } + + // Add additional parameters + if (additionalParams != null) { + parameters.putAll(additionalParams); + } + + // Build URL + return getPath(env.getWServletPath(), parameters, true); + } + /** * Adds GET parameters to a path. * diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java index d4b8fd57d..80e9dde3e 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java @@ -7,11 +7,13 @@ import com.github.bordertech.wcomponents.util.mock.MockRequest; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.junit.Assert; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -118,124 +120,6 @@ public void testGetTop() { Assert.assertEquals("Incorrect top component returned for top", root, WebUtilities.getTop(root)); } - // @Test - // public void testGetWComponentPath() - // { - // // Simple test, one root element. - // WContainer root = new WContainer(); - // UIContext uic = new UIContextImpl(); - // uic.setUI(root); - // - // List path = WebUtilities.getWComponentPath(root, root.getId(), false); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Add a static child - // WContainer staticChild = new WContainer(); - // root.add(staticChild); - // - // path = WebUtilities.getWComponentPath(root, staticChild.getId(), false); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Add a dynamic child - // root.setLocked(true); - // setActiveContext(uic); - // WComponent dynamicChild = new DefaultWComponent(); - // staticChild.add( dynamicChild); - // - // path = WebUtilities.getWComponentPath(root, dynamicChild.getId(), false); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild), - // new WComponentPathElement(dynamicChild), - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Test against another context with strict - should not find dynamic child - // String dynamicChildId = dynamicChild.getId(); - // UIContext otherUic = new UIContextImpl(); - // otherUic.setUI(root); - // setActiveContext(otherUic); - // - // path = WebUtilities.getWComponentPath(root, dynamicChildId, false); - // Assert.assertNull("Path should not have been found", path); - // - // // Test against another context with tolerant - should return up to the static child - // path = WebUtilities.getWComponentPath(root, dynamicChildId, true); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // } - // - // @Test - // public void testGetWComponentPathWithRepeater() - // { - // WContainer root = new WContainer(); - // UIContext uic = new UIContextImpl(); - // uic.setUI(root); - // WRepeater repeater = new WRepeater(); - // WComponent repeatedComponent = new WText(); - // List data = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" })); - // - // repeater.setRepeatedComponent(repeatedComponent); - // root.add(repeater); - // - // setActiveContext(uic); - // repeater.setData(data); - // List contexts = repeater.getRowContexts(); - // - // for (int i = 0; i < data.size(); i++) - // { - // UIContext rowContext = contexts.get(i); - // String repeatedComponentId = getComponentId(repeatedComponent, rowContext); - // - // List path = WebUtilities.getWComponentPath(root, repeatedComponentId, false); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(repeater, i), - // new WComponentPathElement(repeater.getRepeatRoot()), - // new WComponentPathElement(repeatedComponent) - // }); - // - // Assert.assertEquals("Incorrect path for row " + i, expected, path); - // } - // - // // Test when a row is removed from the repeater - // UIContext rowContext = contexts.get(contexts.size() - 1); - // data.remove(data.size() - 1); - // - // // Strict should return null - // UIContextHolder.pushContext(rowContext); - // String repeatedComponentId = getComponentId(repeatedComponent, rowContext); - // - // List path = WebUtilities.getWComponentPath(root, repeatedComponentId, false); - // Assert.assertNull("Path should not have been found in strict mode after row removal", path); - // - // // Tolerant should return up to the repeater - // path = WebUtilities.getWComponentPath(root, repeatedComponentId, true); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(repeater), - // new WComponentPathElement(repeater.getRepeatRoot()), - // new WComponentPathElement(repeatedComponent) - // }); - // - // Assert.assertEquals("Incorrect tolerant path after row removal", expected, path); - // } @Test public void testFindClosestContext() { WContainer root = new WContainer(); @@ -465,36 +349,53 @@ public void testDecode() { encoded)); } + @Test(expected = IllegalArgumentException.class) + public void testGetPathNullURL() { + // Should not allow null base URL + WebUtilities.getPath(null, Collections.emptyMap()); + } + @Test - public void testGetPath() { - // Simple case - String url = "/foo"; - String expected = "/foo"; - Assert.assertEquals("Incorrect path returned for " + url, expected, WebUtilities. - getPath(url, null)); + public void testGetPathSimple() { + String baseUrl = "/foo"; + String url = WebUtilities.getPath(baseUrl, null); + Assert.assertEquals("Incorrect path returned for base URL", baseUrl, url); + } - // Simple case with one param + @Test + public void testGetPathWithParameter() { + // Simple case with adding one param + String baseUrl = "/foo"; Map params = new HashMap<>(); params.put("a", "b"); + String url = WebUtilities.getPath(baseUrl, params); + String expected = "/foo?a=b"; + Assert.assertEquals("Incorrect path returned for base URL with one parameter", expected, url); + } - url = "/foo"; - expected = "/foo?a=b"; - Assert.assertEquals("Incorrect path returned for " + url + " with a=b", expected, - WebUtilities.getPath(url, params)); - + @Test + public void testGetPathWithExistingParameter() { // Case with existing params and two in the map - params = new HashMap<>(); + String baseUrl = "/foo?a=b"; + Map params = new LinkedHashMap<>(); params.put("c", "d"); params.put("e", "f"); + String url = WebUtilities.getPath(baseUrl, params); + String expected = "/foo?a=b&c=d&e=f"; + Assert.assertEquals("Incorrect path returned for base URL with existing parameters", expected, url); + } - url = "/foo?a=b"; - expected = "/foo?a=b&c=d&e=f"; - - assertURLEquals(expected, WebUtilities.getPath(url, params), "&"); - + @Test + public void testGetPathWithAsJavascriptURL() { + // Case with existing params and two in the map + String baseUrl = "/foo?a=b"; + Map params = new HashMap<>(); + params.put("c", "d"); + params.put("e", "f"); // As a javascript url - expected = "/foo?a=b&c=d&e=f"; - assertURLEquals(expected, WebUtilities.getPath(url, params, true), "&"); + String url = WebUtilities.getPath(baseUrl, params, true); + String expected = "/foo?a=b&c=d&e=f"; + Assert.assertEquals("Incorrect path returned for URL for javascript", expected, url); } @Test @@ -594,26 +495,6 @@ public void testRenderToHtmlWithXML() { Assert.assertEquals("Invalid html output with XML", TransformXMLTestHelper.EXPECTED, output); } -// @Test -// public void testContainsBrackets() { -// Assert.assertTrue("Contains a open bracket", WebUtilities.containsBrackets("{")); -// Assert.assertTrue("Contains a closed bracket", WebUtilities.containsBrackets("}")); -// Assert.assertFalse("Contains an encoded open bracket", WebUtilities.containsBrackets("{")); -// Assert.assertFalse("Contains an encoded closed bracket", WebUtilities.containsBrackets("}")); -// Assert.assertFalse("Contains a double encoded open bracket", WebUtilities.containsBrackets("&#123;")); -// Assert.assertFalse("Contains a double encoded closed bracket", WebUtilities.containsBrackets("&#125;")); -// } - -// @Test -// public void testContainsEncodeBrackets() { -// Assert.assertTrue("Contains an encoded open bracket", WebUtilities.containsEncodedBrackets("{")); -// Assert.assertTrue("Contains an encoded closed bracket", WebUtilities.containsEncodedBrackets("}")); -// Assert.assertFalse("Contains a double encoded open bracket", WebUtilities.containsEncodedBrackets("&#123;")); -// Assert.assertFalse("Contains a double encoded closed bracket", WebUtilities.containsEncodedBrackets("&#125;")); -// Assert.assertFalse("Contains a open bracket", WebUtilities.containsEncodedBrackets("{")); -// Assert.assertFalse("Contains a closed bracket", WebUtilities.containsEncodedBrackets("}")); -// } - @Test public void testEncodeBrackets() { String in = "{}<{}>"; @@ -677,8 +558,110 @@ public void testDoubleDecodeBracketsWithNoMatches() { Assert.assertEquals("Double decode brackets not correct", out, WebUtilities.doubleDecodeBrackets(in)); } + @Test + public void testCreateTargetUrl() { + + String baseUrl = "/path"; + + // Setup context + UIContext uic = createUIContext(); + MockWEnvironment env = new MockWEnvironment(); + env.setPostPath(baseUrl); + uic.setEnvironment(env); + setActiveContext(uic); + + // Target URL with no hidden or additional parameters + Targetable target = new MyTargetable(); + HashMap expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + String url = WebUtilities.createTargetUrl(target, null); + assertCreatedURLCorrect("Target URL with no hidden or additional parameters. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters + // Setup hidden parameters + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "session"); + hiddenParams.put(Environment.STEP_VARIABLE, "1"); + env.setHiddenParameters(hiddenParams); + uic.setEnvironment(env); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_s", "1"); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + url = WebUtilities.createTargetUrl(target, null); + assertCreatedURLCorrect("Target URL with hidden parameter. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters and additional + // Setup additional params + HashMap additionalParams = new LinkedHashMap<>(); + additionalParams = new HashMap<>(); + additionalParams.put("c", "d"); + additionalParams.put("e", "f"); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_s", "1"); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + expectedParams.putAll(additionalParams); + url = WebUtilities.createTargetUrl(target, null, additionalParams); + assertCreatedURLCorrect("Target URL with hidden parameters and additional. ", url, baseUrl, expectedParams, "&"); + } + + @Test + public void testCreateTargetUrlWithCache() { + + String baseUrl = "/path"; + String cacheKey = "CACHE"; + + // Setup context + UIContext uic = createUIContext(); + MockWEnvironment env = new MockWEnvironment(); + env.setPostPath(baseUrl); + uic.setEnvironment(env); + setActiveContext(uic); + + // Target URL with no hidden or additional parameters + Targetable target = new MyTargetable(); + HashMap expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + String url = WebUtilities.createTargetUrl(target, cacheKey); + assertCreatedURLCorrect("Target URL with no hidden or additional parameters and CACHE. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters + // Setup hidden parameters + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "session"); + hiddenParams.put(Environment.STEP_VARIABLE, "1"); + env.setHiddenParameters(hiddenParams); + uic.setEnvironment(env); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + url = WebUtilities.createTargetUrl(target, cacheKey); + assertCreatedURLCorrect("Target URL with hidden parameter and CACHE. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters and additional + // Setup additional params + HashMap additionalParams = new LinkedHashMap<>(); + additionalParams = new HashMap<>(); + additionalParams.put("c", "d"); + additionalParams.put("e", "f"); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + expectedParams.putAll(additionalParams); + url = WebUtilities.createTargetUrl(target, cacheKey, additionalParams); + assertCreatedURLCorrect("Target URL with hidden parameters and additional and CACHE. ", url, baseUrl, expectedParams, "&"); + } + /** * Generates a range of characters. + * * @param from The first character in the range (must be > 0). * @param to The last character in the range (must be >= from). * @return A string containing the character range. @@ -691,99 +674,48 @@ private static String characterRange(final int from, final int to) { return result.toString(); } -// /** -// * Set up and execute the updateBeanValue method with the given parameter. -// * If the parameter is null then the default updateBeanValue(component) method will be invoked. -// * -// * @param visibleOnly the parameter to pass to WebUtilities.updateBeanValue(component, visibleOnly). -// */ -// private void runUpdateBeanValue(final Boolean visibleOnly) { -// final String directChild = "directChild"; -// final String grandChild = "grandChild"; -// final String invisibleGrandChild = "invisibleGrandChild"; -// final String childOfInvisibleContainer = "childOfInvisibleContainer"; -// -// Map beanMap = new HashMap<>(); -// beanMap.put(directChild, null); -// beanMap.put(grandChild, null); -// beanMap.put(invisibleGrandChild, null); -// beanMap.put(childOfInvisibleContainer, null); -// -// WContainer root = new WContainer(); -// root.setBean(beanMap); -// WTextField childTextField = new WTextField(); -// childTextField.setBeanProperty(directChild); -// root.add(childTextField); -// -// WContainer childContainer = new WContainer(); -// root.add(childContainer); -// WTextField grandChildTextField = new WTextField(); -// grandChildTextField.setBeanProperty(grandChild); -// childContainer.add(grandChildTextField); -// -// WTextField invisibleGrandChildTextField = new WTextField(); -// invisibleGrandChildTextField.setBeanProperty(invisibleGrandChild); -// invisibleGrandChildTextField.setVisible(false); -// childContainer.add(invisibleGrandChildTextField); -// -// WContainer invisibleContainer = new WContainer(); -// invisibleContainer.setVisible(false); -// root.add(invisibleContainer); -// WTextField childOfInivisbleContainerTextField = new WTextField(); -// childOfInivisbleContainerTextField.setBeanProperty(childOfInvisibleContainer); -// invisibleContainer.add(childOfInivisbleContainerTextField); -// -// root.setLocked(true); -// setActiveContext(createUIContext()); -// -// childTextField.setData(directChild); -// grandChildTextField.setData(grandChild); -// invisibleGrandChildTextField.setData(invisibleGrandChild); -// childOfInivisbleContainerTextField.setData(childOfInvisibleContainer); -// -// if (visibleOnly == null) { -// WebUtilities.updateBeanValue(root); -// } else { -// WebUtilities.updateBeanValue(root, visibleOnly); -// } -// -// Assert.assertEquals("updateBeanValue failed to update directChild with visibleOnly=[" + visibleOnly + "]", directChild, beanMap.get(directChild)); -// Assert.assertEquals("updateBeanValue failed to update grandChild with visibleOnly=[" + visibleOnly + "]", grandChild, beanMap.get(grandChild)); -// Assert.assertEquals("updateBeanValue updated an incorrect value for invisibleGrandChild with visibleOnly=[" + visibleOnly + "]", BooleanUtils.isNotFalse(visibleOnly) ? null : invisibleGrandChild, beanMap.get(invisibleGrandChild)); -// Assert.assertEquals("updateBeanValue updated an incorrect value for childOfInvisibleContainer with visibleOnly=[" + visibleOnly + "]", BooleanUtils.isNotFalse(visibleOnly) ? null : childOfInvisibleContainer, beanMap.get(childOfInvisibleContainer)); -// } /** * Compare the URLS. The parameters of the URL must be equal but they do not have to be in the same order. * - * @param actual the actual value - * @param expected the expected value + * @param msgPrefix message prefix for assert messages + * @param actualUrl the actual value + * @param expectedBase the expected value + * @param expectedParams the expected parameters * @param separator the separator */ - private void assertURLEquals(final String actual, final String expected, final String separator) { + private void assertCreatedURLCorrect(final String msgPrefix, final String actualUrl, final String expectedBase, final Map expectedParams, final String separator) { // compare the path section of urls (string compare) - int paramStartIndex = actual.indexOf('?'); - - // if the path elements of the url are not equal bail out now. - Assert.assertTrue("The path elements of the URLs are not equal", - expected.startsWith(actual.substring(0, paramStartIndex))); - - // now compare the parameters of each URL - String expectedURLParams = expected.substring(paramStartIndex + 1); - String actualURLParams = actual.substring(paramStartIndex + 1); - - String[] expectedParams = expectedURLParams.split(separator); - String[] actualParams = actualURLParams.split(separator); + int paramStartIndex = actualUrl.indexOf('?'); - int params = expectedParams.length; + String actualURLBase = actualUrl.substring(0, paramStartIndex); + String actualURLParams = actualUrl.substring(paramStartIndex + 1); - Assert.assertEquals("The number of parameters in URLs are not equal", params, - actualParams.length); - - List expectedParamArray = Arrays.asList(expectedParams); - List actualParamArray = Arrays.asList(actualParams); + // if the path elements of the url are not equal bail out now. + Assert.assertEquals(msgPrefix + "The path elements of the URLs are not equal", expectedBase, actualURLBase); + + // Extract actual parameters + Map actualParams = new LinkedHashMap<>(); + String[] actualParamsSplit = actualURLParams.split(separator); + for (String actual : actualParamsSplit) { + String split[] = actual.split("="); + actualParams.put(split[0], split[1]); + } - Assert.assertTrue("The parameters contained in the URLs are not equal", - actualParamArray.containsAll(expectedParamArray)); + // Check parameter keys + Assert.assertEquals(msgPrefix + "Expected parameter keys not on URL", expectedParams.keySet(), actualParams.keySet()); + + // Check parameter values + for (Map.Entry entry : expectedParams.entrySet()) { + String key = entry.getKey(); + String actualValue = actualParams.get(key); + String expectedValue = entry.getValue(); + if (expectedValue == null) { + // Null value used to indicate value is random and cannot be checked but is at least present + Assert.assertNotNull(msgPrefix + "Parameter [" + key + "] has no value", actualValue); + } else { + Assert.assertEquals(msgPrefix + "Parameter [" + key + "] has incorrect value", expectedValue, actualValue); + } + } } /** @@ -802,4 +734,13 @@ private static String getComponentId(final WComponent component, final UIContext UIContextHolder.popContext(); } } + + private static class MyTargetable extends AbstractWComponent implements Targetable { + + @Override + public String getTargetId() { + return "TARGET"; + } + + } } From d5ec908106f76d214d670fa9474f243e8d7d2854 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 15:26:29 +1100 Subject: [PATCH 02/32] Modified Targetable components to use the new createTargetUrl method in WebUtilites --- CHANGELOG.md | 3 +- .../github/bordertech/wcomponents/WAudio.java | 26 ++-------- .../bordertech/wcomponents/WContent.java | 37 +++---------- .../github/bordertech/wcomponents/WImage.java | 22 +------- .../wcomponents/WMultiFileWidget.java | 50 ++---------------- .../github/bordertech/wcomponents/WTree.java | 26 +--------- .../github/bordertech/wcomponents/WVideo.java | 52 ++++--------------- 7 files changed, 30 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b741a147..d1d704a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ ### API Changes ### Enhancements -* Add new utility method createTargetUrl in WebUtilities to centralise the logic for creating the URL for Targetable components. +* Modified Targetable components to use the new createTargetUrl method in WebUtilites that centralises the logic for + creating the URLs for Targetable components. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java index e60e3d732..68fcc8efe 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java @@ -2,6 +2,7 @@ import com.github.bordertech.wcomponents.util.Util; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -324,29 +325,11 @@ public String[] getAudioUrls() { } String[] urls = new String[audio.length]; - - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - // this variable needs to be set in the portlet environment. - String url = env.getWServletPath(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(AUDIO_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -439,7 +422,6 @@ public boolean isRenderControls() { return getComponentModel().renderControls; } - /** * Sets whether the browser should render the default controls. The default is true. * @param renderControls if true then the controls are rendered diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java index 2167a6b06..6c43003bf 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java @@ -1,7 +1,6 @@ package com.github.bordertech.wcomponents; import com.github.bordertech.wcomponents.util.Util; -import java.util.Map; /** *

@@ -176,41 +175,19 @@ public String getUrl() { String mode = DisplayMode.PROMPT_TO_SAVE.equals(getDisplayMode()) ? "attach" : "inline"; + String url; // Check for a "static" resource if (content instanceof InternalResource) { - String url = ((InternalResource) content).getTargetUrl(); - // This magic parameter is a work-around to the loading indicator becoming - // "stuck" in certain browsers. - // It is also used by the static resource handler to set the correct headers - url = url + "&" + URL_CONTENT_MODE_PARAMETER_KEY + "=" + mode; - return url; - } - - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); + url = ((InternalResource) content).getTargetUrl(); } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); + url = WebUtilities.createTargetUrl(this, getCacheKey()); } - // This magic parameter is a work-around to the loading indicator becoming - // "stuck" in certain browsers. It is only read by the theme. - parameters.put(URL_CONTENT_MODE_PARAMETER_KEY, mode); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); + // This magic parameter is a work-around to the loading indicator becoming "stuck" in certain browsers. + // It is also used by the static resource handler to set the correct headers + url = url + "&" + URL_CONTENT_MODE_PARAMETER_KEY + "=" + mode; - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return url; } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java index 2774887d5..386559d97 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java @@ -2,7 +2,6 @@ import com.github.bordertech.wcomponents.util.Util; import java.awt.Dimension; -import java.util.Map; /** *

@@ -99,26 +98,7 @@ public String getTargetUrl() { return ((InternalResource) image).getTargetUrl(); } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - // this variable needs to be set in the portlet environment. - String url = env.getWServletPath(); - - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, getCacheKey()); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java index c8319b6ad..6e22570d9 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -810,30 +811,10 @@ public String getFileUrl(final String fileId) { return null; } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(file.getFileCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, file.getFileCacheKey()); - } - // File id + Map parameters = new HashMap<>(); parameters.put(FILE_UPLOAD_ID_KEY, fileId); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, file.getFileCacheKey(), parameters); } /** @@ -854,33 +835,12 @@ public String getFileThumbnailUrl(final String fileId) { return ((InternalResource) thumbnail).getTargetUrl(); } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(file.getThumbnailCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, file.getThumbnailCacheKey()); - } - + Map parameters = new HashMap<>(); // File id parameters.put(FILE_UPLOAD_ID_KEY, fileId); - // Thumbnail flag parameters.put(FILE_UPLOAD_THUMB_NAIL_KEY, "Y"); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, file.getThumbnailCacheKey(), parameters); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java index fc5b15248..33f9c4376 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java @@ -454,32 +454,10 @@ public String getItemImageUrl(final TreeItemImage item, final String itemId) { } // Build targetted url - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - String cacheKey = item.getImageCacheKey(); - - if (Util.empty(cacheKey)) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, cacheKey); - } - + Map parameters = new HashMap<>(); // Item id parameters.put(ITEM_REQUEST_KEY, itemId); - - // The targetable path needs to be configured for the portal environment. - url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, item.getImageCacheKey(), parameters); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java index 38f908a0c..54a2db45a 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java @@ -2,10 +2,10 @@ import com.github.bordertech.wcomponents.util.Util; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -446,14 +446,11 @@ public String[] getVideoUrls() { } String[] urls = new String[video.length]; - - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(VIDEO_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -474,14 +471,11 @@ public String[] getTrackUrls() { } String[] urls = new String[tracks.length]; - - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(TRACK_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -501,36 +495,9 @@ public String getPosterUrl() { return null; } - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); + Map parameters = new HashMap<>(); parameters.put(POSTER_REQUEST_PARAM_KEY, "x"); - return WebUtilities.getPath(url, parameters, true); - } - - /** - * Retrieves the base parameter map for serving content (videos + tracks). - * - * @return the base map for serving content. - */ - private Map getBaseParameterMap() { - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - return parameters; + return WebUtilities.createTargetUrl(this, getCacheKey(), parameters); } /** @@ -559,7 +526,6 @@ public boolean isVisible() { public void handleRequest(final Request request) { super.handleRequest(request); - String targ = request.getParameter(Environment.TARGET_ID); boolean contentReqested = (targ != null && targ.equals(getTargetId())); From b966744ab01cb4ca6d6b2adbef6a324637e9d5cd Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 15:34:30 +1100 Subject: [PATCH 03/32] Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer --- CHANGELOG.md | 1 + .../render/webxml/WApplicationRenderer.java | 13 ++++++- .../wcomponents/MockWEnvironment.java | 7 ++-- .../webxml/WApplicationRenderer_Test.java | 34 +++++++++++++++++-- .../src/main/xslt/wc.ui.application.xsl | 20 ++++------- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d704a34..12149f22d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Modified Targetable components to use the new createTargetUrl method in WebUtilites that centralises the logic for creating the URLs for Targetable components. +* Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session token can be excluded. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java index 082c812af..3fe4030bb 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java @@ -1,10 +1,12 @@ package com.github.bordertech.wcomponents.render.webxml; +import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.Renderer; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.WApplication; import com.github.bordertech.wcomponents.WComponent; +import com.github.bordertech.wcomponents.WebUtilities; import com.github.bordertech.wcomponents.XmlStringBuilder; import com.github.bordertech.wcomponents.servlet.WebXmlRenderContext; import com.github.bordertech.wcomponents.util.TrackingUtil; @@ -45,11 +47,20 @@ public void doRender(final WComponent component, final WebXmlRenderContext rende LOG.warn("WApplication component should be the top level component."); } + // Build AJAX url (add hidden parameters that was previously added by XSL) + String ajaxUrl = uic.getEnvironment().getWServletPath(); + if (ajaxUrl != null) { + Map params = uic.getEnvironment().getHiddenParameters(); + // Dont add session token on URL (CSRF Rules) + params.remove(Environment.SESSION_TOKEN_VARIABLE); + ajaxUrl = WebUtilities.getPath(ajaxUrl, params, true); + } + xml.appendTagOpen("ui:application"); xml.appendAttribute("id", component.getId()); xml.appendOptionalAttribute("class", component.getHtmlClass()); xml.appendUrlAttribute("applicationUrl", uic.getEnvironment().getPostPath()); - xml.appendUrlAttribute("ajaxUrl", uic.getEnvironment().getWServletPath()); + xml.appendUrlAttribute("ajaxUrl", ajaxUrl); xml.appendOptionalAttribute("unsavedChanges", application.hasUnsavedChanges(), "true"); xml.appendOptionalAttribute("title", application.getTitle()); xml.appendOptionalAttribute("defaultFocusId", uic.isFocusRequired() && !Util.empty(focusId), diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java index 123dffbc0..b42328efd 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java @@ -1,6 +1,6 @@ package com.github.bordertech.wcomponents; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -14,7 +14,7 @@ public class MockWEnvironment extends AbstractEnvironment { /** * The hidden parameters map. */ - private Map hiddenParameters = new HashMap<>(); + private Map hiddenParameters = new LinkedHashMap<>(); /** * Sets the post path. Overriden in order to make method public, as it's useful for unit testing. @@ -33,7 +33,8 @@ public void setPostPath(final String postPath) { */ @Override public Map getHiddenParameters() { - return hiddenParameters; + // Simulate behaviour to create new map each time + return new LinkedHashMap<>(hiddenParameters); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java index a7fbf90e0..75f265317 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java @@ -1,5 +1,6 @@ package com.github.bordertech.wcomponents.render.webxml; +import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.MockWEnvironment; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.WApplication; @@ -12,10 +13,11 @@ import com.github.bordertech.wcomponents.util.ConfigurationProperties; import java.io.IOException; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import org.junit.Assert; import org.apache.commons.configuration.Configuration; import org.custommonkey.xmlunit.exceptions.XpathException; +import org.junit.Assert; import org.junit.Test; import org.xml.sax.SAXException; @@ -103,6 +105,30 @@ public void testBasicRenderedFormat() throws XpathException, IOException, SAXExc assertXpathNotExists("//ui:application/@defaultFocusId", application); } + @Test + public void testAjaxUrlWithParameters() throws XpathException, IOException, SAXException { + // Basic component (no optional fields) + MockWEnvironment environment = new MockWEnvironment(); + WApplication application = new WApplication(); + environment.setPostPath("WApplicationRendererTest.postPath"); + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put("A", "B"); + hiddenParams.put("X", "Y"); + // This should be ignored and not added to the AJAX url + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "SESSION"); + environment.setHiddenParameters(hiddenParams); + + String expectedUrl = "WApplicationRendererTest.postPath?A=B&X=Y"; + + UIContext uic = createUIContext(); + uic.setEnvironment(environment); + uic.setUI(application); + setActiveContext(uic); + + assertSchemaMatch(application); + assertXpathEvaluatesTo(expectedUrl, "//ui:application/@ajaxUrl", application); + } + @Test public void testRenderedFormatWithFocussedComponent() throws XpathException, IOException, SAXException { @@ -180,8 +206,10 @@ public void testXssEscaping() throws IOException, SAXException, XpathException { application.setTitle(getMaliciousAttribute("ui:application")); assertSafeContent(application); - uic.getEnvironment().getHiddenParameters().put(getMaliciousAttribute("ui:param"), "dummy"); - uic.getEnvironment().getHiddenParameters().put("dummy", getMaliciousAttribute("ui:param")); + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(getMaliciousAttribute("ui:param"), "dummy"); + hiddenParams.put("dummy", getMaliciousAttribute("ui:param")); + environment.setHiddenParameters(hiddenParams); assertSafeContent(application); } diff --git a/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl b/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl index 0bb337c78..97133ed63 100644 --- a/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl +++ b/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl @@ -1,8 +1,8 @@ + xmlns:ui="https://github.com/bordertech/wcomponents/namespace/ui/v1.0" + xmlns:html="http://www.w3.org/1999/xhtml" version="2.0" + exclude-result-prefixes="xsl ui html"> @@ -18,17 +18,6 @@

- - - - & - - - ? - - - - @@ -40,6 +29,9 @@ hidden + + + From 4daa7720cc1260dd9474a793cbc1550d294b5e4d Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 15:53:31 +1100 Subject: [PATCH 04/32] Modified the session token interceptors to only accept a session token on a POST --- CHANGELOG.md | 10 +- .../bordertech/wcomponents/WWindow.java | 2 + .../SessionTokenAjaxInterceptor.java | 20 ++- .../SessionTokenContentInterceptor.java | 33 ++-- .../container/SessionTokenInterceptor.java | 40 +++-- .../SerializationPerformance_Test.java | 2 +- .../SessionTokenAjaxInterceptor_Test.java | 70 +++++---- .../SessionTokenContentInterceptor_Test.java | 83 ++++------ .../SessionTokenInterceptor_Test.java | 142 ++++++++++-------- .../WebXmlRenderingPerformance_Test.java | 5 +- .../src/main/js/wc/ajax/Trigger.js | 3 + .../src/main/js/wc/ui/multiFileUploader.js | 5 + 12 files changed, 226 insertions(+), 189 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12149f22d..a59cd4c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,13 @@ ### API Changes ### Enhancements -* Modified Targetable components to use the new createTargetUrl method in WebUtilites that centralises the logic for - creating the URLs for Targetable components. -* Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session token can be excluded. +* To improve the robustness of the session token parameter (wc_t), which is used to prevent CSRF attacks, the following changes have been made: + * The session token is no longer included on any GET URLs and only posted in the body for POSTS. + * Modified the session token interceptors to only accept a session token on a POST and throw an exception if provided on a GET. + * Modified Targetable components to use the new createTargetUrl method in WebUtilites that centralises the logic for + creating the URLs for Targetable components and excludes the session token. + * Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session + token can be excluded. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java index c78c7d28b..309db3883 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java @@ -351,6 +351,8 @@ public String getUrl() { parameters.put(WWINDOW_REQUEST_PARAM_KEY, getId()); // Override the step count with WWindow step parameters.put(Environment.STEP_VARIABLE, String.valueOf(getStep())); + // Remove session token as this should not be exposed on GET URLs (CSRF Rules) + parameters.remove(Environment.SESSION_TOKEN_VARIABLE); String url = env.getWServletPath(); diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java index 36730e987..d0bcbc9c3 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java @@ -5,6 +5,8 @@ import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.util.Util; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * This session token interceptor makes sure the ajax request being processed is for the correct session. @@ -17,6 +19,11 @@ */ public class SessionTokenAjaxInterceptor extends InterceptorComponent { + /** + * The logger instance for this class. + */ + private static final Log LOG = LogFactory.getLog(SessionTokenAjaxInterceptor.class); + /** * Override to check whether the session token variable in the incoming request matches what we expect. * @@ -38,14 +45,19 @@ public void serviceRequest(final Request request) { // Get the session token from the AJAX request String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); - // Check tokens match (both must be provided) - if (Util.equals(expected, got)) { + // Session token should not be provided on a GET URL (CSRF Rules) + if (got != null && "GET".equals(request.getMethod())) { + throw new IllegalStateException("A session token should not be provided on a GET"); + } + + // Check processing a GET or tokens must match + if ("GET".equals(request.getMethod()) || (got != null && Util.equals(expected, got))) { // Process AJAX request getBackingComponent().serviceRequest(request); } else { // Invalid token on AJAX request - throw new SessionTokenException("Wrong session token detected for AJAX request. Expected token [" - + expected + "] but got token [" + got + "]."); + LOG.debug("Wrong session token detected for AJAX request. Expected token [" + expected + "] but got token [" + got + "]."); + throw new SessionTokenException("Wrong session token detected for AJAX request."); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java index 6d35fc0a2..d12b4776f 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java @@ -4,16 +4,9 @@ import com.github.bordertech.wcomponents.Request; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; -import com.github.bordertech.wcomponents.WImage; -import com.github.bordertech.wcomponents.util.StepCountUtil; -import com.github.bordertech.wcomponents.util.Util; /** - * This session token interceptor makes sure the content request being processed is for the correct session. - *

- * Similar to {@link SessionTokenInterceptor} but caters for setting error codes for content requests such as - * {@link WImage} when a token error is detected. - *

+ * This session token interceptor makes sure the session token on content requests are handled correctly for CSRF. * * @author Jonathan Austin * @since 1.0.0 @@ -21,14 +14,14 @@ public class SessionTokenContentInterceptor extends InterceptorComponent { /** - * Override to check whether the session token variable in the incoming request matches what we expect. + * Override to check whether the session token is handled correctly for CSRF. * * @param request the request being serviced. */ @Override public void serviceRequest(final Request request) { - // Get the expected session token + // Get the current session token UIContext uic = UIContextHolder.getCurrent(); String expected = uic.getEnvironment().getSessionToken(); @@ -38,19 +31,19 @@ public void serviceRequest(final Request request) { + " Can be due to the session timing out."); } - // Get the session token from the content request - String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); + // Content requests should only be a GET (CSRF Rules) + if (!"GET".equals(request.getMethod())) { + throw new IllegalStateException("Content request should only be a GET"); + } - // Check tokens match (both must be provided) or check if cached content (no session token on request) - if (Util.equals(expected, got) || (got == null && StepCountUtil.isCachedContentRequest(request))) { - // Process content request - getBackingComponent().serviceRequest(request); - } else { - // Invalid token on content request - throw new SessionTokenException("Wrong session token detected for content request. Expected token [" - + expected + "] but got token [" + got + "]."); + // Check no session token on the content request (CSRF Rules) + String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); + if (got != null) { + throw new IllegalStateException("A session token should not be provided on a GET"); } + getBackingComponent().serviceRequest(request); + } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java index 20430b516..737b2a613 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java @@ -6,12 +6,13 @@ import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.util.Util; import java.util.UUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * This session token interceptor makes sure the request being processed is for the correct session. *

- * As the token is a UUID, it will be much harder for CSRF attacks. No request processing will occur without the correct - * UUID. + * As the token is a UUID, it will be much harder for CSRF attacks. No request processing will occur without the correct UUID. *

* * @author Jonathan Austin @@ -19,6 +20,11 @@ */ public class SessionTokenInterceptor extends InterceptorComponent { + /** + * The logger instance for this class. + */ + private static final Log LOG = LogFactory.getLog(SessionTokenInterceptor.class); + /** * Override to check whether the session token variable in the incoming request matches what we expect. * @@ -26,27 +32,31 @@ public class SessionTokenInterceptor extends InterceptorComponent { */ @Override public void serviceRequest(final Request request) { - // Get the expected session token + + // Get the expected session token (could be null for new session) UIContext uic = UIContextHolder.getCurrent(); String expected = uic.getEnvironment().getSessionToken(); // Get the session token from the request String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); - // Check tokens match (Both null if new session) - // or processing a GET and no token - if (Util.equals(expected, got) || (got == null && "GET".equals(request.getMethod()))) { + // Session token should not be provided on a GET URL (CSRF Rules) + if (got != null && "GET".equals(request.getMethod())) { + throw new IllegalStateException("A session token should not be provided on a GET"); + } + + // Check processing a GET or tokens must match + if ("GET".equals(request.getMethod()) || (got != null && Util.equals(expected, got))) { // Process request getBackingComponent().serviceRequest(request); - } else { // Invalid token - String msg; - if (expected == null && got != null) { - msg = "Session for token [" + got + "] is no longer valid or timed out."; - } else { - msg = "Wrong session token detected for servlet request. Expected token [" + expected - + "] but got token [" + got + "]."; - } - throw new SessionTokenException(msg); + } else if (expected == null && got != null) { + // Expired token + LOG.debug("Session for token [" + got + "] is no longer valid or timed out."); + throw new SessionTokenException("Session for token is no longer valid or timed out."); + } else { + // Wrong token + LOG.debug("Wrong session token detected for servlet request. Expected token [" + expected + "] but got token [" + got + "]."); + throw new SessionTokenException("Wrong session token detected for servlet request."); } } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java index ceea8ba06..edf1a790f 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java @@ -226,7 +226,6 @@ private void sendRequest(final WComponent comp, final UIContext uic) { PrintWriter writer = new PrintWriter(new NullWriter()); uic.setEnvironment(new WServlet.WServletEnvironment("", "http://localhost", "")); uic.setUI(comp); - InterceptorComponent root = ServletUtil.createInterceptorChain(new MockHttpServletRequest()); root.attachUI(comp); @@ -235,6 +234,7 @@ private void sendRequest(final WComponent comp, final UIContext uic) { setActiveContext(uic); MockRequest request = new MockRequest(); + request.setMethod("GET"); try { root.serviceRequest(request); diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java index 0ccbbc5d7..8803ba58c 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java @@ -17,63 +17,77 @@ */ public class SessionTokenAjaxInterceptor_Test extends AbstractWComponentTestCase { + private static final String VALID_TOKEN = "X"; + private static final String INVALID_TOKEN = "Y"; + @Before public void setupUIC() { + // Set up user context and session token + MockWEnvironment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); UIContext uic = createUIContext(); - uic.setEnvironment(new MockWEnvironment()); + uic.setEnvironment(env); setActiveContext(uic); } @Test(expected = SessionTokenException.class) public void testServiceRequestNoTokenOnUIC() { - SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); - interceptor.serviceRequest(new MockRequest()); + // Clear session token on UIC + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process with a UIC with no session token + new SessionTokenAjaxInterceptor().serviceRequest(new MockRequest()); } @Test - public void testServiceRequestCorrectToken() { + public void testServiceRequestWithPOSTandCorrectToken() { // Setup interceptor SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); MyBackingComponent component = new MyBackingComponent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); // Setup request MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - // Process request + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should process with POST request and valid token interceptor.serviceRequest(request); - Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); + Assert.assertTrue("Action phase should have occurred for POST and correct token", component.handleRequestCalled); } @Test(expected = SessionTokenException.class) - public void testServiceRequestInvalidToken() { - // Setup interceptor - SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); - MyBackingComponent component = new MyBackingComponent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); + public void testServiceRequestWithPOSTandInvalidToken() { // Setup invalid request MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - // Process request - interceptor.serviceRequest(request); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, INVALID_TOKEN); + // Should not process with a POST request and invalid token + new SessionTokenAjaxInterceptor().serviceRequest(request); } @Test(expected = SessionTokenException.class) - public void testServiceRequestNoTokenOnRequest() { - // Setup interceptor + public void testServiceRequestWithPOSTandNoTokenOnRequest() { + // Should not process with a POST request and no token + new SessionTokenAjaxInterceptor().serviceRequest(new MockRequest()); + } + + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not allow GET request with token parameter + new SessionTokenAjaxInterceptor().serviceRequest(request); + } + + @Test + public void testServiceRequestWithGETandNoToken() { SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); MyBackingComponent component = new MyBackingComponent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Process request - interceptor.serviceRequest(new MockRequest()); + // Setup GET request with no token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + // Should allow GET request with no token parameter + interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for GET request with no token", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java index 2c62105ac..b9425d353 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java @@ -1,7 +1,6 @@ package com.github.bordertech.wcomponents.container; import com.github.bordertech.wcomponents.AbstractWComponentTestCase; -import com.github.bordertech.wcomponents.ContentEscape; import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.MockWEnvironment; import com.github.bordertech.wcomponents.Request; @@ -18,82 +17,54 @@ */ public class SessionTokenContentInterceptor_Test extends AbstractWComponentTestCase { + private static final String VALID_TOKEN = "X"; + @Before public void setupUIC() { + // Set up user context and session token + Environment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); UIContext uic = createUIContext(); - uic.setEnvironment(new MockWEnvironment()); + uic.setEnvironment(env); setActiveContext(uic); } @Test(expected = SessionTokenException.class) public void testServiceRequestNoTokenOnUIC() { - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - interceptor.serviceRequest(new MockRequest()); + // Clear token on Context + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process if UIC has no session token + new SessionTokenContentInterceptor().serviceRequest(new MockRequest()); } - @Test - public void testServiceRequestCorrectToken() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Setup request - MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - // Process request - interceptor.serviceRequest(request); - Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithPOST() { + // Should not process with a POST request + new SessionTokenContentInterceptor().serviceRequest(new MockRequest()); } - @Test(expected = SessionTokenException.class) - public void testServiceRequestInvalidToken() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Setup invalid request + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with token MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - // Process request - interceptor.serviceRequest(request); - } - - @Test(expected = SessionTokenException.class) - public void testServiceRequestNoTokenOnRequest() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Process request - interceptor.serviceRequest(new MockRequest()); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process with a GET request with a token + new SessionTokenContentInterceptor().serviceRequest(request); } - @Test(expected = ContentEscape.class) - public void testServiceRequestNoTokenWIthCachedContent() { + @Test + public void testServiceRequestWithGETandNoToken() { // Setup interceptor SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); MyBackingContent component = new MyBackingContent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - uic.setUI(component); - // Setup request - TargetID makes the WContent trigger the ContentEscape + // Setup request MockRequest request = new MockRequest(); - request.setParameter(Environment.TARGET_ID, component.getId()); - // Set cached content - component.setCacheKey("mykey"); - // Process request + request.setMethod("GET"); + // Should process with a GET request and no token interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for GET request and no token", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java index 2328a4a9a..5c9efdd89 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java @@ -6,10 +6,10 @@ import com.github.bordertech.wcomponents.Request; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; -import com.github.bordertech.wcomponents.UIContextImpl; import com.github.bordertech.wcomponents.WApplication; import com.github.bordertech.wcomponents.util.mock.MockRequest; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; /** @@ -20,90 +20,112 @@ */ public class SessionTokenInterceptor_Test extends AbstractWComponentTestCase { - @Test - public void testServiceRequestCorrectToken() { + private static final String VALID_TOKEN = "X"; + private static final String INVALID_TOKEN = "Y"; + @Before + public void setupUIC() { + // Set up user context and session token + MockWEnvironment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); + UIContext uic = createUIContext(); + uic.setEnvironment(env); + setActiveContext(uic); + } + + @Test + public void testServiceRequestWithPOSTandCorrectToken() { // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); + // Setup request with valid token MockRequest request = new MockRequest(); - - // Setup matching tokens on session and request - uic.getEnvironment().setSessionToken("X"); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - - // Process request + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should process POST request with correct token interceptor.serviceRequest(request); Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); } - @Test - public void testServiceRequestIncorrectToken() { - // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + @Test(expected = SessionTokenException.class) + public void testServiceRequestWithPOSTandIncorrectToken() { + // Setup request with invalid token MockRequest request = new MockRequest(); - - // Setup tokens that dont match on session and request - uic.getEnvironment().setSessionToken("X"); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - - try { - // Process request - interceptor.serviceRequest(request); - Assert.fail("Should have thrown an excpetion for incorrect token"); - } catch (SessionTokenException e) { - Assert.assertFalse("Action phase should not have occurred for token error", component.handleRequestCalled); - } + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, INVALID_TOKEN); + // Should not process POST request with incorrect token + new SessionTokenInterceptor().serviceRequest(request); } - @Test + @Test(expected = SessionTokenException.class) public void testSessionTimeout() { - // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + // Clear session token on UIC (simulate new session from timeout) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Simulate request parameter from previous session (new session has null token) MockRequest request = new MockRequest(); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a POST request with a token and null session token + new SessionTokenInterceptor().serviceRequest(request); + } - // Simulate request parameter from previous session (new session has null token) - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - try { - // Process request - interceptor.serviceRequest(request); - Assert.fail("Should have thrown an excpetion for incorrect token"); - } catch (SessionTokenException e) { - Assert.assertFalse("Action phase should not have occurred for session timeout", component.handleRequestCalled); - Assert.assertEquals("Step count should not have been incremented for session timeout", 0, uic.getEnvironment().getStep()); - } + @Test(expected = SessionTokenException.class) + public void testNewSessionWithPOSTRequestWithToken() { + // Clear session token on UIC (simulate new session) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Setup POST request with a token + MockRequest request = new MockRequest(); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a POST request with a token and a new session + new SessionTokenInterceptor().serviceRequest(request); + } + + @Test(expected = SessionTokenException.class) + public void testNewSessionWithPOSTRequestWithNoToken() { + // Clear session token on UIC (simulate new session) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process a POST request with no token and a new session + new SessionTokenInterceptor().serviceRequest(new MockRequest()); } @Test - public void testNewSession() { + public void testNewSessionWithGETRequest() { // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); + SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); UIContext uic = UIContextHolder.getCurrent(); - - // Check no session token (ie new session) - Assert.assertNull("Session token should be null for new session", uic.getEnvironment().getSessionToken()); - - // Test default state (ie no params and new session) + // Clear session token on UIC (simulate new session) + uic.getEnvironment().setSessionToken(null); + // Setup GET Request with no token MockRequest request = new MockRequest(); + request.setMethod("GET"); interceptor.serviceRequest(request); interceptor.preparePaint(request); + Assert.assertTrue("Action phase should have occurred for new session", component.handleRequestCalled); Assert.assertNotNull("Session token should be set for new session", uic.getEnvironment().getSessionToken()); } - private SessionTokenInterceptor setupInterceptor() { - MyBackingComponent component = new MyBackingComponent(); + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with a token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a GET request with a token + new SessionTokenInterceptor().serviceRequest(request); + } + + @Test + public void testServiceRequestWithGETandNoToken() { + // Setup interceptor SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); - interceptor.setBackingComponent(component); - UIContext uic = new UIContextImpl(); - uic.setUI(component); - uic.setEnvironment(new MockWEnvironment()); - setActiveContext(uic); - return interceptor; + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); + // Setup GET request with no token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + // Should process GET request with no token + interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for new session", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java index bd2fb7182..c39aa128d 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java @@ -28,8 +28,8 @@ import org.junit.experimental.categories.Category; /** - * Tests to check the performance of WComponent XML rendering. This test does not check that the XML output is correct - - * see the tests for each Renderer. + * Tests to check the performance of WComponent XML rendering. This test does not check that the XML output is correct - see the tests for each + * Renderer. * * @author Yiannis Paschalidis * @since 1.0.0 @@ -236,6 +236,7 @@ private void sendRequest(final WComponent comp, final UIContext uic) { setActiveContext(uic); MockRequest request = new MockRequest(); + request.setMethod("GET"); try { root.serviceRequest(request); diff --git a/wcomponents-theme/src/main/js/wc/ajax/Trigger.js b/wcomponents-theme/src/main/js/wc/ajax/Trigger.js index fa0252d75..e376f3412 100755 --- a/wcomponents-theme/src/main/js/wc/ajax/Trigger.js +++ b/wcomponents-theme/src/main/js/wc/ajax/Trigger.js @@ -704,6 +704,9 @@ function(tag, event, serialize, Widget, getAncestorOrSelf, ajax, formUpdateManag result = addToQueryString(result, serialize.serialize(region.getElementsByTagName(TAG.SELECT))); result = addToQueryString(result, serialize.serialize(region.getElementsByTagName(TAG.TEXTAREA))); result = addToQueryString(result, serialize.serialize(stateContainer.getElementsByTagName(TAG.INPUT))); + if (instance.method !== instance.METHODS.GET) { + result = addToQueryString(result, serialize.serialize(document.getElementsByName("wc_t"))); + } } else { formUpdateManager.update(form); result = serialize.serialize(form); diff --git a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js index 1d664a884..4d77086b0 100755 --- a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js +++ b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js @@ -893,6 +893,7 @@ function (attribute, prefetch, event, initialise, uid, Trigger, has, clearSelect */ function sendFile(uri, uploadName, fileId, file, callback) { var request, xhr, formData = new FormData(), + token = document.getElementById("wc_t"), onProgress = progressEventFactory(fileId), onError = errorHandlerFactory(fileId), onAbort = abortHandlerFactory(fileId); @@ -905,6 +906,10 @@ function (attribute, prefetch, event, initialise, uid, Trigger, has, clearSelect * The name, however, is a readonly property of blob and while we may appear to have overridden the value we probably haven't. */ formData.append(uploadName, file, file.name); + // Add session token + if (token) { + formData.append("wc_t", token.value); + } request = { url: uri, From 4ab3fc1e3557851fddd9116cc7fb066bebe0199e Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 16:05:09 +1100 Subject: [PATCH 05/32] Update WebUtilites to check for null URL in getPath method --- .../com/github/bordertech/wcomponents/WebUtilities.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java index 0131b0653..be49eda44 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java @@ -498,6 +498,12 @@ public static String getPath(final String url, final Map paramet */ public static String getPath(final String url, final Map parameters, final boolean javascript) { + + // Check URL provided + if (url == null) { + throw new IllegalArgumentException("URL must be provided."); + } + // Have we already got some parameters? int index = url.indexOf('?'); boolean hasVars = false; From 0ab4a04a855ed1380d01986969ab2160e7807a52 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 12 Nov 2025 16:18:07 +1100 Subject: [PATCH 06/32] Kick the can down the road to 2040 for date parser unit test --- wcomponents-theme/src/test/intern/wc.date.parser.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wcomponents-theme/src/test/intern/wc.date.parser.test.js b/wcomponents-theme/src/test/intern/wc.date.parser.test.js index bcd28e4e2..c4b2a31a1 100755 --- a/wcomponents-theme/src/test/intern/wc.date.parser.test.js +++ b/wcomponents-theme/src/test/intern/wc.date.parser.test.js @@ -114,19 +114,19 @@ define(["intern!object", "intern/chai!assert", "intern/resources/test.utils!"], }, testParserStndExpandYear: function() { var parser = getParser(standardMasks, false, false), - result = parser.parse("281025"); + result = parser.parse("281040"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].day, 28); assert.strictEqual(result[0].month, 10); - assert.strictEqual(result[0].year, 2025); + assert.strictEqual(result[0].year, 2040); }, testParserStndExpandYearPast: function() { var parser = getParser(standardMasks, true, false), - result = parser.parse("281025"); + result = parser.parse("281045"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].day, 28); assert.strictEqual(result[0].month, 10); - assert.strictEqual(result[0].year, 1925); + assert.strictEqual(result[0].year, 1945); }, testParserStndMonthAbbr: function() { var parser = getParser(standardMasks, false, false), From 02f5d7bf1f832960ff4b9e370f3c4aed54b0ddd5 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 24 Nov 2025 16:32:48 +1100 Subject: [PATCH 07/32] Update sonar plugin in Github Actions --- .github/workflows/github-actions-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-build.yml b/.github/workflows/github-actions-build.yml index 0dc721a12..05d03519e 100644 --- a/.github/workflows/github-actions-build.yml +++ b/.github/workflows/github-actions-build.yml @@ -48,7 +48,7 @@ jobs: echo "Sonar secure variables NOT available" else echo "Sonar secure variables ARE available" - mvn -B sonar:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" + mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 48941685d57b27396fcbf8ed98efdcb599570c8a Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 28 Nov 2025 09:21:11 +1000 Subject: [PATCH 08/32] Updated beanutils as it had a transient dependency on commons-collections that has security vulnerabilties. --- CHANGELOG.md | 2 ++ wcomponents-core/pom.xml | 10 +++++++--- .../wcomponents/AbstractBeanBoundTableModel.java | 2 +- .../wcomponents/AbstractBeanTableDataModel.java | 2 +- .../wcomponents/SimpleBeanBoundTableDataModel.java | 2 +- .../wcomponents/SimpleBeanBoundTableModel.java | 2 +- .../wcomponents/SimpleBeanListTableDataModel.java | 2 +- .../wcomponents/SimpleBeanTreeTableDataModel.java | 2 +- .../bordertech/wcomponents/WBeanComponent.java | 2 +- .../github/bordertech/wcomponents/WRepeater.java | 2 +- .../wcomponents/AbstractWComponentTestCase.java | 2 +- .../wcomponents/WComponentsPerformance_Test.java | 2 +- .../wcomponents/WMultiFileWidget_Test.java | 10 ++++------ .../servlet/WServletPerformance_Test.java | 2 +- wcomponents-examples/pom.xml | 13 ------------- .../wcomponents/examples/LinkOptionsExample.java | 12 +++++++++--- .../examples/datatable/DataTableOptionsExample.java | 2 +- .../WMultiSelectPairTestingExample_Test.java | 2 +- .../examples/theme/WCheckBoxSelectExample_Test.java | 2 +- .../theme/WRadioButtonSelectExample_Test.java | 2 +- .../wcomponents/test/selenium/ByLabel.java | 2 +- .../SeleniumCheckableGroupInputWebElement.java | 2 +- 22 files changed, 39 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a59cd4c1a..ccde2a236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ creating the URLs for Targetable components and excludes the session token. * Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session token can be excluded. +* Updated beanutils version and package names as beanutils had a transient dependency on commons-collections that has security vulnerabilies. + * commons-beanutils:commons-beanutils:1.11.0 to org.apache.commons:commons-beanutils2:2.0.0-M2 ### Bug Fixes diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 1eb6d2cb3..1739c3cd1 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -129,15 +129,19 @@ - commons-beanutils - commons-beanutils - 1.11.0 + org.apache.commons + commons-beanutils2 + 2.0.0-M2 commons-logging commons-logging + + org.apache.commons + commons-lang3 + diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanBoundTableModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanBoundTableModel.java index c99a53923..b9dd89094 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanBoundTableModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanBoundTableModel.java @@ -1,7 +1,7 @@ package com.github.bordertech.wcomponents; import com.github.bordertech.wcomponents.WTable.BeanBoundTableModel; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanTableDataModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanTableDataModel.java index e230090bd..d55e9a19b 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanTableDataModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractBeanTableDataModel.java @@ -1,6 +1,6 @@ package com.github.bordertech.wcomponents; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableDataModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableDataModel.java index defa9d8fc..c1a7949b6 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableDataModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableDataModel.java @@ -5,7 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableModel.java index b7e1b17bd..2c579d292 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanBoundTableModel.java @@ -7,7 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanListTableDataModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanListTableDataModel.java index aec0d4d43..e56bd45a7 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanListTableDataModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanListTableDataModel.java @@ -6,7 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanTreeTableDataModel.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanTreeTableDataModel.java index 7562fdffd..cbee423d4 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanTreeTableDataModel.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/SimpleBeanTreeTableDataModel.java @@ -7,7 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WBeanComponent.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WBeanComponent.java index ec1cfcb7e..990c77330 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WBeanComponent.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WBeanComponent.java @@ -6,7 +6,7 @@ import java.io.Serializable; import java.util.Map; import java.util.Objects; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WRepeater.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WRepeater.java index 584b4e962..8b15189ab 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WRepeater.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WRepeater.java @@ -12,7 +12,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/AbstractWComponentTestCase.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/AbstractWComponentTestCase.java index 97f880d56..95619f3c7 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/AbstractWComponentTestCase.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/AbstractWComponentTestCase.java @@ -12,7 +12,7 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; -import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils2.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WComponentsPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WComponentsPerformance_Test.java index 0cb81141a..e729673b6 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WComponentsPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WComponentsPerformance_Test.java @@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils2.BeanUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Assert; diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WMultiFileWidget_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WMultiFileWidget_Test.java index 91e1c6ec9..a7126fd0e 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WMultiFileWidget_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WMultiFileWidget_Test.java @@ -10,7 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.fileupload.FileItem; import org.junit.Assert; import org.junit.Test; @@ -124,8 +124,6 @@ public void testGetFile() { Assert.assertEquals("File2 should be returned for index 1", TEST_FILE_ITEM_WRAP2, widget. getFile("2")); } - - @Test public void testGetMimeType() { @@ -323,18 +321,18 @@ public void testSetFileTypesAsNullOrEmptyList() { @Test public void testMaxFileSizeAccessors() { assertAccessorsCorrect(new WMultiFileWidget(), WMultiFileWidget::getMaxFileSize, WMultiFileWidget::setMaxFileSize, - 10240000L, 1L, 2L); + 10240000L, 1L, 2L); } @Test public void testDuplicateComponentModels() { WMultiFileWidget multiFileWidget = new WMultiFileWidget(); - assertNoDuplicateComponentModels(multiFileWidget,"maxFileSize", 2012312312); + assertNoDuplicateComponentModels(multiFileWidget, "maxFileSize", 2012312312); assertNoDuplicateComponentModels(multiFileWidget, "maxFiles", 123); assertNoDuplicateComponentModels(multiFileWidget, "newUpload", true); assertNoDuplicateComponentModels(multiFileWidget, "useThumbnails", true); assertNoDuplicateComponentModels(multiFileWidget, "thumbnailPosition", WLink.ImagePosition.SOUTH); - assertNoDuplicateComponentModels(multiFileWidget, "thumbnailSize", new Dimension(22,33)); + assertNoDuplicateComponentModels(multiFileWidget, "thumbnailSize", new Dimension(22, 33)); // TODO: See issue #1574 https://github.com/BorderTech/wcomponents/issues/1574 // assertNoDuplicateComponentModels(multiFileWidget, "fileUploadRequestId", "testId"); // No such method exception as it's a private method } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/servlet/WServletPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/servlet/WServletPerformance_Test.java index 703b75a55..09ee638ce 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/servlet/WServletPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/servlet/WServletPerformance_Test.java @@ -29,7 +29,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils2.BeanUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Assert; diff --git a/wcomponents-examples/pom.xml b/wcomponents-examples/pom.xml index 651b7e195..9766f67ea 100755 --- a/wcomponents-examples/pom.xml +++ b/wcomponents-examples/pom.xml @@ -57,19 +57,6 @@ ${project.version} - - commons-validator - commons-validator - 1.10.0 - - - - commons-logging - commons-logging - - - - com.github.bordertech.wcomponents diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/LinkOptionsExample.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/LinkOptionsExample.java index f72556a38..0eac430a5 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/LinkOptionsExample.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/LinkOptionsExample.java @@ -17,10 +17,12 @@ import com.github.bordertech.wcomponents.WTextField; import com.github.bordertech.wcomponents.validation.ValidatingAction; import com.github.bordertech.wcomponents.validation.WValidationErrors; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.apache.commons.validator.routines.UrlValidator; /** * LinkOptionsExample contains a series of controls for displaying and manipulating an example link. @@ -151,8 +153,12 @@ public void executeOnValid(final ActionEvent event) { * @return true if valid */ private boolean isValidUrl(final String url) { - UrlValidator validator = new UrlValidator(); - return validator.isValid(url); + try { + new URL(url).toURI(); + return true; + } catch (MalformedURLException | URISyntaxException e) { + return false; + } } /** diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/datatable/DataTableOptionsExample.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/datatable/DataTableOptionsExample.java index 734ac6c95..ae275dcc6 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/datatable/DataTableOptionsExample.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/datatable/DataTableOptionsExample.java @@ -31,7 +31,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import org.apache.commons.beanutils.BeanComparator; +import org.apache.commons.beanutils2.BeanComparator; /** * This class demonstrates the options available on a {@link WDataTable}. diff --git a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/WMultiSelectPairTestingExample_Test.java b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/WMultiSelectPairTestingExample_Test.java index 431c3a874..7ef87a5dd 100644 --- a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/WMultiSelectPairTestingExample_Test.java +++ b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/WMultiSelectPairTestingExample_Test.java @@ -5,7 +5,7 @@ import com.github.bordertech.wcomponents.test.selenium.driver.SeleniumWComponentsWebDriver; import com.github.bordertech.wcomponents.test.selenium.element.SeleniumWMultiSelectPairWebElement; import java.util.List; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WCheckBoxSelectExample_Test.java b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WCheckBoxSelectExample_Test.java index facecc4bb..4763278af 100644 --- a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WCheckBoxSelectExample_Test.java +++ b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WCheckBoxSelectExample_Test.java @@ -7,7 +7,7 @@ import com.github.bordertech.wcomponents.test.selenium.driver.SeleniumWComponentsWebDriver; import com.github.bordertech.wcomponents.test.selenium.element.SeleniumWCheckBoxSelectWebElement; import com.github.bordertech.wcomponents.util.SystemException; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WRadioButtonSelectExample_Test.java b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WRadioButtonSelectExample_Test.java index 75c91604b..e03a6f5a1 100644 --- a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WRadioButtonSelectExample_Test.java +++ b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/theme/WRadioButtonSelectExample_Test.java @@ -6,7 +6,7 @@ import com.github.bordertech.wcomponents.test.selenium.MultiBrowserRunner; import com.github.bordertech.wcomponents.test.selenium.driver.SeleniumWComponentsWebDriver; import com.github.bordertech.wcomponents.test.selenium.element.SeleniumWRadioButtonSelectWebElement; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/ByLabel.java b/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/ByLabel.java index f5d740464..fe1eb8c30 100644 --- a/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/ByLabel.java +++ b/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/ByLabel.java @@ -4,7 +4,7 @@ import com.github.bordertech.wcomponents.test.selenium.element.SeleniumWComponentWebProperties; import java.util.ArrayList; import java.util.List; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; diff --git a/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/element/SeleniumCheckableGroupInputWebElement.java b/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/element/SeleniumCheckableGroupInputWebElement.java index e37caf0e4..25ad92f1b 100644 --- a/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/element/SeleniumCheckableGroupInputWebElement.java +++ b/wcomponents-test-lib/src/main/java/com/github/bordertech/wcomponents/test/selenium/element/SeleniumCheckableGroupInputWebElement.java @@ -5,7 +5,7 @@ import com.github.bordertech.wcomponents.util.SystemException; import java.util.ArrayList; import java.util.List; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; From 70d231039527b8e747247d6aa241816438464a0b Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 28 Nov 2025 12:18:26 +1000 Subject: [PATCH 09/32] Updated antisamy to latest version 1.7.8 as it has reinstated the xHTML behaviour for tags --- CHANGELOG.md | 4 ++- wcomponents-core/pom.xml | 27 ++----------------- .../render/webxml/WLabelRenderer_Test.java | 4 +-- .../util/HtmlSanitizerUtil_Test.java | 4 +-- 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccde2a236..8956b9bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ * Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session token can be excluded. * Updated beanutils version and package names as beanutils had a transient dependency on commons-collections that has security vulnerabilies. - * commons-beanutils:commons-beanutils:1.11.0 to org.apache.commons:commons-beanutils2:2.0.0-M2 + * commons-beanutils:commons-beanutils:1.11.0 to org.apache.commons:commons-beanutils2:2.0.0-M2 +* Updated antisamy to latest version 1.7.8 as it has reinstated the xHTML behaviour for tags. Versions 1.7.0 to 1.7.6 did not support xHTML and would break the XML. + * org.owasp.antisamy:antismay from 1.6.8 to 1.7.8 ### Bug Fixes diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 1739c3cd1..0d3c61620 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -228,51 +228,28 @@ - - + org.owasp.antisamy antisamy - 1.6.8 + 1.7.8 - - org.slf4j - slf4j-api - org.apache.xmlgraphics batik-css - - commons-io - commons-io - org.apache.httpcomponents.client5 httpclient5 - - org.apache.httpcomponents.core5 - httpcore5 - - - net.sourceforge.htmlunit - neko-htmlunit - xerces xercesImpl - - - net.sourceforge.htmlunit - neko-htmlunit - 2.70.0 - org.apache.xmlgraphics batik-css diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WLabelRenderer_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WLabelRenderer_Test.java index a2b2cf9cc..89506e316 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WLabelRenderer_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WLabelRenderer_Test.java @@ -94,12 +94,12 @@ public void testDoPaintAllOptions() throws IOException, SAXException, XpathExcep @Test public void testSanitizedText() throws IOException, SAXException, XpathException { MyInput comp = new MyInput(); - WLabel label = new WLabel("
content

", comp); + WLabel label = new WLabel("
content

", comp); label.setEncodeText(false); label.setSanitizeOnOutput(true); assertSchemaMatch(label); String xml = toXHtml(label); - Assert.assertTrue("Label text should contain sanitized xml", xml.contains("content
")); + Assert.assertTrue("Label text should contain sanitized xml", xml.contains("content
")); } @Test diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil_Test.java index d24f50392..1d9865d4e 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil_Test.java @@ -174,8 +174,8 @@ public void testStrictSanitizerElement() throws ScanException, PolicyException { @Test public void testLaxScanVoidElements() throws ScanException, PolicyException { - // Antisamy as of 1.7.X does not support xhtml and will remove the closing tag on "void" elements which will break the XML - String input = "
"; + // Note - Antisamy 1.7.0 to 1.7.6 does not support xhtml and will remove the closing tag on "void" elements which will break the XML + String input = "
"; Assert.assertEquals(input, HtmlSanitizerUtil.sanitize(input, true)); } From cd5aa224b1af2b699d2a59590ebcab3f19f3720c Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 28 Nov 2025 18:20:13 +1100 Subject: [PATCH 10/32] Updated FileUtil to include MetaData hints when calling tika to help tika identify a files content type. --- CHANGELOG.md | 1 + .../bordertech/wcomponents/util/FileUtil.java | 12 +- .../wcomponents/util/FileUtil_Test.java | 111 +++++++++++++++--- .../content/text-non-ascii-less-10-per.txt | 1 + .../content/text-non-ascii-more-10-per.txt | 1 + 5 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 wcomponents-core/src/test/resources/content/text-non-ascii-less-10-per.txt create mode 100644 wcomponents-core/src/test/resources/content/text-non-ascii-more-10-per.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 8956b9bf8..5ac5ebd52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * commons-beanutils:commons-beanutils:1.11.0 to org.apache.commons:commons-beanutils2:2.0.0-M2 * Updated antisamy to latest version 1.7.8 as it has reinstated the xHTML behaviour for tags. Versions 1.7.0 to 1.7.6 did not support xHTML and would break the XML. * org.owasp.antisamy:antismay from 1.6.8 to 1.7.8 +* Updated FileUtil to include MetaData hints when calling tika to help tika identify a files content type. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java index 248bcffec..c81cbee96 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java @@ -10,6 +10,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tika.Tika; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; /** * Utility methods for {@link File}. @@ -98,7 +100,15 @@ public static String getFileMimeType(final File file) { if (file != null) { try { final Tika tika = new Tika(); - return tika.detect(file.getInputStream()); + // Setup metatdata hints to help Tika detect the mime type + Metadata meta = new Metadata(); + if (file.getName() != null) { + meta.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getName()); + } + if (file.getMimeType() != null) { + meta.set(TikaCoreProperties.CONTENT_TYPE_HINT, file.getMimeType()); + } + return tika.detect(file.getInputStream(), meta); } catch (IOException ex) { LOG.error("Invalid file, name " + file.getName(), ex); } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java index 73403c10e..a490fcc8f 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java @@ -1,5 +1,6 @@ package com.github.bordertech.wcomponents.util; +import com.github.bordertech.wcomponents.file.File; import com.github.bordertech.wcomponents.file.FileItemWrap; import com.github.bordertech.wcomponents.util.mock.MockFileItem; import java.io.IOException; @@ -12,6 +13,7 @@ /** * FileUtil_Test - unit test for {@link FileUtil}. + * * @author Aswin Kandula * @since 1.5 */ @@ -32,7 +34,7 @@ public void testValidateFileTypeImageFile() throws IOException { public void testValidateFileTypeAnyFile() throws IOException { boolean validateFileType = FileUtil.validateFileType(null, null); Assert.assertFalse(validateFileType); - + FileItem newFileItem = createFileItem(null); validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Collections.EMPTY_LIST); Assert.assertTrue(validateFileType); @@ -44,35 +46,35 @@ public void testValidateFileTypePdfFile() throws IOException { boolean validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList("application/pdf")); Assert.assertTrue(validateFileType); } - + @Test public void testValidateFileTypeTr5File() throws IOException { // 'tr5' file has no mime type, so validation will pass with extension only. FileItem newFileItem = createFileItem("/content/test.tr5"); boolean validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList(".tr5")); Assert.assertTrue(validateFileType); - + newFileItem = createFileItem("/content/test.tr5"); validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList("text/plain")); Assert.assertTrue(validateFileType); - + newFileItem = createFileItem("/content/test.tr5"); validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList("image/jpg")); Assert.assertFalse(validateFileType); } - + @Test public void testValidateFileTypeDodgyTr5File() throws IOException { // 'tr5' file has no mime type, so validation will pass with extension only. FileItem newFileItem = createFileItem("/content/dodgy.pdf.tr5"); boolean validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList(".tr5")); Assert.assertTrue(validateFileType); - + newFileItem = createFileItem("/content/dodgy.pdf.tr5"); validateFileType = FileUtil.validateFileType(new FileItemWrap(newFileItem), Arrays.asList("text/plain")); Assert.assertFalse(validateFileType); } - + @Test public void testValidateFileSize() throws IOException { FileItem newFileItem = createFileItem(null); @@ -81,20 +83,20 @@ public void testValidateFileSize() throws IOException { validateFileSize = FileUtil.validateFileSize(new FileItemWrap(newFileItem), 50); Assert.assertFalse(validateFileSize); - + FileUtil.validateFileSize(null, 0); Assert.assertFalse(validateFileSize); - + validateFileSize = FileUtil.validateFileSize(new FileItemWrap(newFileItem), -1000); Assert.assertTrue(validateFileSize); } - + @Test public void testReadableFileSize() { String readableFileSize = FileUtil.readableFileSize(10101); Assert.assertEquals("10.1 KB", readableFileSize); } - + @Test public void testGetInvalidFileTypeMessage() { String invalidFileTypeMessage = FileUtil.getInvalidFileTypeMessage(null); @@ -103,16 +105,40 @@ public void testGetInvalidFileTypeMessage() { invalidFileTypeMessage = FileUtil.getInvalidFileTypeMessage(Arrays.asList("*")); Assert.assertEquals("The file you have selected is not of an accepted type. Only the following type/s are accepted: *.", invalidFileTypeMessage); } - + @Test public void testGetInvalidFileSizeMessage() { String invalidFileSizeMessage = FileUtil.getInvalidFileSizeMessage(1111); Assert.assertEquals("The file you have selected is too large. Maximum file size is 1.1 KB.", invalidFileSizeMessage); } - + + @Test + public void testGetMimeTypeForTextFileAndNoHint() throws IOException { + // Tika text detector by default will treat a file with less than 10% non-ascii characters as a text file + // Test Tika detects the file as text with no hint + MyMockFile file = new MyMockFile("/content/text-non-ascii-less-10-per.txt", null, null); + Assert.assertEquals("Incorrect type for text file that should have been detected as text with no hints", "text/plain", FileUtil.getFileMimeType(file)); + } + + @Test + public void testGetMimeTypeForTextFileWithAsciiAndNoHint() throws IOException { + // Tika text detector by default will treat a file with more than 10% non-ascii characters as not a text file + // Test providing no hint of the file name to Tika that it wont detect it as text + MyMockFile file = new MyMockFile("/content/text-non-ascii-more-10-per.txt", null, null); + Assert.assertEquals("Incorrect type for text file that should not be detected as text with no hints", "application/octet-stream", FileUtil.getFileMimeType(file)); + } + + @Test + public void testGetMimeTypeForTextFileWithAsciiAndNameHint() throws IOException { + // Tika text detector by default will treat a file with more than 10% non-ascii characters as not a text file + // Test providing a hint of the file name to Tika will detect it as text + MyMockFile file = new MyMockFile("/content/text-non-ascii-more-10-per.txt", "text-non-ascii-more-10-per.txt", null); + Assert.assertEquals("Incorrect type for text file that should be detected as text with name hint", "text/plain", FileUtil.getFileMimeType(file)); + } + /** * Create a new fileitem. - * + * * @param fileResource if {@code null} dummy byte[] are set on file, otherwise given file resource. * @return a file item */ @@ -123,21 +149,70 @@ private FileItem createFileItem(String fileResource) throws IOException { for (int i = 0; i < testFileContent.length; i++) { testFileContent[i] = (byte) (i & 0xff); } - } - else { + } else { InputStream stream = getClass().getResourceAsStream(fileResource); if (stream == null) { throw new IOException("File resource not found: " + fileResource); } - testFileContent = StreamUtil.getBytes(stream); + testFileContent = StreamUtil.getBytes(stream); } MockFileItem fileItem = new MockFileItem(); fileItem.set(testFileContent); fileItem.setFieldName(fileResource); - if (fileResource != null) { + if (fileResource != null) { String[] tokens = fileResource.split(".+?/(?=[^/]+$)"); fileItem.setName(tokens[1]); } return fileItem; } + + private static class MyMockFile implements File { + + private final String fileResource; + private final String name; + private final String mimeType; + + public MyMockFile(String fileResource, String name, String mimeType) { + this.fileResource = fileResource; + this.name = name; + this.mimeType = mimeType; + } + + @Override + public InputStream getInputStream() throws IOException { + return getClass().getResourceAsStream(fileResource); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getMimeType() { + return mimeType; + } + + @Override + public long getSize() { + throw new UnsupportedOperationException("Not used in test"); + } + + @Override + public String getFileName() { + throw new UnsupportedOperationException("Not used in test"); + } + + @Override + public byte[] getBytes() { + throw new UnsupportedOperationException("Not used in test"); + } + + @Override + public String getDescription() { + throw new UnsupportedOperationException("Not used in test"); + } + + } + } diff --git a/wcomponents-core/src/test/resources/content/text-non-ascii-less-10-per.txt b/wcomponents-core/src/test/resources/content/text-non-ascii-less-10-per.txt new file mode 100644 index 000000000..d791c2131 --- /dev/null +++ b/wcomponents-core/src/test/resources/content/text-non-ascii-less-10-per.txt @@ -0,0 +1 @@ +““XXXXXXXXXXXXXXXXXX diff --git a/wcomponents-core/src/test/resources/content/text-non-ascii-more-10-per.txt b/wcomponents-core/src/test/resources/content/text-non-ascii-more-10-per.txt new file mode 100644 index 000000000..95e5b808e --- /dev/null +++ b/wcomponents-core/src/test/resources/content/text-non-ascii-more-10-per.txt @@ -0,0 +1 @@ +“““““XX From 008725702e5c6e2e2eaa67d49a9d72109b2bba86 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 1 Dec 2025 12:56:33 +1000 Subject: [PATCH 11/32] [maven-release-plugin] prepare release wcomponents-parent-1.5.38 --- pom.xml | 4 ++-- wcomponents-bundle/pom.xml | 2 +- wcomponents-core/pom.xml | 2 +- wcomponents-examples-lde/pom.xml | 2 +- wcomponents-examples/pom.xml | 2 +- wcomponents-i18n/pom.xml | 2 +- wcomponents-lde/pom.xml | 2 +- wcomponents-test-lib/pom.xml | 2 +- wcomponents-theme/pom.xml | 2 +- wcomponents-xslt/pom.xml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index beaee6fea..e877783e1 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 pom @@ -57,7 +57,7 @@ https://github.com/bordertech/wcomponents scm:git:https://github.com/bordertech/wcomponents.git scm:git:https://github.com/bordertech/wcomponents.git - wcomponents-1.0.0 + wcomponents-parent-1.5.38 diff --git a/wcomponents-bundle/pom.xml b/wcomponents-bundle/pom.xml index df7aff26b..53c92dcde 100755 --- a/wcomponents-bundle/pom.xml +++ b/wcomponents-bundle/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 0d3c61620..58f581768 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-examples-lde/pom.xml b/wcomponents-examples-lde/pom.xml index 932637062..18a15a613 100755 --- a/wcomponents-examples-lde/pom.xml +++ b/wcomponents-examples-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-examples/pom.xml b/wcomponents-examples/pom.xml index 9766f67ea..05320ec62 100755 --- a/wcomponents-examples/pom.xml +++ b/wcomponents-examples/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-i18n/pom.xml b/wcomponents-i18n/pom.xml index d3c37fe82..cc5d7d93c 100755 --- a/wcomponents-i18n/pom.xml +++ b/wcomponents-i18n/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-lde/pom.xml b/wcomponents-lde/pom.xml index 9db7de618..59fb2c33d 100755 --- a/wcomponents-lde/pom.xml +++ b/wcomponents-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-test-lib/pom.xml b/wcomponents-test-lib/pom.xml index d138affd5..1ed355d59 100755 --- a/wcomponents-test-lib/pom.xml +++ b/wcomponents-test-lib/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 ../pom.xml diff --git a/wcomponents-theme/pom.xml b/wcomponents-theme/pom.xml index 8dabc2bca..f8a608665 100755 --- a/wcomponents-theme/pom.xml +++ b/wcomponents-theme/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 jar diff --git a/wcomponents-xslt/pom.xml b/wcomponents-xslt/pom.xml index 0f07a0a43..2aeb59c53 100644 --- a/wcomponents-xslt/pom.xml +++ b/wcomponents-xslt/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38-SNAPSHOT + 1.5.38 jar From a0660dc2e36e9d4c6f728bb23bfdb1017f984b86 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 1 Dec 2025 12:56:37 +1000 Subject: [PATCH 12/32] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- wcomponents-bundle/pom.xml | 2 +- wcomponents-core/pom.xml | 2 +- wcomponents-examples-lde/pom.xml | 2 +- wcomponents-examples/pom.xml | 2 +- wcomponents-i18n/pom.xml | 2 +- wcomponents-lde/pom.xml | 2 +- wcomponents-test-lib/pom.xml | 2 +- wcomponents-theme/pom.xml | 2 +- wcomponents-xslt/pom.xml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index e877783e1..14dd0afc3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT pom @@ -57,7 +57,7 @@ https://github.com/bordertech/wcomponents scm:git:https://github.com/bordertech/wcomponents.git scm:git:https://github.com/bordertech/wcomponents.git - wcomponents-parent-1.5.38 + wcomponents-1.0.0 diff --git a/wcomponents-bundle/pom.xml b/wcomponents-bundle/pom.xml index 53c92dcde..6bac8948b 100755 --- a/wcomponents-bundle/pom.xml +++ b/wcomponents-bundle/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 58f581768..6bac339ba 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-examples-lde/pom.xml b/wcomponents-examples-lde/pom.xml index 18a15a613..1a121cecd 100755 --- a/wcomponents-examples-lde/pom.xml +++ b/wcomponents-examples-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-examples/pom.xml b/wcomponents-examples/pom.xml index 05320ec62..e69198c84 100755 --- a/wcomponents-examples/pom.xml +++ b/wcomponents-examples/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-i18n/pom.xml b/wcomponents-i18n/pom.xml index cc5d7d93c..3f75a018d 100755 --- a/wcomponents-i18n/pom.xml +++ b/wcomponents-i18n/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-lde/pom.xml b/wcomponents-lde/pom.xml index 59fb2c33d..2585a3342 100755 --- a/wcomponents-lde/pom.xml +++ b/wcomponents-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-test-lib/pom.xml b/wcomponents-test-lib/pom.xml index 1ed355d59..4bb9a2097 100755 --- a/wcomponents-test-lib/pom.xml +++ b/wcomponents-test-lib/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT ../pom.xml diff --git a/wcomponents-theme/pom.xml b/wcomponents-theme/pom.xml index f8a608665..d5337bcc9 100755 --- a/wcomponents-theme/pom.xml +++ b/wcomponents-theme/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT jar diff --git a/wcomponents-xslt/pom.xml b/wcomponents-xslt/pom.xml index 2aeb59c53..74e19c62b 100644 --- a/wcomponents-xslt/pom.xml +++ b/wcomponents-xslt/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.38 + 1.5.39-SNAPSHOT jar From a710eca37a54da964330444f9bed2390b1535a32 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 1 Dec 2025 14:04:20 +1000 Subject: [PATCH 13/32] Roll CHANGELOG --- CHANGELOG.md | 6 +++++- wcomponents-theme/package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac5ebd52..4e5f33022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ ### API Changes ### Enhancements +### Bug Fixes + +## 1.5.38 +### Enhancements * To improve the robustness of the session token parameter (wc_t), which is used to prevent CSRF attacks, the following changes have been made: * The session token is no longer included on any GET URLs and only posted in the body for POSTS. * Modified the session token interceptors to only accept a session token on a POST and throw an exception if provided on a GET. @@ -18,7 +22,7 @@ * org.owasp.antisamy:antismay from 1.6.8 to 1.7.8 * Updated FileUtil to include MetaData hints when calling tika to help tika identify a files content type. -### Bug Fixes +NOTE - The session token changes are not backwards compatable with older themes. ## 1.5.37 diff --git a/wcomponents-theme/package.json b/wcomponents-theme/package.json index eb78483cf..de57f7d61 100644 --- a/wcomponents-theme/package.json +++ b/wcomponents-theme/package.json @@ -1,6 +1,6 @@ { "name": "wcomponents-theme", - "version": "1.5.38-SNAPSHOT", + "version": "1.5.39-SNAPSHOT", "description": "Client side code for WComponents UI tool kit.", "private": true, "com_github_bordertech": { From a6ff85df1e7beebc6ec6781cf538b282f7dd7f02 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 12 Dec 2025 16:26:22 +1100 Subject: [PATCH 14/32] Make backing FileItem available in FileItemWrap --- .../github/bordertech/wcomponents/file/FileItemWrap.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/file/FileItemWrap.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/file/FileItemWrap.java index e99802be5..4d53417e2 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/file/FileItemWrap.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/file/FileItemWrap.java @@ -26,6 +26,13 @@ public FileItemWrap(final FileItem item) { backing = item; } + /** + * @return the backing file item. + */ + public FileItem getBacking() { + return backing; + } + /** * The name of the file as supplied by the client. Depending on the client this may or may not include the full path * to the file. From f0ec781073826ed86d531571446290ef81762bc5 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 9 Jan 2026 13:19:37 +1100 Subject: [PATCH 15/32] Make stream handling more consistent --- .../wcomponents/WMultiFileWidget.java | 12 ++- .../wcomponents/servlet/ServletUtil.java | 61 ++++++++------- .../bordertech/wcomponents/util/FileUtil.java | 37 +++++----- .../wcomponents/util/HtmlSanitizerUtil.java | 2 + .../util/thumbnail/ThumbnailUtil.java | 74 ++++++++++--------- 5 files changed, 96 insertions(+), 90 deletions(-) diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java index 6e22570d9..fcef054c3 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java @@ -9,6 +9,7 @@ import com.github.bordertech.wcomponents.util.Util; import com.github.bordertech.wcomponents.util.thumbnail.ThumbnailUtil; import java.awt.Dimension; +import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -779,8 +780,7 @@ protected void doHandleThumbnailRequest(final FileWidgetUpload file) { * @param file the file to process */ protected void doHandleFileContentRequest(final FileWidgetUpload file) { - ContentEscape escape = new ContentEscape(file.getFile()); - throw escape; + throw new ContentEscape(file.getFile()); } /** @@ -788,15 +788,13 @@ protected void doHandleFileContentRequest(final FileWidgetUpload file) { * @return the thumbnail */ protected Image createThumbNail(final File file) { - Image image = null; - try { + try (InputStream stream = file.getInputStream()) { Dimension size = getThumbnailSize(); - image = ThumbnailUtil.createThumbnail(file.getInputStream(), file.getName(), size, file. - getMimeType()); + return ThumbnailUtil.createThumbnail(stream, file.getName(), size, file.getMimeType()); } catch (Exception e) { LOG.error("Could not generate thumbnail for file. " + e.getMessage(), e); + return null; } - return image; } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java index 112633183..ee206439e 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java @@ -20,7 +20,6 @@ import com.github.bordertech.wcomponents.container.DataListInterceptor; import com.github.bordertech.wcomponents.container.DebugStructureInterceptor; import com.github.bordertech.wcomponents.container.FormInterceptor; -import com.github.bordertech.wcomponents.container.TemplateRenderInterceptor; import com.github.bordertech.wcomponents.container.InterceptorComponent; import com.github.bordertech.wcomponents.container.PageShellInterceptor; import com.github.bordertech.wcomponents.container.ResponseCacheInterceptor; @@ -31,6 +30,7 @@ import com.github.bordertech.wcomponents.container.SubordinateControlInterceptor; import com.github.bordertech.wcomponents.container.TargetableErrorInterceptor; import com.github.bordertech.wcomponents.container.TargetableInterceptor; +import com.github.bordertech.wcomponents.container.TemplateRenderInterceptor; import com.github.bordertech.wcomponents.container.TransformXMLInterceptor; import com.github.bordertech.wcomponents.container.UIContextDumpInterceptor; import com.github.bordertech.wcomponents.container.ValidateXMLInterceptor; @@ -230,37 +230,41 @@ public static void handleStaticResourceRequest(final HttpServletRequest request, } InputStream resourceStream = staticResource.getStream(); - if (resourceStream == null) { - LOG.warn( - "Static resource [" + staticRequest + "] not found. Stream for content is null."); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - int size = resourceStream.available(); - String fileName = WebUtilities.encodeForContentDispositionHeader(staticRequest. - substring(staticRequest - .lastIndexOf('/') + 1)); + try { + if (resourceStream == null) { + LOG.warn( + "Static resource [" + staticRequest + "] not found. Stream for content is null."); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } - if (size > 0) { - response.setContentLength(size); - } + int size = resourceStream.available(); + String fileName = WebUtilities.encodeForContentDispositionHeader(staticRequest. + substring(staticRequest + .lastIndexOf('/') + 1)); - response.setContentType(WebUtilities.getContentType(staticRequest)); - response.setHeader("Cache-Control", CacheType.CONTENT_CACHE.getSettings()); + if (size > 0) { + response.setContentLength(size); + } - String param = request.getParameter(WContent.URL_CONTENT_MODE_PARAMETER_KEY); - if ("inline".equals(param)) { - response.setHeader("Content-Disposition", "inline; filename=" + fileName); - } else if ("attach".equals(param)) { - response.setHeader("Content-Disposition", "attachment; filename=" + fileName); - } else { - // added "filename=" to comply with https://tools.ietf.org/html/rfc6266 - response.setHeader("Content-Disposition", "filename=" + fileName); - } + response.setContentType(WebUtilities.getContentType(staticRequest)); + response.setHeader("Cache-Control", CacheType.CONTENT_CACHE.getSettings()); + + String param = request.getParameter(WContent.URL_CONTENT_MODE_PARAMETER_KEY); + if ("inline".equals(param)) { + response.setHeader("Content-Disposition", "inline; filename=" + fileName); + } else if ("attach".equals(param)) { + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + } else { + // added "filename=" to comply with https://tools.ietf.org/html/rfc6266 + response.setHeader("Content-Disposition", "filename=" + fileName); + } - if (!headersOnly) { - StreamUtil.copy(resourceStream, response.getOutputStream()); + if (!headersOnly) { + StreamUtil.copy(resourceStream, response.getOutputStream()); + } + } finally { + StreamUtil.safeClose(resourceStream); } } catch (IOException e) { LOG.warn("Could not process static resource [" + staticRequest + "]. ", e); @@ -650,6 +654,7 @@ public static void extractParameterMap(final HttpServletRequest request, final M /** * Find the value of a cookie on the request, by name. + * * @param request The request on which to check for the cookie. * @param name The name of the cookie we want the value of. * @return The value of the cookie, if present, otherwise null. diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java index c81cbee96..5db00b0e7 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java @@ -3,6 +3,7 @@ import com.github.bordertech.wcomponents.file.File; import com.github.bordertech.wcomponents.file.FileItemWrap; import java.io.IOException; +import java.io.InputStream; import java.text.DecimalFormat; import java.util.List; import java.util.stream.Collectors; @@ -33,13 +34,11 @@ private FileUtil() { private static final Log LOG = LogFactory.getLog(FileUtil.class); /** - * Checks if the file item is one among the supplied file types. - * This first checks against file extensions, then against file mime types + * Checks if the file item is one among the supplied file types. This first checks against file extensions, then + * against file mime types * - * @param newFile the file to be checked, if null then return false - * otherwise validate - * @param fileTypes allowed file types, if null or empty return true, - * otherwise validate + * @param newFile the file to be checked, if null then return false otherwise validate + * @param fileTypes allowed file types, if null or empty return true, otherwise validate * @return {@code true} if either extension or mime-type match is successful */ public static boolean validateFileType(final FileItemWrap newFile, final List fileTypes) { @@ -53,12 +52,12 @@ public static boolean validateFileType(final FileItemWrap newFile, final List fileExts = fileTypes.stream() - .filter(fileType -> fileType.startsWith(".")) - .collect(Collectors.toList()); + .filter(fileType -> fileType.startsWith(".")) + .collect(Collectors.toList()); // filter mime types from fileTypes. final List fileMimes = fileTypes.stream() - .filter(fileType -> !fileExts.contains(fileType)) - .collect(Collectors.toList()); + .filter(fileType -> !fileExts.contains(fileType)) + .collect(Collectors.toList()); // First validate newFile against fileExts list // If extensions are supplied, then check if newFile has a name @@ -67,7 +66,7 @@ public static boolean validateFileType(final FileItemWrap newFile, final List fileExt.equals("." + split[1]))) { + && fileExts.stream().anyMatch(fileExt -> fileExt.equals("." + split[1]))) { return true; } } @@ -108,7 +107,9 @@ public static String getFileMimeType(final File file) { if (file.getMimeType() != null) { meta.set(TikaCoreProperties.CONTENT_TYPE_HINT, file.getMimeType()); } - return tika.detect(file.getInputStream(), meta); + try (InputStream stream = file.getInputStream()) { + return tika.detect(stream, meta); + } } catch (IOException ex) { LOG.error("Invalid file, name " + file.getName(), ex); } @@ -119,10 +120,8 @@ public static String getFileMimeType(final File file) { /** * Checks if the file item size is within the supplied max file size. * - * @param newFile the file to be checked, if null then return false - * otherwise validate - * @param maxFileSize max file size in bytes, if zero or negative return - * true, otherwise validate + * @param newFile the file to be checked, if null then return false otherwise validate + * @param maxFileSize max file size in bytes, if zero or negative return true, otherwise validate * @return {@code true} if file size is valid. */ public static boolean validateFileSize(final FileItemWrap newFile, final long maxFileSize) { @@ -164,8 +163,8 @@ public static String getInvalidFileTypeMessage(final List fileTypes) { return null; } return String.format(I18nUtilities.format(null, - InternalMessages.DEFAULT_VALIDATION_ERROR_FILE_WRONG_TYPE), - StringUtils.join(fileTypes.toArray(new Object[fileTypes.size()]), ",")); + InternalMessages.DEFAULT_VALIDATION_ERROR_FILE_WRONG_TYPE), + StringUtils.join(fileTypes.toArray(new Object[fileTypes.size()]), ",")); } /** @@ -176,6 +175,6 @@ public static String getInvalidFileTypeMessage(final List fileTypes) { */ public static String getInvalidFileSizeMessage(final long maxFileSize) { return String.format(I18nUtilities.format(null, InternalMessages.DEFAULT_VALIDATION_ERROR_FILE_WRONG_SIZE), - FileUtil.readableFileSize(maxFileSize)); + FileUtil.readableFileSize(maxFileSize)); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java index 885d26d1d..bf6e19639 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java @@ -159,6 +159,8 @@ public static Policy createPolicy(final String resourceName) { return Policy.getInstance(resource); } catch (PolicyException ex) { throw new SystemException("Could not create AntiSamy Policy" + ex.getMessage(), ex); + } finally { + StreamUtil.safeClose(resource); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java index d84b1e55c..d8c4feab8 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java @@ -113,6 +113,11 @@ public final class ThumbnailUtil { */ private static final String IMAGE_JPEG_FORMAT = "jpeg"; + /** + * JPEG Mime Type. + */ + private static final String MIMETYPE_JPEG = "image/jpeg"; + /** * Don't allow this utility class to be constructed. */ @@ -136,19 +141,27 @@ public static com.github.bordertech.wcomponents.Image createThumbnail(final Inpu final Dimension scaledSize, final String mimeType) { final Dimension scale = scaledSize == null ? THUMBNAIL_SCALE_SIZE : scaledSize; - // Generate thumbnail for image files - if (is != null && mimeType != null && (mimeType.equals("image/jpeg") || mimeType.equals( - "image/bmp") - || mimeType.equals("image/png") || mimeType.equals("image/gif"))) { + // Attempt to generate thumbnail for image files + if (is != null && isImageMimeType(mimeType)) { byte[] bytes = createImageThumbnail(is, scale); if (bytes != null) { - return new BytesImage(bytes, "image/jpeg", "Thumbnail of " + name, null); + return new BytesImage(bytes, MIMETYPE_JPEG, "Thumbnail of " + name, null); } } // Use default thumbnail depending on mime type - com.github.bordertech.wcomponents.Image image = handleDefaultImage(mimeType, name, scale); - return image; + return handleDefaultImage(mimeType, name, scale); + } + + /** + * @param mimeType the mime type to check + * @return true if mime type is for an image + */ + private static boolean isImageMimeType(final String mimeType) { + return mimeType != null && (mimeType.equals(MIMETYPE_JPEG) + || mimeType.equals("image/bmp") + || mimeType.equals("image/png") + || mimeType.equals("image/gif")); } /** @@ -193,7 +206,7 @@ private static com.github.bordertech.wcomponents.Image handleDefaultImage(final // Scale to correct size ByteArrayInputStream byteIs = new ByteArrayInputStream(image.getBytes()); byte[] bytes = createImageThumbnail(byteIs, scale); - image = new BytesImage(bytes, "image/jpeg", "Thumbnail of " + name, null); + image = new BytesImage(bytes, MIMETYPE_JPEG, "Thumbnail of " + name, null); } return image; } @@ -207,32 +220,28 @@ private static com.github.bordertech.wcomponents.Image handleDefaultImage(final * @return a byte[] representing the JPEG thumb nail. */ private static byte[] createImageThumbnail(final InputStream is, final Dimension scaledSize) { + // Create buffered image from input stream BufferedImage image; - MemoryCacheImageInputStream mciis; - try { - mciis = new MemoryCacheImageInputStream(is); - image = ImageIO.read(mciis); + // ImageIO closes stream + image = ImageIO.read(new MemoryCacheImageInputStream(is)); + if (image == null) { + return null; + } } catch (Exception e) { LOG.warn("Unable to read input image", e); return null; } - if (image == null) { - return null; - } - + // Create scaled image try { - byte[] jpeg = createScaledJPEG(image, scaledSize); - return jpeg; + return createScaledJPEG(image, scaledSize); } catch (Exception e) { LOG.error("Error creating thumbnail from image", e); + return null; } finally { image.flush(); } - - return null; - } /** @@ -247,12 +256,10 @@ private static byte[] createImageThumbnail(final InputStream is, final Dimension private static byte[] createScaledJPEG(final Image image, final Dimension scaledSize) throws IOException { // Scale the image. - Image scaledImage = image.getScaledInstance(scaledSize.width, scaledSize.height, - Image.SCALE_SMOOTH); + Image scaledImage = image.getScaledInstance(scaledSize.width, scaledSize.height, Image.SCALE_SMOOTH); // Create a BufferedImage copy of the scaledImage. - BufferedImage bufferedImage = new BufferedImage(scaledImage.getWidth(null), scaledImage. - getHeight(null), + BufferedImage bufferedImage = new BufferedImage(scaledImage.getWidth(null), scaledImage.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D graphics = bufferedImage.createGraphics(); graphics.drawImage(scaledImage, 0, 0, null); @@ -260,17 +267,12 @@ private static byte[] createScaledJPEG(final Image image, final Dimension scaled graphics.dispose(); // Convert the scaled image to a JPEG byte array. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - MemoryCacheImageOutputStream mciis = new MemoryCacheImageOutputStream(baos); - ImageIO.write(bufferedImage, IMAGE_JPEG_FORMAT, mciis); - mciis.flush(); - bufferedImage.flush(); - - byte[] jpeg = baos.toByteArray(); - mciis.close(); - - return jpeg; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); MemoryCacheImageOutputStream mciis = new MemoryCacheImageOutputStream(baos)) { + ImageIO.write(bufferedImage, IMAGE_JPEG_FORMAT, mciis); + mciis.flush(); + bufferedImage.flush(); + return baos.toByteArray(); + } } } From bcb7569f71ad53f0fbb6b1e3482ad7fe7374fed9 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 12 Jan 2026 16:44:44 +1100 Subject: [PATCH 16/32] Consistent use of try-with-resources when handling streams --- CHANGELOG.md | 2 + .../bordertech/wcomponents/ContentEscape.java | 14 +--- .../wcomponents/InternalResource.java | 8 +- .../bordertech/wcomponents/WebUtilities.java | 4 +- .../container/TransformXMLInterceptor.java | 7 +- .../wcomponents/monitor/UicStats.java | 28 +++---- .../wcomponents/servlet/ServletUtil.java | 73 +++++++++---------- .../template/PlainTextRendererImpl.java | 13 ++-- .../util/DefaultInternalConfiguration.java | 10 ++- .../wcomponents/util/HtmlSanitizerUtil.java | 14 ++-- .../wcomponents/util/SerializationUtil.java | 17 ++--- .../wcomponents/util/StreamUtil.java | 16 ++-- .../wcomponents/util/ThemeUtil.java | 6 +- .../util/thumbnail/ThumbnailUtil.java | 12 ++- .../SerializationPerformance_Test.java | 17 ++--- .../wcomponents/Serialization_Test.java | 32 ++++---- .../wcomponents/util/FileUtil_Test.java | 9 ++- 17 files changed, 124 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5f33022..08b2a42b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### API Changes ### Enhancements +* Consistent use of try-with-resources when handling streams + ### Bug Fixes ## 1.5.38 diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/ContentEscape.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/ContentEscape.java index 7706ef0b9..a64759a9e 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/ContentEscape.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/ContentEscape.java @@ -9,8 +9,7 @@ import org.apache.commons.logging.LogFactory; /** - * An Escape subclass that bypasses the usual request -> paint flow by directly producing the binary document - * content. + * An Escape subclass that bypasses the usual request -> paint flow by directly producing the binary document content. * * @author Martin Shevchenko * @since 1.0.0 @@ -81,28 +80,19 @@ public void escape() throws IOException { } if (contentAccess instanceof ContentStreamAccess) { - InputStream stream = null; - - try { - stream = ((ContentStreamAccess) contentAccess).getStream(); - + try (InputStream stream = ((ContentStreamAccess) contentAccess).getStream()) { if (stream == null) { throw new SystemException( "ContentAccess returned null stream, access=" + contentAccess); } - StreamUtil.copy(stream, response.getOutputStream()); - } finally { - StreamUtil.safeClose(stream); } } else { byte[] bytes = contentAccess.getBytes(); - if (bytes == null) { throw new SystemException( "ContentAccess returned null data, access=" + contentAccess); } - response.getOutputStream().write(bytes); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/InternalResource.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/InternalResource.java index 42bf2c7be..ef096226f 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/InternalResource.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/InternalResource.java @@ -56,17 +56,11 @@ public InternalResource(final String resourceName, final String description) { */ @Override public byte[] getBytes() { - InputStream stream = null; - - try { - stream = getClass().getResourceAsStream(resourceName); + try (InputStream stream = getClass().getResourceAsStream(resourceName)) { return StreamUtil.getBytes(stream); } catch (Exception e) { LOG.error("Failed to read resource: " + resourceName, e); - } finally { - StreamUtil.safeClose(stream); } - return EMPTY; } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java index be49eda44..af28ca968 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java @@ -752,8 +752,8 @@ public static String render(final Request request, final WComponent component) { component.preparePaint(request); try (PrintWriter writer = new PrintWriter(buffer)) { component.paint(new WebXmlRenderContext(writer)); + return buffer.toString(); } - return buffer.toString(); } finally { if (needsContext) { UIContextHolder.popContext(); @@ -807,8 +807,8 @@ public static String renderWithTransformToHTML(final Request request, final WCom chain.preparePaint(request); try (PrintWriter writer = new PrintWriter(buffer)) { chain.paint(new WebXmlRenderContext(writer)); + return buffer.toString(); } - return buffer.toString(); } finally { if (needsContext) { UIContextHolder.popContext(); diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/TransformXMLInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/TransformXMLInterceptor.java index a193895a2..ddb04c9bc 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/TransformXMLInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/TransformXMLInterceptor.java @@ -182,15 +182,14 @@ public void paint(final RenderContext renderContext) { private void transform(final String xml, final UIContext uic, final PrintWriter writer) { Transformer transformer = newTransformer(); - Source inputXml; - try { - inputXml = new StreamSource(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + try (InputStream inStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + Source inputXml = new StreamSource(inStream); StreamResult result = new StreamResult(writer); if (debugRequested) { transformer.setParameter("isDebug", 1); } transformer.transform(inputXml, result); - } catch (TransformerException ex) { + } catch (TransformerException | IOException ex) { throw new SystemException("Could not transform xml", ex); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/monitor/UicStats.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/monitor/UicStats.java index 675eb91ba..7d29e96f1 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/monitor/UicStats.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/monitor/UicStats.java @@ -86,8 +86,7 @@ public Iterator getWCsAnalysed() { } /** - * Retrieves the map wchich contains all the WComponent instances that make up the WComponent tree starting from the - * given root component. + * Retrieves the map wchich contains all the WComponent instances that make up the WComponent tree starting from the given root component. * * @param root the root component. * @return the map of Stats for the components under the given root component. @@ -205,12 +204,8 @@ private Stat createStat(final WComponent comp) { * @return the serialized size of the given object, or -1 on error. */ private int getSerializationSize(final Object obj) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(obj); - oos.close(); - byte[] bytes = bos.toByteArray(); return bytes.length; } catch (IOException ex) { @@ -225,17 +220,18 @@ private int getSerializationSize(final Object obj) { */ private void addSerializationStat(final ComponentModel model, final Stat stat) { try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); - oos.writeObject(model); - oos.close(); - - byte[] bytes = bos.toByteArray(); + // Calc size + byte[] bytes; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(model); + bytes = bos.toByteArray(); + } stat.setSerializedSize(bytes.length); - ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bis); - ois.readObject(); + // Check serializable + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis)) { + ois.readObject(); + } stat.setModelState(Stat.MDL_SERIALIZABLE); } catch (Exception ex) { diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java index ee206439e..679a0bbd8 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/servlet/ServletUtil.java @@ -229,8 +229,7 @@ public static void handleStaticResourceRequest(final HttpServletRequest request, return; } - InputStream resourceStream = staticResource.getStream(); - try { + try (InputStream resourceStream = staticResource.getStream()) { if (resourceStream == null) { LOG.warn( "Static resource [" + staticRequest + "] not found. Stream for content is null."); @@ -263,8 +262,6 @@ public static void handleStaticResourceRequest(final HttpServletRequest request, if (!headersOnly) { StreamUtil.copy(resourceStream, response.getOutputStream()); } - } finally { - StreamUtil.safeClose(resourceStream); } } catch (IOException e) { LOG.warn("Could not process static resource [" + staticRequest + "]. ", e); @@ -316,34 +313,33 @@ public static void handleThemeResourceRequest(final HttpServletRequest req, return; } - InputStream resourceStream = null; + URL url = null; - try { - URL url = null; + // Check for project translation file + if (fileName.startsWith(THEME_TRANSLATION_RESOURCE_PREFIX)) { + String resourceFileName = fileName.substring(THEME_TRANSLATION_RESOURCE_PREFIX.length()); + url = ServletUtil.class.getResource(THEME_PROJECT_TRANSLATION_RESOURCE_PATH + resourceFileName); + } - // Check for project translation file - if (fileName.startsWith(THEME_TRANSLATION_RESOURCE_PREFIX)) { - String resourceFileName = fileName.substring(THEME_TRANSLATION_RESOURCE_PREFIX.length()); - url = ServletUtil.class.getResource(THEME_PROJECT_TRANSLATION_RESOURCE_PATH + resourceFileName); - } + // Load from the theme path + if (url == null) { + String resourceName = ThemeUtil.getThemeBase() + fileName; + url = ServletUtil.class.getResource(resourceName); + } - // Load from the theme path - if (url == null) { - String resourceName = ThemeUtil.getThemeBase() + fileName; - url = ServletUtil.class.getResource(resourceName); - } + if (url == null) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } - if (url == null) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - } else { - URLConnection connection = url.openConnection(); - resourceStream = connection.getInputStream(); - int size = resourceStream.available(); - if (size > 0) { - resp.setContentLength(size); - } + URLConnection connection = url.openConnection(); + try (InputStream resourceStream = connection.getInputStream()) { + int size = resourceStream.available(); + if (size > 0) { + resp.setContentLength(size); + } - /* + /* I have commented out the setting of the Content-Disposition on static theme resources because, well why is it there? If this needs to be reinstated please provide a thorough justification comment here so the reasons are clear. @@ -353,19 +349,16 @@ public static void handleThemeResourceRequest(final HttpServletRequest req, substring(fileName .lastIndexOf('/') + 1)); resp.setHeader("Content-Disposition", "filename=" + encodedName); // "filename=" to comply with https://tools.ietf.org/html/rfc6266 - */ - resp.setContentType(WebUtilities.getContentType(fileName)); - resp.setHeader("Cache-Control", CacheType.THEME_CACHE.getSettings()); - - resp.setHeader("Expires", "31536000"); - resp.setHeader("ETag", "\"" + WebUtilities.getProjectVersion() + "\""); - // resp.setHeader("Last-Modified", "Mon, 02 Jan 2015 01:00:00 GMT"); - long modified = connection.getLastModified(); - resp.setDateHeader("Last-Modified", modified); - StreamUtil.copy(resourceStream, resp.getOutputStream()); - } - } finally { - StreamUtil.safeClose(resourceStream); + */ + resp.setContentType(WebUtilities.getContentType(fileName)); + resp.setHeader("Cache-Control", CacheType.THEME_CACHE.getSettings()); + + resp.setHeader("Expires", "31536000"); + resp.setHeader("ETag", "\"" + WebUtilities.getProjectVersion() + "\""); + // resp.setHeader("Last-Modified", "Mon, 02 Jan 2015 01:00:00 GMT"); + long modified = connection.getLastModified(); + resp.setDateHeader("Last-Modified", modified); + StreamUtil.copy(resourceStream, resp.getOutputStream()); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/template/PlainTextRendererImpl.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/template/PlainTextRendererImpl.java index ec7cd2e9e..4d3b0b360 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/template/PlainTextRendererImpl.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/template/PlainTextRendererImpl.java @@ -71,8 +71,6 @@ public void renderTemplate(final String templateName, final Map boolean xmlEncode = options.containsKey(XML_ENCODE); String cacheKey = templateName + "-" + xmlEncode; - InputStream stream = null; - // Caching Object value = options.get(USE_CACHE); boolean cache = (isCaching() && value == null) || (value != null && "true".equalsIgnoreCase(value.toString())); @@ -83,11 +81,12 @@ public void renderTemplate(final String templateName, final Map output = getCache().get(cacheKey); } if (output == null) { - stream = getClass().getResourceAsStream(name); - if (stream == null) { - throw new SystemException("Could not find plain text template [" + templateName + "]."); + try (InputStream stream = getClass().getResourceAsStream(name)) { + if (stream == null) { + throw new SystemException("Could not find plain text template [" + templateName + "]."); + } + output = new String(StreamUtil.getBytes(stream), StandardCharsets.UTF_8); } - output = new String(StreamUtil.getBytes(stream), StandardCharsets.UTF_8); if (xmlEncode) { output = WebUtilities.encode(output); } @@ -100,8 +99,6 @@ public void renderTemplate(final String templateName, final Map throw e; } catch (Exception e) { throw new SystemException("Problems with plain text template [" + templateName + "]. " + e.getMessage(), e); - } finally { - StreamUtil.safeClose(stream); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/DefaultInternalConfiguration.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/DefaultInternalConfiguration.java index e0f1ca9ae..571953393 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/DefaultInternalConfiguration.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/DefaultInternalConfiguration.java @@ -435,11 +435,11 @@ private void load(final String resourceName) { byte[] buff = contentsList.get(i); URL url = urlList.get(i); recordMessage("Loading from url " + url + "..."); - ByteArrayInputStream in = new ByteArrayInputStream(buff); - // Use the "IncludeProperties" to load properties into us one at a time.... IncludeProperties properties = new IncludeProperties(url.toString()); - properties.load(in); + try (ByteArrayInputStream in = new ByteArrayInputStream(buff)) { + properties.load(in); + } } File file = new File(resourceName); @@ -451,7 +451,9 @@ private void load(final String resourceName) { // Use the "IncludeProperties" to load properties into us, one at a time.... IncludeProperties properties = new IncludeProperties("file:" + filename(file)); - properties.load(new BufferedInputStream(new FileInputStream(file))); + try (InputStream fileStream = new FileInputStream(file); InputStream stream = new BufferedInputStream(fileStream)) { + properties.load(stream); + } } if (!found) { diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java index bf6e19639..9422607dc 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/HtmlSanitizerUtil.java @@ -1,6 +1,7 @@ package com.github.bordertech.wcomponents.util; import com.github.bordertech.wcomponents.WebUtilities; +import java.io.IOException; import java.io.InputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -151,16 +152,13 @@ public static Policy createPolicy(final String resourceName) { if (StringUtils.isBlank(resourceName)) { throw new SystemException("AntiSamy Policy resourceName cannot be null "); } - InputStream resource = HtmlSanitizerUtil.class.getClassLoader().getResourceAsStream(resourceName); - if (resource == null) { - throw new SystemException("Could not find AntiSamy Policy XML resource."); - } - try { + try (InputStream resource = HtmlSanitizerUtil.class.getClassLoader().getResourceAsStream(resourceName)) { + if (resource == null) { + throw new SystemException("Could not find AntiSamy Policy XML resource."); + } return Policy.getInstance(resource); - } catch (PolicyException ex) { + } catch (IOException | PolicyException ex) { throw new SystemException("Could not create AntiSamy Policy" + ex.getMessage(), ex); - } finally { - StreamUtil.safeClose(resource); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/SerializationUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/SerializationUtil.java index c6a8e42db..e04b9d1a8 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/SerializationUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/SerializationUtil.java @@ -26,16 +26,15 @@ private SerializationUtil() { */ public static Object pipe(final Object in) { try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream os = new ObjectOutputStream(bos); - os.writeObject(in); - os.close(); + byte[] bytes; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(bos)) { + os.writeObject(in); + bytes = bos.toByteArray(); + } - byte[] bytes = bos.toByteArray(); - ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInputStream is = new ObjectInputStream(bis); - Object out = is.readObject(); - return out; + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream is = new ObjectInputStream(bis)) { + return is.readObject(); + } } catch (Exception ex) { throw new SystemException("Failed to pipe " + in, ex); } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/StreamUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/StreamUtil.java index 5c6bffc43..7b9e2521f 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/StreamUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/StreamUtil.java @@ -72,10 +72,10 @@ public static void copy(final InputStream in, final OutputStream out, final int * @throws IOException if there is an error reading from the stream. */ public static byte[] getBytes(final InputStream in) throws IOException { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - copy(in, result); - result.close(); - return result.toByteArray(); + try (ByteArrayOutputStream result = new ByteArrayOutputStream()) { + copy(in, result); + return result.toByteArray(); + } } /** @@ -101,9 +101,9 @@ public static void safeClose(final Closeable stream) { * @throws IOException If there is an error reading from the is. */ public static byte[] streamToByteArray(final InputStream is) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - copy(is, baos); - byte[] bytes = baos.toByteArray(); - return bytes; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + copy(is, baos); + return baos.toByteArray(); + } } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/ThemeUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/ThemeUtil.java index 10b4f41d9..812c9120d 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/ThemeUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/ThemeUtil.java @@ -55,12 +55,10 @@ public final class ThemeUtil { String resourceName = THEME_BASE + THEME_VERSION_FILE_NAME; // Get theme version property file (if in classpath) - InputStream resourceStream = null; Properties prop = new Properties(); String themeBuild = null; String themeWcVersion = null; - try { - resourceStream = ThemeUtil.class.getResourceAsStream(resourceName); + try (InputStream resourceStream = ThemeUtil.class.getResourceAsStream(resourceName)) { prop.load(resourceStream); // Theme build property themeBuild = prop.getProperty(THEME_BUILD_NUMBER_PARAM); @@ -68,8 +66,6 @@ public final class ThemeUtil { themeWcVersion = prop.getProperty(THEME_WC_BUILD_NUMBER_PARAM); } catch (Exception e) { LOG.warn("Could not load theme version properties file \"" + resourceName + "\""); - } finally { - StreamUtil.safeClose(resourceStream); } // If theme build not available, then use the wcomponents project version diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java index d8c4feab8..455c0aab7 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/thumbnail/ThumbnailUtil.java @@ -204,9 +204,13 @@ private static com.github.bordertech.wcomponents.Image handleDefaultImage(final boolean sameWidth = scale.width == -1 || scale.width == THUMBNAIL_DEFAULT_SIZE.width; if (!sameHeight || !sameWidth) { // Scale to correct size - ByteArrayInputStream byteIs = new ByteArrayInputStream(image.getBytes()); - byte[] bytes = createImageThumbnail(byteIs, scale); - image = new BytesImage(bytes, MIMETYPE_JPEG, "Thumbnail of " + name, null); + try (ByteArrayInputStream byteIs = new ByteArrayInputStream(image.getBytes())) { + byte[] bytes = createImageThumbnail(byteIs, scale); + image = new BytesImage(bytes, MIMETYPE_JPEG, "Thumbnail of " + name, null); + } catch (IOException ex) { + image = null; + LOG.error("Error creating scaled thumbnail from image", ex); + } } return image; } @@ -223,7 +227,7 @@ private static byte[] createImageThumbnail(final InputStream is, final Dimension // Create buffered image from input stream BufferedImage image; try { - // ImageIO closes stream + // ImageIO closes cache stream image = ImageIO.read(new MemoryCacheImageInputStream(is)); if (image == null) { return null; diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java index edf1a790f..1cd79b01b 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java @@ -23,8 +23,8 @@ import org.junit.experimental.categories.Category; /** - * Tests to check the performance of WComponent graph serialization. This test does not check for correct serialization - * - see {@link Serialization_Test} instead. + * Tests to check the performance of WComponent graph serialization. This test does not check for correct serialization - see + * {@link Serialization_Test} instead. * * @author Yiannis Paschalidis * @since 1.0.0 @@ -180,12 +180,10 @@ private int getUISize(final UIContext uic, final WComponent comp) throws IOExcep * @throws IOException an IO exception */ private static byte[] serialize(final Serializable obj) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); - oos.writeObject(obj); - oos.close(); - - return bos.toByteArray(); + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(obj); + return bos.toByteArray(); + } } /** @@ -288,8 +286,7 @@ private void findWComponents(final WComponent comp, final List resul } /** - * AllComponents instantiated with 10 repetitions. This needs to be created as a subclass as the UIRegistry uses the - * class name. + * AllComponents instantiated with 10 repetitions. This needs to be created as a subclass as the UIRegistry uses the class name. */ public static final class AllComponents10 extends AllComponents { diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/Serialization_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/Serialization_Test.java index 9645d5add..d7e2fe1c9 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/Serialization_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/Serialization_Test.java @@ -7,8 +7,8 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import org.junit.Assert; import org.apache.commons.logging.LogFactory; +import org.junit.Assert; import org.junit.Test; /** @@ -129,22 +129,20 @@ private void assertSerializable(final Object obj) { */ private static Object pipe(final Object obj) { try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); - oos.writeObject(obj); - oos.close(); - - byte[] bytes = bos.toByteArray(); - - FileOutputStream fos = new FileOutputStream("SerializeText.txt"); - fos.write(bytes); - fos.flush(); - fos.close(); - - ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bis); - Object out = ois.readObject(); - return out; + byte[] bytes; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(obj); + bytes = bos.toByteArray(); + } + + try (FileOutputStream fos = new FileOutputStream("SerializeText.txt")) { + fos.write(bytes); + fos.flush(); + } + + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis)) { + return ois.readObject(); + } } catch (Exception ex) { throw new SystemException("Failed to pipe " + obj, ex); } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java index a490fcc8f..bf3e88c3f 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/util/FileUtil_Test.java @@ -150,11 +150,12 @@ private FileItem createFileItem(String fileResource) throws IOException { testFileContent[i] = (byte) (i & 0xff); } } else { - InputStream stream = getClass().getResourceAsStream(fileResource); - if (stream == null) { - throw new IOException("File resource not found: " + fileResource); + try (InputStream stream = getClass().getResourceAsStream(fileResource)) { + if (stream == null) { + throw new IOException("File resource not found: " + fileResource); + } + testFileContent = StreamUtil.getBytes(stream); } - testFileContent = StreamUtil.getBytes(stream); } MockFileItem fileItem = new MockFileItem(); fileItem.set(testFileContent); From c772d9c2ed22bf034a50decb6f8fb171d3199cf1 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 12 Jan 2026 17:05:06 +1100 Subject: [PATCH 17/32] Update examples to have consistent use of try-with-resources when handling streams --- .../wcomponents/examples/DynamicImage.java | 14 ++++++------- .../wcomponents/examples/TextImage.java | 18 +++++++--------- .../examples/WComponentRenderPerfTest.java | 19 ++++++++--------- .../wcomponents/examples/WImageExample.java | 12 ++++------- .../examples/picker/ExampleSection.java | 15 +------------ .../examples/theme/XsltTestComponent.java | 21 +------------------ .../examples/SimpleFileUpload_Test.java | 7 +++---- 7 files changed, 32 insertions(+), 74 deletions(-) diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/DynamicImage.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/DynamicImage.java index ecfa4f580..01edd5b5f 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/DynamicImage.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/DynamicImage.java @@ -55,8 +55,7 @@ public String getMimeType() { } /** - * Retrieves the natural size of the image. If only one dimension is known, a negative value will be returned for - * the other dimension. + * Retrieves the natural size of the image. If only one dimension is known, a negative value will be returned for the other dimension. * * @return the image size, or null if unknown. */ @@ -98,12 +97,11 @@ public byte[] getBytes() { // Write the image to a byte array. Iterator writers = ImageIO.getImageWritersByMIMEType(getMimeType()); ImageWriter writer = writers.next(); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ImageOutputStream ios = ImageIO.createImageOutputStream(os); - writer.setOutput(ios); - writer.write(image); - - return os.toByteArray(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageOutputStream ios = ImageIO.createImageOutputStream(os)) { + writer.setOutput(ios); + writer.write(image); + return os.toByteArray(); + } } catch (IOException ex) { LOG.error("Unable to generate client image.", ex); } diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/TextImage.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/TextImage.java index 5ca169898..934c51502 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/TextImage.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/TextImage.java @@ -112,12 +112,11 @@ public TextImage(final String text, final Dimension size) { // Write the image to a byte array. Iterator writers = ImageIO.getImageWritersByMIMEType(MIME_TYPE); ImageWriter writer = writers.next(); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ImageOutputStream ios = ImageIO.createImageOutputStream(os); - writer.setOutput(ios); - writer.write(image); - - imageBytes = os.toByteArray(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageOutputStream ios = ImageIO.createImageOutputStream(os)) { + writer.setOutput(ios); + writer.write(image); + imageBytes = os.toByteArray(); + } } catch (IOException ex) { LOG.error("Unable to generate client image.", ex); } @@ -135,8 +134,7 @@ public String getMimeType() { } /** - * Retrieves the natural size of the image. If only one dimension is known, a negative value will be returned for - * the other dimension. + * Retrieves the natural size of the image. If only one dimension is known, a negative value will be returned for the other dimension. * * @return the image size, or null if unknown. */ @@ -146,8 +144,8 @@ public Dimension getSize() { } /** - * Sets the natural size of the image. If only one dimension is known, use a negative value for the other dimension. - * If the image size is unknown, set the size to null. + * Sets the natural size of the image. If only one dimension is known, use a negative value for the other dimension. If the image size is unknown, + * set the size to null. * * @param size the image size. */ diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WComponentRenderPerfTest.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WComponentRenderPerfTest.java index 77fa85b74..315bd3a2e 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WComponentRenderPerfTest.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WComponentRenderPerfTest.java @@ -109,16 +109,15 @@ private static void launchTest(final String testName) throws IOException { testName }); - InputStream stdout = process.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)); - - // Pipe the input from the process to the logger - for (String line = reader.readLine(); line != null; line = reader.readLine()) { - int index = line.indexOf(LINE_PREFIX); - - if (index != -1) { - line = line.substring(index + LINE_PREFIX.length()); - LOG.info(line); + try (InputStream stdout = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(stdout))) { + // Pipe the input from the process to the logger + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + int index = line.indexOf(LINE_PREFIX); + + if (index != -1) { + line = line.substring(index + LINE_PREFIX.length()); + LOG.info(line); + } } } } diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WImageExample.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WImageExample.java index 7585e1f3b..bbc96d5f2 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WImageExample.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/WImageExample.java @@ -126,16 +126,12 @@ public static class ExampleImage implements Image { * @param resource the path to the image file. */ public ExampleImage(final String resource) { - InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream( - resource); - - if (in != null) { - try { + try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) { + if (in != null) { imageBytes = StreamUtil.getBytes(in); - in.close(); - } catch (IOException ex) { - LOG.error("Cannot load example image.", ex); } + } catch (IOException ex) { + LOG.error("Cannot load example image.", ex); } } diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/picker/ExampleSection.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/picker/ExampleSection.java index 4370dc87a..62dde9707 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/picker/ExampleSection.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/picker/ExampleSection.java @@ -215,27 +215,14 @@ public void resetExample() { private static String getSource(final String className) { String sourceName = '/' + className.replace('.', '/') + ".java"; - InputStream stream = null; - - try { - stream = ExampleSection.class.getResourceAsStream(sourceName); - + try (InputStream stream = ExampleSection.class.getResourceAsStream(sourceName)) { if (stream != null) { byte[] sourceBytes = StreamUtil.getBytes(stream); - // we need to do some basic formatting of the source now. return new String(sourceBytes, "UTF-8"); } } catch (IOException e) { LOG.warn("Unable to read source code for class " + className, e); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - LOG.error("Error closing stream", e); - } - } } return null; diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/XsltTestComponent.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/XsltTestComponent.java index bd7f75ccb..aa7cbcb9f 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/XsltTestComponent.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/XsltTestComponent.java @@ -95,33 +95,14 @@ private void runTransform() { */ private void transform(final File xsltFile, final File inputFile, final File outputFile) throws Exception { - FileReader xsltIn = null; - FileReader in = null; - FileWriter out = null; - - try { - xsltIn = new FileReader(xsltFile); + try (FileReader xsltIn = new FileReader(xsltFile); FileReader in = new FileReader(inputFile); FileWriter out = new FileWriter(outputFile)) { Source xsltSource = new StreamSource(xsltIn); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(xsltSource); // transformer.setOutputProperty("disable-empty-element-collapsing", "true"); - - in = new FileReader(inputFile); Source source = new StreamSource(in); - out = new FileWriter(outputFile); StreamResult result = new StreamResult(out); transformer.transform(source, result); - } finally { - if (xsltIn != null) { - xsltIn.close(); - } - if (in != null) { - in.close(); - } - if (out != null) { - out.flush(); - out.close(); - } } } } diff --git a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/SimpleFileUpload_Test.java b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/SimpleFileUpload_Test.java index 18d1ec05b..592b7b16f 100755 --- a/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/SimpleFileUpload_Test.java +++ b/wcomponents-examples/src/test/java/com/github/bordertech/wcomponents/examples/SimpleFileUpload_Test.java @@ -65,10 +65,9 @@ private static File createTempFile(final String content) throws IOException { File tempFile = File.createTempFile("SimpleFileUpload_Test", "tmp"); tempFile.deleteOnExit(); - OutputStream out = new FileOutputStream(tempFile); - out.write(content.getBytes()); - out.close(); - + try (OutputStream out = new FileOutputStream(tempFile)) { + out.write(content.getBytes()); + } return tempFile; } } From e176826d89849ec142c03194f1874ae3e0cc1e15 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 12 Jan 2026 17:06:25 +1100 Subject: [PATCH 18/32] Update LDE to have consistent use of try-with-resources when handling streams --- .../wcomponents/lde/LdeSessionUtil.java | 30 ++----------------- .../wcomponents/lde/PlainLauncher_Test.java | 10 +++++-- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/wcomponents-lde/src/main/java/com/github/bordertech/wcomponents/lde/LdeSessionUtil.java b/wcomponents-lde/src/main/java/com/github/bordertech/wcomponents/lde/LdeSessionUtil.java index f4106d6f9..fad7ccb3d 100755 --- a/wcomponents-lde/src/main/java/com/github/bordertech/wcomponents/lde/LdeSessionUtil.java +++ b/wcomponents-lde/src/main/java/com/github/bordertech/wcomponents/lde/LdeSessionUtil.java @@ -1,7 +1,6 @@ package com.github.bordertech.wcomponents.lde; import com.github.bordertech.wcomponents.util.ConfigurationProperties; -import com.github.bordertech.wcomponents.util.StreamUtil; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -47,16 +46,9 @@ private LdeSessionUtil() { */ public static void deserializeSessionAttributes(final HttpSession session) { File file = new File(SERIALIZE_SESSION_NAME); - FileInputStream fis = null; - ObjectInputStream ois = null; - if (file.canRead()) { - try { - fis = new FileInputStream(file); - ois = new ObjectInputStream(fis); - + try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { List data = (List) ois.readObject(); - for (Iterator i = data.iterator(); i.hasNext();) { String key = (String) i.next(); Object value = i.next(); @@ -64,12 +56,6 @@ public static void deserializeSessionAttributes(final HttpSession session) { } } catch (Exception e) { LOG.error("Failed to read serialized session from " + file, e); - } finally { - if (ois != null) { - StreamUtil.safeClose(ois); - } else { - StreamUtil.safeClose(fis); - } } } else { LOG.warn("Unable to read serialized session from " + file); @@ -105,22 +91,10 @@ public static synchronized void serializeSessionAttributes(final HttpSession ses } // Write them to the file - FileOutputStream fos = null; - ObjectOutputStream oos = null; - - try { - fos = new FileOutputStream(file); - oos = new ObjectOutputStream(fos); - + try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(data); } catch (Exception e) { LOG.error("Failed to write serialized session to " + file, e); - } finally { - if (oos != null) { - StreamUtil.safeClose(oos); - } else { - StreamUtil.safeClose(fos); - } } } else { LOG.warn("Unable to write serialized session to " + file); diff --git a/wcomponents-lde/src/test/java/com/github/bordertech/wcomponents/lde/PlainLauncher_Test.java b/wcomponents-lde/src/test/java/com/github/bordertech/wcomponents/lde/PlainLauncher_Test.java index e0eda7c14..69370c3da 100755 --- a/wcomponents-lde/src/test/java/com/github/bordertech/wcomponents/lde/PlainLauncher_Test.java +++ b/wcomponents-lde/src/test/java/com/github/bordertech/wcomponents/lde/PlainLauncher_Test.java @@ -11,10 +11,11 @@ import com.github.bordertech.wcomponents.util.ConfigurationProperties; import com.github.bordertech.wcomponents.util.StreamUtil; import com.github.bordertech.wcomponents.util.mock.servlet.MockHttpServletRequest; +import java.io.InputStream; import java.net.URL; import java.net.URLConnection; -import org.junit.Assert; import org.junit.After; +import org.junit.Assert; import org.junit.Test; /** @@ -95,8 +96,11 @@ public void testServer() throws Exception { // Access the server and record the output URL url = new URL(launcher.getUrl()); URLConnection conn = url.openConnection(); - byte[] result = StreamUtil.getBytes(conn.getInputStream()); - String content = new String(result, "UTF-8"); + String content; + try (InputStream stream = conn.getInputStream()) { + byte[] result = StreamUtil.getBytes(stream); + content = new String(result, "UTF-8"); + } Assert.assertEquals("HandleRequest should have been called once", 1, MyTestApp.handleRequestCount); From 5b4a327495fc8191e7ce88ca0b55283f2b786721 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 12 Jan 2026 17:25:42 +1100 Subject: [PATCH 19/32] Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static) --- CHANGELOG.md | 1 + .../wcomponents/AbstractRequest.java | 42 +------------------ 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b2a42b9..13c00d7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### API Changes ### Enhancements * Consistent use of try-with-resources when handling streams +* Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static). Use StreamUtils instead. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractRequest.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractRequest.java index 3511feb72..5ed0ab8b6 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractRequest.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/AbstractRequest.java @@ -1,16 +1,11 @@ package com.github.bordertech.wcomponents; -import com.github.bordertech.wcomponents.servlet.ServletUtil; import com.github.bordertech.wcomponents.util.Config; import com.github.bordertech.wcomponents.util.Enumerator; -import com.github.bordertech.wcomponents.util.StreamUtil; import com.github.bordertech.wcomponents.util.Util; -import java.io.IOException; -import java.io.InputStream; import java.io.Serializable; import java.util.Enumeration; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.apache.commons.fileupload.FileItem; @@ -69,7 +64,7 @@ public FileItem[] getFileItems(final String key) { result = deserialized.toArray(new FileItem[]{}); } } - */ + */ return result; } @@ -128,41 +123,6 @@ public boolean isLogout() { return logout; } - /** - *

- * {@link FileItem} classes (if attachements) will be kept as part of the request. The default behaviour of the file - * item is to store the upload in memory until it reaches a certain size, after which the content is streamed to a - * temp file.

- * - *

- * If, in the future, performance of uploads becomes a focus we can instead look into using the Jakarta Commons - * Streaming API. In this case, the content of the upload isn't stored anywhere. It will be up to the user to - * read/store the content of the stream.

- * - * @param fileItems a list of {@link FileItem}s corresponding to POSTed form data. - * @param parameters the map to store non-file request parameters in. - * @param files the map to store the uploaded file parameters in. - * @deprecated Use {@link ServletUtil#uploadFileItems(java.util.List, java.util.Map, java.util.Map)} instead. - */ - @Deprecated - protected static void uploadFileItems(final List fileItems, final Map parameters, - final Map files) { - ServletUtil.uploadFileItems(fileItems, parameters, files); - } - - /** - * Returns a byte array containing all the information contained in the given input stream. - * - * @param stream the input stream to read from. - * @return the stream contents as a byte array. - * @throws IOException if there is an error reading from the stream. - * @deprecated Use {@link StreamUtil#getBytes(java.io.InputStream)} instead. - */ - @Deprecated - protected static byte[] readBytes(final InputStream stream) throws IOException { - return StreamUtil.getBytes(stream); - } - /** * This method contains no logic. Subclasses which need to perform event handling logic (eg. * WPortletRequest) should override this method. From 40ec02fde48018848d8e1ab51ea68dca448a7a99 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Wed, 14 Jan 2026 14:06:20 +1000 Subject: [PATCH 20/32] Replaced org.apache.tika:tika library with org.overviewproject:mime-types in FileUtil to validate uploaded file mime types --- CHANGELOG.md | 1 + wcomponents-core/pom.xml | 17 ++------ .../wcomponents/WMultiFileWidget.java | 39 ++++++++++++++----- .../bordertech/wcomponents/util/FileUtil.java | 23 +++-------- .../wcomponents/util/FileUtil_Test.java | 11 ++---- 5 files changed, 44 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c00d7c2..0e1e438b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Enhancements * Consistent use of try-with-resources when handling streams * Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static). Use StreamUtils instead. +* Replaced org.apache.tika:tika library with org.overviewproject:mime-types in FileUtil to validate uploaded file mime types. ### Bug Fixes diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 6bac339ba..19dd93a28 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -289,20 +289,9 @@
- org.apache.tika - tika-core - 2.9.4 - - - - org.slf4j - slf4j-api - - - commons-io - commons-io - - + org.overviewproject + mime-types + 2.0.0 diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java index fcef054c3..c8d49514f 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java @@ -739,16 +739,13 @@ protected void doHandleUploadRequest(final Request request) { // Wrap the file item FileItemWrap wrap = new FileItemWrap(items[0]); - // if fileType is supplied then validate it - if (hasFileTypes() && !FileUtil.validateFileType(wrap, getFileTypes())) { - String invalidMessage = FileUtil.getInvalidFileTypeMessage(getFileTypes()); - throw new SystemException(invalidMessage); + // Validate the file type + if (hasFileTypes()) { + doHandleUploadedFileTypeValidation(wrap); } - - // if fileSize is supplied then validate it - if (hasMaxFileSize() && !FileUtil.validateFileSize(wrap, getMaxFileSize())) { - String invalidMessage = FileUtil.getInvalidFileSizeMessage(getMaxFileSize()); - throw new SystemException(invalidMessage); + // Validate the file size + if (hasMaxFileSize()) { + doHandleUploadedFileSizeValidation(wrap); } FileWidgetUpload file = new FileWidgetUpload(fileId, wrap); @@ -759,6 +756,30 @@ protected void doHandleUploadRequest(final Request request) { setNewUpload(true); } + /** + * Perform file type validation on the uploaded file. + * + * @param wrap the file item to validate + */ + protected void doHandleUploadedFileTypeValidation(final FileItemWrap wrap) { + if (!FileUtil.validateFileType(wrap, getFileTypes())) { + String invalidMessage = FileUtil.getInvalidFileTypeMessage(getFileTypes()); + throw new SystemException(invalidMessage); + } + } + + /** + * Perform file size validation on the uploaded file. + * + * @param wrap the file item to validate + */ + protected void doHandleUploadedFileSizeValidation(final FileItemWrap wrap) { + if (!FileUtil.validateFileSize(wrap, getMaxFileSize())) { + String invalidMessage = FileUtil.getInvalidFileSizeMessage(getMaxFileSize()); + throw new SystemException(invalidMessage); + } + } + /** * Handle the thumb nail request. * diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java index 5db00b0e7..fe9a74435 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java @@ -2,6 +2,7 @@ import com.github.bordertech.wcomponents.file.File; import com.github.bordertech.wcomponents.file.FileItemWrap; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.text.DecimalFormat; @@ -10,9 +11,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.tika.Tika; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.metadata.TikaCoreProperties; +import org.overviewproject.mime_types.GetBytesException; +import org.overviewproject.mime_types.MimeTypeDetector; /** * Utility methods for {@link File}. @@ -97,20 +97,9 @@ public static boolean validateFileType(final FileItemWrap newFile, final List Date: Wed, 14 Jan 2026 15:38:45 +1000 Subject: [PATCH 21/32] Updated FileUtil to make file extension and mime type validation case insensitive --- CHANGELOG.md | 1 + .../bordertech/wcomponents/util/FileUtil.java | 21 +++++++++++------- .../wcomponents/util/FileUtil_Test.java | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1e438b5..2a2997cc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Consistent use of try-with-resources when handling streams * Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static). Use StreamUtils instead. * Replaced org.apache.tika:tika library with org.overviewproject:mime-types in FileUtil to validate uploaded file mime types. +* Updated FileUtil to make file extension and mime type validation case insensitive. ### Bug Fixes diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java index fe9a74435..0afc420a8 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/util/FileUtil.java @@ -51,36 +51,41 @@ public static boolean validateFileType(final FileItemWrap newFile, final List fileExts = fileTypes.stream() .filter(fileType -> fileType.startsWith(".")) .collect(Collectors.toList()); - // filter mime types from fileTypes. + // Filter mime types final List fileMimes = fileTypes.stream() .filter(fileType -> !fileExts.contains(fileType)) .collect(Collectors.toList()); // First validate newFile against fileExts list // If extensions are supplied, then check if newFile has a name - if (fileExts.size() > 0 && newFile.getName() != null) { + if (!fileExts.isEmpty() && newFile.getName() != null) { // Then see if newFile has an extension String[] split = newFile.getName().split(("\\.(?=[^\\.]+$)")); // If it exists, then check if it matches supplied extension(s) if (split.length == 2 - && fileExts.stream().anyMatch(fileExt -> fileExt.equals("." + split[1]))) { + && fileExts.stream().anyMatch(fileExt -> fileExt.equalsIgnoreCase("." + split[1]))) { return true; } } // If extension match is unsucessful, then move to fileMimes list - if (fileMimes.size() > 0) { + if (!fileMimes.isEmpty()) { final String mimeType = getFileMimeType(newFile); + if (mimeType == null) { + return false; + } LOG.debug("File mime-type is: " + mimeType); for (String fileMime : fileMimes) { - if (StringUtils.equals(mimeType, fileMime)) { + if (mimeType.equalsIgnoreCase(fileMime)) { return true; } if (fileMime.indexOf("*") == fileMime.length() - 1) { - fileMime = fileMime.substring(0, fileMime.length() - 1); - if (mimeType.indexOf(fileMime) == 0) { + String fileMimePrefix = fileMime.substring(0, fileMime.length() - 1).toLowerCase(); + String lcMimeType = mimeType.toLowerCase(); + if (lcMimeType.startsWith(fileMimePrefix)) { return true; } } @@ -93,7 +98,7 @@ public static boolean validateFileType(final FileItemWrap newFile, final List Date: Fri, 16 Jan 2026 13:33:21 +1000 Subject: [PATCH 22/32] Update project dependencies to latest versions --- CHANGELOG.md | 17 +++++++++++++++++ pom.xml | 4 ++-- wcomponents-core/pom.xml | 20 ++++++++++++++------ wcomponents-test-lib/pom.xml | 10 +++++----- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2997cc1..6a961f4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ * Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static). Use StreamUtils instead. * Replaced org.apache.tika:tika library with org.overviewproject:mime-types in FileUtil to validate uploaded file mime types. * Updated FileUtil to make file extension and mime type validation case insensitive. +* Updated the following dependencies: + * wcomponents-core: + * com.google.code.gson:gson from 2.13.1 to 2.13.2 + * org.apache.commons:commons-lang3 from 3.18.0 to 3.20.0 + * commons-io:commons-io from 2.19.0 to 2.21.0 + * com.google.errorprone:error_prone_annotations from 2.39.0 to 2.46.0 + * org.apache.httpcomponents.client5:httpclient5 from 5.5 to 5.6 + * org.apache.httpcomponents.core5:httpcore5 from 5.3.4 to 5.4 + * wcomponents-test-lib: + * io.github.bonigarcia:webdrivermanager from 6.1.0 to 6.3.3 + * org.apache.commons:commons-compress from 1.27.1 to 1.28.0 + * commons-codec:commons-codec from 1.18.0 to 1.20.0 + * com.google.guava:guava from 33.4.8-jre to 33.5.0-jre + * net.java.dev.jna:jna from 5.17.0 to 5.18.1 + * wcomponents-bundle: + * org.ehcache:ehcahce from 3.10.8 to 3.11.1 + * org.glassfish.jaxb:jaxb-runtime from 4.0.5 to 4.0.6 ### Bug Fixes diff --git a/pom.xml b/pom.xml index 14dd0afc3..a8313fcac 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ org.ehcache ehcache - 3.10.8 + 3.11.1 @@ -107,7 +107,7 @@ org.glassfish.jaxb jaxb-runtime - 4.0.5 + 4.0.6 diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 19dd93a28..d5043b201 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -218,7 +218,7 @@ com.google.code.gson gson - 2.13.1 + 2.13.2 com.google.errorprone @@ -248,6 +248,14 @@ xerces xercesImpl + + commons-io + commons-io + + + org.apache.httpcomponents.core5 + httpcore5 + @@ -308,22 +316,22 @@ org.apache.commons commons-lang3 - 3.18.0 + 3.20.0 commons-io commons-io - 2.19.0 + 2.21.0 com.google.errorprone error_prone_annotations - 2.39.0 + 2.46.0 org.apache.httpcomponents.client5 httpclient5 - 5.5 + 5.6 org.slf4j @@ -334,7 +342,7 @@ org.apache.httpcomponents.core5 httpcore5 - 5.3.4 + 5.4 diff --git a/wcomponents-test-lib/pom.xml b/wcomponents-test-lib/pom.xml index 4bb9a2097..afab26971 100755 --- a/wcomponents-test-lib/pom.xml +++ b/wcomponents-test-lib/pom.xml @@ -79,7 +79,7 @@ io.github.bonigarcia webdrivermanager - 6.1.0 + 6.3.3 @@ -131,7 +131,7 @@ org.apache.commons commons-compress - 1.27.1 + 1.28.0 @@ -151,12 +151,12 @@ commons-codec commons-codec - 1.18.0 + 1.20.0 com.google.guava guava - 33.4.8-jre + 33.5.0-jre com.google.errorprone @@ -167,7 +167,7 @@ net.java.dev.jna jna - 5.17.0 + 5.18.1 From 930648d5594484b05b9f1ed3e124d9b59b2c56e3 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 16 Jan 2026 16:11:48 +1100 Subject: [PATCH 23/32] Update missed example with try-with-resources --- .../wcomponents/examples/theme/WMultiFileWidgetExample.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/WMultiFileWidgetExample.java b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/WMultiFileWidgetExample.java index f2d4df488..b8a9f5119 100755 --- a/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/WMultiFileWidgetExample.java +++ b/wcomponents-examples/src/main/java/com/github/bordertech/wcomponents/examples/theme/WMultiFileWidgetExample.java @@ -204,9 +204,7 @@ private void appendFileDetails(final StringBuffer buf, final WMultiFileWidget fi for (FileWidgetUpload file : files) { String streamedSize; - try { - InputStream in = file.getFile().getInputStream(); - + try (InputStream in = file.getFile().getInputStream()) { int size = 0; while (in.read() >= 0) { size++; From deb1ed47fa861633756192c388e97da5131aefa6 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 16 Jan 2026 16:49:49 +1000 Subject: [PATCH 24/32] Add code-coverage module --- code-coverage/pom.xml | 69 +++++++++++++++++++++++++++++++++++++++++++ pom.xml | 1 + 2 files changed, 70 insertions(+) create mode 100644 code-coverage/pom.xml diff --git a/code-coverage/pom.xml b/code-coverage/pom.xml new file mode 100644 index 000000000..fa9f960c8 --- /dev/null +++ b/code-coverage/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + com.github.bordertech.wcomponents + wcomponents-parent + 1.5.39-SNAPSHOT + ../pom.xml + + + code-coverage + code-coverage + + jar + + + + com.github.bordertech.wcomponents + wcomponents-core + ${project.version} + + + com.github.bordertech.wcomponents + wcomponents-examples + ${project.version} + + + com.github.bordertech.wcomponents + wcomponents-test-lib + ${project.version} + + + com.github.bordertech.wcomponents + wcomponents-lde + ${project.version} + + + + + + + + + org.jacoco + jacoco-maven-plugin + + + report-aggregate + test + + report-aggregate + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index a8313fcac..986b82860 100644 --- a/pom.xml +++ b/pom.xml @@ -217,6 +217,7 @@ wcomponents-theme wcomponents-xslt wcomponents-bundle + code-coverage From 07e726251c0fd016b23011ca720f0608f36688e8 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 16 Jan 2026 17:03:19 +1000 Subject: [PATCH 25/32] Add version to deploy plugin in code-coverage module --- code-coverage/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/code-coverage/pom.xml b/code-coverage/pom.xml index fa9f960c8..0cae18b87 100644 --- a/code-coverage/pom.xml +++ b/code-coverage/pom.xml @@ -58,6 +58,7 @@ org.apache.maven.plugins maven-deploy-plugin + 3.1.4 true From 71b2464decf35d043b57e0630ef957734a578aa9 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Fri, 16 Jan 2026 17:28:21 +1000 Subject: [PATCH 26/32] Add wait for quality gate on sonar scan to fail build --- .github/workflows/github-actions-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-build.yml b/.github/workflows/github-actions-build.yml index 05d03519e..062f1ea42 100644 --- a/.github/workflows/github-actions-build.yml +++ b/.github/workflows/github-actions-build.yml @@ -48,7 +48,7 @@ jobs: echo "Sonar secure variables NOT available" else echo "Sonar secure variables ARE available" - mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" + mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" -Dsonar.qualitygate.wait=true fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d6b53cad62557ee9fc6654c5fda08773dd3cde7d Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Mon, 19 Jan 2026 15:29:54 +1000 Subject: [PATCH 27/32] EOL for new code-coverage pom.xml --- code-coverage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code-coverage/pom.xml b/code-coverage/pom.xml index 0cae18b87..1e2fcf881 100644 --- a/code-coverage/pom.xml +++ b/code-coverage/pom.xml @@ -67,4 +67,4 @@ - \ No newline at end of file + From 294d21d75c0f162be836e5744ab368ee1cde0b0b Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Tue, 20 Jan 2026 13:24:15 +1000 Subject: [PATCH 28/32] [maven-release-plugin] prepare release wcomponents-parent-1.5.39 --- code-coverage/pom.xml | 2 +- pom.xml | 4 ++-- wcomponents-bundle/pom.xml | 2 +- wcomponents-core/pom.xml | 2 +- wcomponents-examples-lde/pom.xml | 2 +- wcomponents-examples/pom.xml | 2 +- wcomponents-i18n/pom.xml | 2 +- wcomponents-lde/pom.xml | 2 +- wcomponents-test-lib/pom.xml | 2 +- wcomponents-theme/pom.xml | 2 +- wcomponents-xslt/pom.xml | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/code-coverage/pom.xml b/code-coverage/pom.xml index 1e2fcf881..ccbbc9462 100644 --- a/code-coverage/pom.xml +++ b/code-coverage/pom.xml @@ -5,7 +5,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/pom.xml b/pom.xml index 986b82860..07a541802 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 pom @@ -57,7 +57,7 @@ https://github.com/bordertech/wcomponents scm:git:https://github.com/bordertech/wcomponents.git scm:git:https://github.com/bordertech/wcomponents.git - wcomponents-1.0.0 + wcomponents-parent-1.5.39 diff --git a/wcomponents-bundle/pom.xml b/wcomponents-bundle/pom.xml index 6bac8948b..054de880f 100755 --- a/wcomponents-bundle/pom.xml +++ b/wcomponents-bundle/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index d5043b201..03dad1d8e 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-examples-lde/pom.xml b/wcomponents-examples-lde/pom.xml index 1a121cecd..fda606e18 100755 --- a/wcomponents-examples-lde/pom.xml +++ b/wcomponents-examples-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-examples/pom.xml b/wcomponents-examples/pom.xml index e69198c84..fa76b5c34 100755 --- a/wcomponents-examples/pom.xml +++ b/wcomponents-examples/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-i18n/pom.xml b/wcomponents-i18n/pom.xml index 3f75a018d..61d186680 100755 --- a/wcomponents-i18n/pom.xml +++ b/wcomponents-i18n/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-lde/pom.xml b/wcomponents-lde/pom.xml index 2585a3342..ea5008304 100755 --- a/wcomponents-lde/pom.xml +++ b/wcomponents-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-test-lib/pom.xml b/wcomponents-test-lib/pom.xml index afab26971..633689355 100755 --- a/wcomponents-test-lib/pom.xml +++ b/wcomponents-test-lib/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 ../pom.xml diff --git a/wcomponents-theme/pom.xml b/wcomponents-theme/pom.xml index d5337bcc9..d3834ab75 100755 --- a/wcomponents-theme/pom.xml +++ b/wcomponents-theme/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 jar diff --git a/wcomponents-xslt/pom.xml b/wcomponents-xslt/pom.xml index 74e19c62b..b97344640 100644 --- a/wcomponents-xslt/pom.xml +++ b/wcomponents-xslt/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39-SNAPSHOT + 1.5.39 jar From 4b4aee4a89ad99d924b8feb82c6509a0d6b0d064 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Tue, 20 Jan 2026 13:24:19 +1000 Subject: [PATCH 29/32] [maven-release-plugin] prepare for next development iteration --- code-coverage/pom.xml | 2 +- pom.xml | 4 ++-- wcomponents-bundle/pom.xml | 2 +- wcomponents-core/pom.xml | 2 +- wcomponents-examples-lde/pom.xml | 2 +- wcomponents-examples/pom.xml | 2 +- wcomponents-i18n/pom.xml | 2 +- wcomponents-lde/pom.xml | 2 +- wcomponents-test-lib/pom.xml | 2 +- wcomponents-theme/pom.xml | 2 +- wcomponents-xslt/pom.xml | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/code-coverage/pom.xml b/code-coverage/pom.xml index ccbbc9462..88cb603bc 100644 --- a/code-coverage/pom.xml +++ b/code-coverage/pom.xml @@ -5,7 +5,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 07a541802..838dd1607 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT pom @@ -57,7 +57,7 @@ https://github.com/bordertech/wcomponents scm:git:https://github.com/bordertech/wcomponents.git scm:git:https://github.com/bordertech/wcomponents.git - wcomponents-parent-1.5.39 + wcomponents-1.0.0 diff --git a/wcomponents-bundle/pom.xml b/wcomponents-bundle/pom.xml index 054de880f..8f63ce162 100755 --- a/wcomponents-bundle/pom.xml +++ b/wcomponents-bundle/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-core/pom.xml b/wcomponents-core/pom.xml index 03dad1d8e..e75bef9c9 100755 --- a/wcomponents-core/pom.xml +++ b/wcomponents-core/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-examples-lde/pom.xml b/wcomponents-examples-lde/pom.xml index fda606e18..2af6be6c9 100755 --- a/wcomponents-examples-lde/pom.xml +++ b/wcomponents-examples-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-examples/pom.xml b/wcomponents-examples/pom.xml index fa76b5c34..60c8e8cdd 100755 --- a/wcomponents-examples/pom.xml +++ b/wcomponents-examples/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-i18n/pom.xml b/wcomponents-i18n/pom.xml index 61d186680..94717304b 100755 --- a/wcomponents-i18n/pom.xml +++ b/wcomponents-i18n/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-lde/pom.xml b/wcomponents-lde/pom.xml index ea5008304..b791adf86 100755 --- a/wcomponents-lde/pom.xml +++ b/wcomponents-lde/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-test-lib/pom.xml b/wcomponents-test-lib/pom.xml index 633689355..e08aad3c4 100755 --- a/wcomponents-test-lib/pom.xml +++ b/wcomponents-test-lib/pom.xml @@ -8,7 +8,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT ../pom.xml diff --git a/wcomponents-theme/pom.xml b/wcomponents-theme/pom.xml index d3834ab75..f8c02ae13 100755 --- a/wcomponents-theme/pom.xml +++ b/wcomponents-theme/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT jar diff --git a/wcomponents-xslt/pom.xml b/wcomponents-xslt/pom.xml index b97344640..375eec640 100644 --- a/wcomponents-xslt/pom.xml +++ b/wcomponents-xslt/pom.xml @@ -7,7 +7,7 @@ com.github.bordertech.wcomponents wcomponents-parent - 1.5.39 + 1.5.40-SNAPSHOT jar From f1cb325b258fb924c2103d4720915481fe81ba68 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Tue, 20 Jan 2026 13:44:13 +1000 Subject: [PATCH 30/32] Roll CHANGELOG --- CHANGELOG.md | 11 ++++++++--- wcomponents-theme/package.json | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a961f4eb..73e323078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,15 @@ ### API Changes ### Enhancements -* Consistent use of try-with-resources when handling streams +### Bug Fixes + +## 1.5.39 + +### API Changes * Updated AbstractRequest to remove deprecated methods uploadFileItems and readBytes (were protected static). Use StreamUtils instead. +### Enhancements +* Consistent use of try-with-resources when handling streams * Replaced org.apache.tika:tika library with org.overviewproject:mime-types in FileUtil to validate uploaded file mime types. -* Updated FileUtil to make file extension and mime type validation case insensitive. * Updated the following dependencies: * wcomponents-core: * com.google.code.gson:gson from 2.13.1 to 2.13.2 @@ -25,8 +30,8 @@ * wcomponents-bundle: * org.ehcache:ehcahce from 3.10.8 to 3.11.1 * org.glassfish.jaxb:jaxb-runtime from 4.0.5 to 4.0.6 - ### Bug Fixes +* Updated FileUtil to make file extension and mime type validation case insensitive. ## 1.5.38 diff --git a/wcomponents-theme/package.json b/wcomponents-theme/package.json index de57f7d61..a622486a6 100644 --- a/wcomponents-theme/package.json +++ b/wcomponents-theme/package.json @@ -1,6 +1,6 @@ { "name": "wcomponents-theme", - "version": "1.5.39-SNAPSHOT", + "version": "1.5.40-SNAPSHOT", "description": "Client side code for WComponents UI tool kit.", "private": true, "com_github_bordertech": { From 3dfa1a7531af175eeaf3312332840a978dced679 Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Thu, 22 Jan 2026 14:45:52 +1000 Subject: [PATCH 31/32] Kick date parser unit test down the road --- wcomponents-theme/src/test/spec/wc.date.parser.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcomponents-theme/src/test/spec/wc.date.parser.test.mjs b/wcomponents-theme/src/test/spec/wc.date.parser.test.mjs index e12602588..b45855632 100644 --- a/wcomponents-theme/src/test/spec/wc.date.parser.test.mjs +++ b/wcomponents-theme/src/test/spec/wc.date.parser.test.mjs @@ -102,11 +102,11 @@ describe("wc/date/Parser", function() { }); it("testParserStndExpandYearPast", function() { const parser = getParser(standardMasks, true, false), - result = parser.parse("281025"); + result = parser.parse("281035"); expect(result.length).toBe(1); expect(result[0].day).toBe(28); expect(result[0].month).toBe(10); - expect(result[0].year).toBe(1925); + expect(result[0].year).toBe(1935); }); it("testParserStndMonthAbbr", function() { const parser = getParser(standardMasks, false, false), From b28bb2fe33f8bf8b02122c31dfa2721f960dea2b Mon Sep 17 00:00:00 2001 From: Jonathan Austin Date: Thu, 22 Jan 2026 15:28:38 +1000 Subject: [PATCH 32/32] Fix theme merge conflicts --- wcomponents-theme/package.json | 2 +- wcomponents-theme/src/main/js/wc/ajax/Trigger.mjs | 3 +++ wcomponents-theme/src/main/js/wc/ui/multiFileUploader.mjs | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/wcomponents-theme/package.json b/wcomponents-theme/package.json index cba21a49f..009b7f740 100755 --- a/wcomponents-theme/package.json +++ b/wcomponents-theme/package.json @@ -1,5 +1,5 @@ { - "name": "wcomponents-theme", + "name": "wc", "version": "1.5.40-SNAPSHOT", "description": "Client side code for WComponents UI tool kit.", "private": true, diff --git a/wcomponents-theme/src/main/js/wc/ajax/Trigger.mjs b/wcomponents-theme/src/main/js/wc/ajax/Trigger.mjs index 884430555..bfbc9b954 100755 --- a/wcomponents-theme/src/main/js/wc/ajax/Trigger.mjs +++ b/wcomponents-theme/src/main/js/wc/ajax/Trigger.mjs @@ -630,6 +630,9 @@ function getFormParams(element, instance) { result = addToQueryString(result, serializeElements(region, "select")); result = addToQueryString(result, serializeElements(region, "textarea")); result = addToQueryString(result, serializeElements(stateContainer, "input")); + if (instance.method !== instance.METHODS.GET) { + result = addToQueryString(result, serialize.serialize(document.getElementsByName("wc_t"))); + } } else { formUpdateManager.update(form); result = /** @type String */(serialize.serialize(form)); diff --git a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.mjs b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.mjs index 5ef4d4400..3094e5cac 100755 --- a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.mjs +++ b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.mjs @@ -853,6 +853,7 @@ function TrueAjax() { */ function sendFile(uri, uploadName, fileId, file, callback) { const formData = new FormData(), + token = document.getElementById("wc_t"), onProgress = progressEventFactory(fileId), onError = errorHandlerFactory(fileId), onAbort = abortHandlerFactory(fileId); @@ -865,6 +866,10 @@ function TrueAjax() { * The name, however, is a readonly property of blob and while we may appear to have overridden the value we probably haven't. */ formData.append(uploadName, file, file.name); + // Add session token + if (token) { + formData.append("wc_t", token.value); + } const request = { url: uri,