You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by js...@apache.org on 2024/08/14 10:43:04 UTC
(sling-org-apache-sling-xss) 02/02: SLING-12366 - Failure to read from InputStream backed by closed session
This is an automated email from the ASF dual-hosted git repository.
jsedding pushed a commit to branch jsedding/SLING-12366-read-from-closed-session
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-xss.git
commit 472348656f2e4bfdc954363abfc7f3501ef4d207
Author: Julian Sedding <js...@apache.org>
AuthorDate: Tue Jul 2 12:46:34 2024 +0200
SLING-12366 - Failure to read from InputStream backed by closed session
- fix failing test case
---
.../org/apache/sling/xss/impl/XSSFilterImpl.java | 174 +++++++++++----------
.../XSSProtectionAPIWebConsolePlugin.java | 10 +-
2 files changed, 100 insertions(+), 84 deletions(-)
diff --git a/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java b/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
index 98f4c3d..606716e 100644
--- a/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
+++ b/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
@@ -16,7 +16,10 @@
******************************************************************************/
package org.apache.sling.xss.impl;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -25,8 +28,11 @@ import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.regex.Pattern;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.translate.NumericEntityUnescaper;
@@ -37,8 +43,6 @@ import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
import org.apache.sling.api.resource.observation.ResourceChange;
import org.apache.sling.api.resource.observation.ResourceChangeListener;
-import org.apache.sling.commons.metrics.Counter;
-import org.apache.sling.commons.metrics.MetricsService;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.apache.sling.xss.ProtectionContext;
import org.apache.sling.xss.XSSFilter;
@@ -47,6 +51,7 @@ import org.apache.sling.xss.impl.xml.Attribute;
import org.apache.sling.xss.impl.xml.Regexp;
import org.apache.sling.xss.impl.xml.Tag;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
@@ -88,7 +93,7 @@ public class XSSFilterImpl implements XSSFilter {
}
- private final Logger logger = LoggerFactory.getLogger(XSSFilterImpl.class);
+ private static final Logger logger = LoggerFactory.getLogger(XSSFilterImpl.class);
public static final String ALPHA = "(?:\\p{L}\\p{M}*)";
public static final String HEX_DIGIT = "\\p{XDigit}";
@@ -175,7 +180,6 @@ public class XSSFilterImpl implements XSSFilter {
private final XSSFilterRule plainHtmlContext = new PlainTextToHtmlContentContext();
private volatile AntiSamyPolicy activePolicy;
- private volatile PolicyHandler policyHandler;
@Reference
private ResourceResolverFactory resourceResolverFactory;
@@ -194,7 +198,7 @@ public class XSSFilterImpl implements XSSFilter {
@Override
public boolean check(final ProtectionContext context, final String src) {
final XSSFilterRule ctx = this.getFilterRule(context);
- return ctx.check(policyHandler, src);
+ return ctx.check(getPolicyHandler(), src);
}
@Override
@@ -205,7 +209,13 @@ public class XSSFilterImpl implements XSSFilter {
@Override
public String filter(final ProtectionContext context, final String src) {
final XSSFilterRule ctx = this.getFilterRule(context);
- return ctx.filter(policyHandler, src);
+ return ctx.filter(getPolicyHandler(), src);
+ }
+
+ private @Nullable PolicyHandler getPolicyHandler() {
+ return Optional.ofNullable(getActivePolicy())
+ .map(AntiSamyPolicy::getPolicyHandler)
+ .orElse(null);
}
@Override
@@ -235,6 +245,13 @@ public class XSSFilterImpl implements XSSFilter {
return activePolicy;
}
+ public void writeActivePolicyConfig(OutputStream outputStream) {
+ withPolicyResource(policyResource -> {
+ activePolicy.writeConfig(policyResource, outputStream);
+ return null;
+ });
+ }
+
private boolean runHrefValidation(@NotNull String url) {
// Same logic as in org.owasp.validator.html.scan.MagicSAXFilter.startElement()
String urlLowerCase = url.toLowerCase();
@@ -266,7 +283,7 @@ public class XSSFilterImpl implements XSSFilter {
protected void activate(ComponentContext componentContext, Configuration configuration) {
// load default handler
policyPath = configuration.policyPath();
- updatePolicy();
+ updateActivePolicy();
if (serviceRegistration != null) {
serviceRegistration.unregister();
}
@@ -285,64 +302,32 @@ public class XSSFilterImpl implements XSSFilter {
}
}
- synchronized void updatePolicy() {
- this.policyHandler = null;
- try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
- Resource policyResource = xssResourceResolver.getResource(policyPath);
- if (policyResource != null) {
- setActivePolicy(policyResource);
- }
- } catch (final LoginException e) {
- logger.error("Unable to load the default policy file.", e);
- }
- if (policyHandler == null) {
+ synchronized void updateActivePolicy() {
+ final AntiSamyPolicy originalActivePolicy = this.activePolicy;
+ this.activePolicy = withPolicyResource(AntiSamyPolicy::create);
+ if (activePolicy == originalActivePolicy) {
// the content was not installed but the service is active; let's use the embedded file for the default handler
- setActiveEmbededPolicy();
+ this.activePolicy = AntiSamyPolicy.createEmbedded();
}
- if (policyHandler == null || activePolicy == null) {
+ if (activePolicy == originalActivePolicy) {
+ activePolicy = null;
throw new IllegalStateException("Cannot load a policy handler.");
}
+ updatePolicyHandler(activePolicy.getPolicyHandler());
}
- private void setActiveEmbededPolicy() {
- // the content was not installed but the service is active; let's use the embedded file for the default handler
- logger.info("Could not find a policy file at the configured location {}. Attempting to use the default resource embedded in" +
- " the bundle.", policyPath);
- activePolicy = new AntiSamyPolicy(true, EMBEDDED_POLICY_PATH);
- try (InputStream policyStream = activePolicy.read()) {
- setPolicyHandler(new PolicyHandler(policyStream));
- logger.info("Installed policy from the embedded {} file from the bundle.", activePolicy.path);
- } catch (Exception e) {
- activePolicy = null;
- Throwable[] suppressed = e.getSuppressed();
- if (suppressed.length > 0) {
- for (Throwable t : suppressed) {
- logger.error("Unable to load policy from embedded policy file.", t);
- }
- }
- logger.error("Unable to load policy from embedded policy file.", e);
- }
-
- }
-
- private void setActivePolicy(Resource policyResource) {
- activePolicy = new AntiSamyPolicy(false, policyResource.getPath());
- try (InputStream policyStream = activePolicy.read()) {
- setPolicyHandler(new PolicyHandler(policyStream));
- logger.info("Installed policy from {}.", activePolicy.path);
- } catch (Exception e) {
- activePolicy = null;
- Throwable[] suppressed = e.getSuppressed();
- if (suppressed.length > 0) {
- for (Throwable t : suppressed) {
- logger.error("Unable to load policy from " + policyResource.getPath(), t);
- }
+ private <T> T withPolicyResource(Function<Resource, T> mapper) {
+ try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
+ Resource policyResource = xssResourceResolver.getResource(policyPath);
+ if (policyResource != null) {
+ return mapper.apply(policyResource);
}
- logger.error("Unable to load policy from " + policyResource.getPath(), e);
+ } catch (final LoginException e) {
+ logger.error("Unable to load the default policy file.", e);
}
+ return null;
}
-
/**
* Get the filter rule context.
*/
@@ -356,15 +341,13 @@ public class XSSFilterImpl implements XSSFilter {
return this.plainHtmlContext;
}
- private void setPolicyHandler(PolicyHandler policyHandler) {
+ private void updatePolicyHandler(PolicyHandler policyHandler) {
Tag linkTag = policyHandler.getPolicy().getTagRules().get("a");
hrefAttribute = (linkTag != null) ? linkTag.getAttributeByName("href") : null;
if (hrefAttribute == null) {
// Fallback to default configuration
hrefAttribute = DEFAULT_HREF_ATTRIBUTE;
}
-
- this.policyHandler = policyHandler;
}
private class PolicyChangeListener implements ResourceChangeListener, ExternalResourceChangeListener {
@@ -373,43 +356,76 @@ public class XSSFilterImpl implements XSSFilter {
for (ResourceChange change : resourceChanges) {
if (change.getPath().endsWith(policyPath)) {
logger.info("Detected policy file change ({}) at {}. Updating policy handler.", change.getType().name(), change.getPath());
- updatePolicy();
+ updateActivePolicy();
}
}
}
}
- public class AntiSamyPolicy {
- private final boolean embedded;
- private final String path;
+ public static class AntiSamyPolicy {
+
+ private final String policyPath;
+
+ private final PolicyHandler policyHandler;
- AntiSamyPolicy(boolean embedded, String path) {
- this.embedded = embedded;
- this.path = path;
+ public static AntiSamyPolicy create(Resource policyResource) {
+ String policyPath = policyResource.getPath();
+ return createAntiSamyPolicy(policyPath, () -> streamResource(policyResource));
}
- public InputStream read() {
- if (embedded) {
- return this.getClass().getClassLoader().getResourceAsStream(EMBEDDED_POLICY_PATH);
- }
- try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
- Resource policyResource = xssResourceResolver.getResource(path);
- if (policyResource != null) {
- return policyResource.adaptTo(InputStream.class);
- } else {
- throw new IllegalStateException("Cannot read policy from " + path + ".");
+ public static AntiSamyPolicy createEmbedded() {
+ return createAntiSamyPolicy(null, AntiSamyPolicy::streamEmbedded);
+ }
+
+ private static AntiSamyPolicy createAntiSamyPolicy(@Nullable String policyPath, @NotNull Supplier<InputStream> policySupplier) {
+ String pathName = policyPath == null ? "embedded policy file" : policyPath;
+ try (InputStream policyStream = policySupplier.get()) {
+ PolicyHandler policyHandler = new PolicyHandler(policyStream);
+ logger.info("Installed policy from {}.", pathName);
+ return new AntiSamyPolicy(policyHandler, policyPath);
+ } catch (Exception e) {
+ Throwable[] suppressed = e.getSuppressed();
+ for (Throwable t : suppressed) {
+ logger.error("Unable to load policy from {}.", pathName, t);
}
- } catch (LoginException e) {
- throw new IllegalStateException("Cannot read policy.", e);
+ logger.error("Unable to load policy from {}.", pathName, e);
+ return null;
}
}
+ private AntiSamyPolicy(@NotNull PolicyHandler policyHandler, @Nullable String policyPath) {
+ this.policyPath = policyPath;
+ this.policyHandler = policyHandler;
+ }
+
public boolean isEmbedded() {
- return embedded;
+ return policyPath == null;
}
public String getPath() {
- return path;
+ return isEmbedded() ? EMBEDDED_POLICY_PATH : policyPath;
+ }
+
+ public PolicyHandler getPolicyHandler() {
+ return policyHandler;
+ }
+
+ public void writeConfig(Resource policyResource, OutputStream outputStream) {
+ try (InputStream inputStream = isEmbedded() ? streamEmbedded() : streamResource(policyResource)) {
+ if (inputStream != null) {
+ IOUtils.copy(inputStream, outputStream);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static @Nullable InputStream streamResource(Resource policyResource) {
+ return policyResource.adaptTo(InputStream.class);
+ }
+
+ private static @Nullable InputStream streamEmbedded() {
+ return XSSFilterImpl.class.getClassLoader().getResourceAsStream(EMBEDDED_POLICY_PATH);
}
}
}
diff --git a/src/main/java/org/apache/sling/xss/impl/webconsole/XSSProtectionAPIWebConsolePlugin.java b/src/main/java/org/apache/sling/xss/impl/webconsole/XSSProtectionAPIWebConsolePlugin.java
index c389d10..536cfe4 100644
--- a/src/main/java/org/apache/sling/xss/impl/webconsole/XSSProtectionAPIWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/xss/impl/webconsole/XSSProtectionAPIWebConsolePlugin.java
@@ -18,6 +18,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.xss.impl.webconsole;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -148,7 +149,7 @@ public class XSSProtectionAPIWebConsolePlugin extends HttpServlet {
response.setContentType("application/xml");
response.setHeader("Content-Disposition", "attachment; filename=config.xml");
XSSFilterImpl xssFilterImpl = (XSSFilterImpl) xssFilter;
- IOUtils.copy(xssFilterImpl.getActivePolicy().read(), response.getOutputStream());
+ xssFilterImpl.writeActivePolicyConfig(response.getOutputStream());
} catch (IOException e) {
LOGGER.error("Unable to stream AntiSamy configuration.", e);
}
@@ -189,11 +190,10 @@ public class XSSProtectionAPIWebConsolePlugin extends HttpServlet {
printWriter.printf("is loaded from %s.", antiSamyPolicy.getPath());
}
printWriter.write("<button style='float:right' type='button' id='download-config'>Download</button></p>");
- String contents = "";
- try (InputStream configurationStream = antiSamyPolicy.read()) {
- contents = IOUtils.toString(configurationStream, StandardCharsets.UTF_8);
- }
printWriter.write("<pre class='prettyprint linenums'>");
+ ByteArrayOutputStream configStream = new ByteArrayOutputStream();
+ xssFilterImpl.writeActivePolicyConfig(configStream);
+ String contents = new String(configStream.toByteArray(), StandardCharsets.UTF_8);
printWriter.write(StringEscapeUtils.escapeHtml4(contents));
printWriter.write("</pre>");
printWriter.write("</div>");