From 9b5cf71f2e8c9b750770d7d3e093162305525729 Mon Sep 17 00:00:00 2001 From: Jumper Chen Date: Fri, 7 Jun 2024 16:17:46 +0800 Subject: [PATCH] fix ZK-5733: URIBuilder causes warnings for resources from jar with "!" in url for f51152c236ba4e4105f2570be55ecbfe617546fc --- .../src/main/java/org/zkoss/util/URLs.java | 66 +++++++++++++++++++ .../zkoss/util/resource/AbstractLoader.java | 9 ++- .../zkoss/util/resource/ContentLoader.java | 8 +-- .../org/zkoss/zhtml/impl/HtmlTreeBuilder.java | 7 +- .../zkoss/zk/ui/http/AbstractExtendlet.java | 32 +-------- .../web/servlet/dsp/InterpreterServlet.java | 7 +- .../java/org/zkoss/web/servlet/Servlets.java | 7 +- .../web/util/resource/ExtendletLoader.java | 12 +--- .../web/util/resource/ResourceLoader.java | 7 +- 9 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 zcommon/src/main/java/org/zkoss/util/URLs.java diff --git a/zcommon/src/main/java/org/zkoss/util/URLs.java b/zcommon/src/main/java/org/zkoss/util/URLs.java new file mode 100644 index 0000000000..c4e8da8554 --- /dev/null +++ b/zcommon/src/main/java/org/zkoss/util/URLs.java @@ -0,0 +1,66 @@ +/* URLs.java + + Purpose: + + Description: + + History: + 4:11 PM 2024/6/7, Created by jumperchen + +Copyright (C) 2024 Potix Corporation. All Rights Reserved. +*/ +package org.zkoss.util; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.hc.core5.net.URIBuilder; + +/** + * Utility class for URL operations. + * @author jumperchen + * @since 10.0.1 + */ +public class URLs { + + /** + * Sanitizes a URL to prevent MalformedURLException and SSRF warning. + * + * @param url The URL to be sanitized. + * @return The sanitized URL. + * @throws MalformedURLException If the URL is not in the correct format. + * @throws URISyntaxException If the URL is not formatted strictly according to RFC2396 and cannot be converted to a URI. + */ + public static URL sanitizeURL(URL url) throws MalformedURLException, URISyntaxException { + if (url == null) return null; + + final String urlString = url.getPath(); + // avoid java.net.MalformedURLException: no !/ in spec + if (urlString.contains("!/")) { + String[] parts = urlString.split("!/"); + if (parts.length == 2) { + String jarFilePath = parts[0]; + String internalPath = parts[1]; + + // Ensure the jarFilePath is properly formed + URL jarURL = new URL(jarFilePath); + URI jarURI = new URIBuilder().setScheme(jarURL.getProtocol()) + .setHost(jarURL.getHost()).setPort(jarURL.getPort()) + .setPath(jarURL.getPath()).build(); + + // Combine the jar URI with the internal path + return new URL("jar:" + jarURI + "!/" + internalPath); + } else { + throw new MalformedURLException("Invalid JAR URL format"); + } + } else { + // prevent SSRF warning + return new URIBuilder().setScheme(url.getProtocol()) + .setHost(url.getHost()).setPort(url.getPort()) + .setPath(url.getPath()) + .setCustomQuery(url.getQuery()).build().toURL(); + } + } +} diff --git a/zcommon/src/main/java/org/zkoss/util/resource/AbstractLoader.java b/zcommon/src/main/java/org/zkoss/util/resource/AbstractLoader.java index 422f4a1b8e..89c621f062 100644 --- a/zcommon/src/main/java/org/zkoss/util/resource/AbstractLoader.java +++ b/zcommon/src/main/java/org/zkoss/util/resource/AbstractLoader.java @@ -20,10 +20,11 @@ import java.net.URL; import java.net.URLConnection; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.zkoss.util.URLs; + /** * A skeletal implementation that assumes the source is either URL or File. * @@ -42,10 +43,8 @@ public long getLastModified(K src) { URLConnection conn = null; try { URL url = (URL) src; - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + // prevent SSRF attack + url = URLs.sanitizeURL(url); conn = url.openConnection(); final long v = conn.getLastModified(); return v != -1 ? v : 0; //not to reload if unknown (5.0.6 for better performance) diff --git a/zcommon/src/main/java/org/zkoss/util/resource/ContentLoader.java b/zcommon/src/main/java/org/zkoss/util/resource/ContentLoader.java index 590189c063..26fd76a4f4 100644 --- a/zcommon/src/main/java/org/zkoss/util/resource/ContentLoader.java +++ b/zcommon/src/main/java/org/zkoss/util/resource/ContentLoader.java @@ -22,9 +22,8 @@ import java.io.InputStreamReader; import java.net.URL; -import org.apache.hc.core5.net.URIBuilder; - import org.zkoss.io.Files; +import org.zkoss.util.URLs; /** * A {@link Loader} that loads the resource by use URL.getContent() @@ -40,10 +39,7 @@ public String load(Object src) throws Exception { if (src instanceof URL) { URL url = (URL) src; // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); is = url.openStream(); } else if (src instanceof File) { is = new FileInputStream((File) src); diff --git a/zhtml/src/main/java/org/zkoss/zhtml/impl/HtmlTreeBuilder.java b/zhtml/src/main/java/org/zkoss/zhtml/impl/HtmlTreeBuilder.java index cdacc0239b..e8ffc04161 100644 --- a/zhtml/src/main/java/org/zkoss/zhtml/impl/HtmlTreeBuilder.java +++ b/zhtml/src/main/java/org/zkoss/zhtml/impl/HtmlTreeBuilder.java @@ -22,13 +22,13 @@ import java.util.Scanner; import org.apache.commons.io.input.ReaderInputStream; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.idom.Namespace; import org.zkoss.idom.ProcessingInstruction; import org.zkoss.util.Pair; +import org.zkoss.util.URLs; import org.zkoss.zk.ui.UiException; import org.zkoss.zk.ui.metainfo.TreeBuilder; import org.zkoss.zsoup.Zsoup; @@ -297,10 +297,7 @@ public org.zkoss.idom.Document parse(URL url) throws Exception { if (log.isDebugEnabled()) log.debug("Parsing file: [{}]", url); // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); try (InputStream inStream = url.openStream()) { return convertToIDOM(Zsoup.parse(inStream, "UTF-8", url.getFile(), Parser.xhtmlParser())); diff --git a/zk/src/main/java/org/zkoss/zk/ui/http/AbstractExtendlet.java b/zk/src/main/java/org/zkoss/zk/ui/http/AbstractExtendlet.java index 0d5b40a23d..67d4803775 100644 --- a/zk/src/main/java/org/zkoss/zk/ui/http/AbstractExtendlet.java +++ b/zk/src/main/java/org/zkoss/zk/ui/http/AbstractExtendlet.java @@ -18,8 +18,6 @@ import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; import javax.servlet.ServletContext; @@ -29,7 +27,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +34,7 @@ import org.zkoss.idom.util.IDOMs; import org.zkoss.lang.ClassResolver; import org.zkoss.lang.Classes; +import org.zkoss.util.URLs; import org.zkoss.util.resource.ResourceCache; import org.zkoss.web.servlet.Servlets; import org.zkoss.web.util.resource.Extendlet; @@ -177,32 +175,8 @@ private InputStream getResourceAsStream(HttpServletRequest request, String path, try { URL url = _webctx.getResource(path); if (url != null) { - final String urlString = url.getPath(); - // avoid java.net.MalformedURLException: no !/ in spec - if (urlString.contains("!/")) { - String[] parts = urlString.split("!/"); - if (parts.length == 2) { - String jarFilePath = parts[0]; - String internalPath = parts[1]; - - // Ensure the jarFilePath is properly formed - URL jarURL = new URL(jarFilePath); - URI jarURI = new URIBuilder().setScheme(jarURL.getProtocol()) - .setHost(jarURL.getHost()).setPort(jarURL.getPort()) - .setPath(jarURL.getPath()).build(); - - // Combine the jar URI with the internal path - url = new URL("jar:" + jarURI + "!/" + internalPath); - } else { - throw new MalformedURLException("Invalid JAR URL format"); - } - } else { - // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()) - .setCustomQuery(url.getQuery()).build().toURL(); - } + // prevent SSRF attack + url = URLs.sanitizeURL(url); return url.openStream(); } } catch (Throwable ex) { diff --git a/zweb-dsp/src/main/java/org/zkoss/web/servlet/dsp/InterpreterServlet.java b/zweb-dsp/src/main/java/org/zkoss/web/servlet/dsp/InterpreterServlet.java index d3f5e0e5c0..a1865f3c95 100644 --- a/zweb-dsp/src/main/java/org/zkoss/web/servlet/dsp/InterpreterServlet.java +++ b/zweb-dsp/src/main/java/org/zkoss/web/servlet/dsp/InterpreterServlet.java @@ -33,13 +33,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.html.HTMLs; import org.zkoss.io.Files; import org.zkoss.lang.Exceptions; +import org.zkoss.util.URLs; import org.zkoss.util.resource.Locator; import org.zkoss.web.servlet.Charsets; import org.zkoss.web.servlet.Servlets; @@ -210,10 +210,7 @@ protected Interpretation parse(String path, File file, Object extra) throws Exce protected Interpretation parse(String path, URL url, Object extra) throws Exception { // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); InputStream is = url.openStream(); if (is != null) diff --git a/zweb/src/main/java/org/zkoss/web/servlet/Servlets.java b/zweb/src/main/java/org/zkoss/web/servlet/Servlets.java index 6b8c5554bc..fe9c3119f9 100644 --- a/zweb/src/main/java/org/zkoss/web/servlet/Servlets.java +++ b/zweb/src/main/java/org/zkoss/web/servlet/Servlets.java @@ -45,7 +45,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +54,7 @@ import org.zkoss.util.CacheMap; import org.zkoss.util.Checksums; import org.zkoss.util.Locales; +import org.zkoss.util.URLs; import org.zkoss.util.resource.Locator; import org.zkoss.util.resource.Locators; import org.zkoss.web.Attributes; @@ -854,10 +854,7 @@ public static final InputStream getResourceAsStream(ServletContext ctx, String u URL url = toURL(uri); if (url != null) { // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); return url.openStream(); } return new ParsedURI(ctx, uri).getResourceAsStream(); diff --git a/zweb/src/main/java/org/zkoss/web/util/resource/ExtendletLoader.java b/zweb/src/main/java/org/zkoss/web/util/resource/ExtendletLoader.java index 02b73c6b17..ab166359e4 100644 --- a/zweb/src/main/java/org/zkoss/web/util/resource/ExtendletLoader.java +++ b/zweb/src/main/java/org/zkoss/web/util/resource/ExtendletLoader.java @@ -20,12 +20,12 @@ import java.net.URL; import java.net.URLConnection; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.io.Files; import org.zkoss.lang.Library; +import org.zkoss.util.URLs; import org.zkoss.util.resource.Loader; /** @@ -82,10 +82,7 @@ public long getLastModified(String src) { URL url = getExtendletContext().getResource(src); if (url != null) { // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); conn = url.openConnection(); final long v = conn.getLastModified(); return v != -1 ? v : 0; //not to reload (5.0.6 for better performance) @@ -115,10 +112,7 @@ public V load(String src) throws Exception { URL real = getExtendletContext().getResource(path); if (real != null) { // prevent SSRF warning - real = new URIBuilder().setScheme(real.getProtocol()) - .setHost(real.getHost()).setPort(real.getPort()) - .setPath(real.getPath()).setCustomQuery(real.getQuery()) - .build().toURL(); + real = URLs.sanitizeURL(real); is = real.openStream(); } } catch (Throwable ex) { diff --git a/zweb/src/main/java/org/zkoss/web/util/resource/ResourceLoader.java b/zweb/src/main/java/org/zkoss/web/util/resource/ResourceLoader.java index fafc7d64c6..63f483550a 100644 --- a/zweb/src/main/java/org/zkoss/web/util/resource/ResourceLoader.java +++ b/zweb/src/main/java/org/zkoss/web/util/resource/ResourceLoader.java @@ -21,10 +21,10 @@ import java.net.URL; import java.net.URLConnection; -import org.apache.hc.core5.net.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.zkoss.util.URLs; import org.zkoss.util.resource.Loader; /** @@ -68,10 +68,7 @@ public long getLastModified(ResourceInfo src) { try { URL url = src.url; // prevent SSRF warning - url = new URIBuilder().setScheme(url.getProtocol()) - .setHost(url.getHost()).setPort(url.getPort()) - .setPath(url.getPath()).setCustomQuery(url.getQuery()) - .build().toURL(); + url = URLs.sanitizeURL(url); conn = url.openConnection(); final long v = conn.getLastModified(); return v != -1 ? v : 0; //not to reload (5.0.6 for better performance)