From 155dff745e5ef56cf7b91acbcd80ab50eec1fa10 Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Fri, 16 Feb 2018 14:01:38 +0330 Subject: [PATCH] fix java.net.JarURLConnection#parseSpecs See also WW-4920 --- .../xwork2/util/fs/JarEntryRevision.java | 4 +- .../util/fs/StrutsJarURLConnection.java | 115 +++++++++++++++--- .../xwork2/util/fs/JarEntryRevisionTest.java | 28 ++++- 3 files changed, 124 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java index dc5f0a757..a0fea58c4 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java @@ -37,7 +37,7 @@ public class JarEntryRevision extends Revision { private long lastModified; public static Revision build(URL fileUrl, FileManager fileManager) { - JarURLConnection conn = null; + StrutsJarURLConnection conn = null; try { conn = StrutsJarURLConnection.openConnection(fileUrl); conn.setUseCaches(false); @@ -70,7 +70,7 @@ public class JarEntryRevision extends Revision { } public boolean needsReloading() { - JarURLConnection conn = null; + StrutsJarURLConnection conn = null; long lastLastModified = lastModified; try { conn = StrutsJarURLConnection.openConnection(jarFileURL); diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java index 8c1b71a79..1077d9087 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java @@ -18,6 +18,8 @@ */ package com.opensymphony.xwork2.util.fs; +import sun.net.www.ParseUtil; + import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; @@ -30,24 +32,77 @@ import java.nio.file.StandardCopyOption; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.jar.JarEntry; import java.util.jar.JarFile; /** - * WW-4901 Decouples from underlying implementation of {@link URL#openConnection()} + * WW-4901 If was needed, decouples from underlying implementation of {@link URL#openConnection()} * e.g. from IBM WebSphere com.ibm.ws.classloader.Handler$ClassLoaderURLConnection + * Also decouples from and fixes {@link JarURLConnection#parseSpecs(URL)} if was needed + * e.g. from Oracle WebLogic which may report jar urls like "zip:C:/web-app-lib-path/some-jar.jar" + * but {@link JarURLConnection#parseSpecs(URL)} breaks on such urls + * While {@link JarURLConnection#parseSpecs(URL)} is private then we had too extends {@link URLConnection} instead * @since 2.5.15 */ -class StrutsJarURLConnection extends JarURLConnection { - private JarFile jarFile; +class StrutsJarURLConnection extends URLConnection { + private static final String FILE_URL_PREFIX = "file:"; - private StrutsJarURLConnection(URL url) throws MalformedURLException { + private JarURLConnection jarURLConnection; + + private JarFile jarFile; + private String entryName; + private URL jarFileURL; + + private StrutsJarURLConnection(URL url) throws IOException { super(url); + + URLConnection conn = this.url.openConnection(); + if (conn instanceof JarURLConnection) {//decoupling is not needed? + jarURLConnection = (JarURLConnection) conn; + } else { + try { + conn.getInputStream().close(); + } catch (IOException ignored) { + } + parseSpecs(url); + } } - @Override - public JarFile getJarFile() throws IOException { - connect(); - return jarFile; + /** + * A fixed copy of {@link JarURLConnection#parseSpecs(URL)} + */ + private void parseSpecs(URL url) throws MalformedURLException { + String spec = url.getFile(); + + int separator = spec.indexOf("!/"); + /* + * REMIND: we don't handle nested JAR URLs + */ + if (separator == -1) { + throw new MalformedURLException("no !/ found in url spec:" + spec); + } + + // start of fixing JarURLConnection#parseSpecs(URL) via handling MalformedURLException + String jarFileSpec = spec.substring(0, separator++); + try { + jarFileURL = new URL(jarFileSpec); + } catch (MalformedURLException e) { + // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". + // This usually indicates that the jar file resides in the file system. + if (!jarFileSpec.startsWith("/")) { + jarFileSpec = "/" + jarFileSpec; + } + jarFileURL = new URL(FILE_URL_PREFIX + jarFileSpec); + } + // end of fix + + entryName = null; + + /* if ! is the last letter of the innerURL, entryName is null */ + if (++separator != spec.length()) { + entryName = spec.substring(separator, spec.length()); + entryName = ParseUtil.decode (entryName); + } } @Override @@ -56,7 +111,12 @@ class StrutsJarURLConnection extends JarURLConnection { return; } - try (final InputStream in = getJarFileURL().openConnection().getInputStream()) { + if (jarURLConnection != null) { + connected = true; + return; + } + + try (final InputStream in = jarFileURL.openConnection().getInputStream()) { jarFile = AccessController.doPrivileged( new PrivilegedExceptionAction() { public JarFile run() throws IOException { @@ -84,19 +144,34 @@ class StrutsJarURLConnection extends JarURLConnection { } } - - static JarURLConnection openConnection(URL url) throws IOException { - URLConnection conn = url.openConnection(); - if (conn instanceof JarURLConnection) { - return (JarURLConnection) conn; + JarEntry getJarEntry() throws IOException { + if (jarURLConnection != null) { + return jarURLConnection.getJarEntry(); } else { - try { - conn.getInputStream().close(); - } catch (IOException ignored) { - } + connect(); + return jarFile.getJarEntry(entryName); } + } - StrutsJarURLConnection result = new StrutsJarURLConnection(url); - return result; + @Override + public void setUseCaches(boolean usecaches) { + super.setUseCaches(usecaches); + + if (jarURLConnection != null) { + jarURLConnection.setUseCaches(usecaches); + } + } + + @Override + public InputStream getInputStream() throws IOException { + if (jarURLConnection != null) { + return jarURLConnection.getInputStream(); + } else { + return jarFile.getInputStream(jarFile.getJarEntry(entryName)); + } + } + + static StrutsJarURLConnection openConnection(URL url) throws IOException { + return new StrutsJarURLConnection(url); } } diff --git a/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java b/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java index 5fd4215d1..54a10b90a 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java @@ -23,6 +23,7 @@ import com.opensymphony.xwork2.FileManagerFactory; import com.opensymphony.xwork2.XWorkTestCase; import org.apache.commons.io.IOUtils; +import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -67,6 +68,7 @@ public class JarEntryRevisionTest extends XWorkTestCase { createJarFile(now); URL url = new URL("jar:file:target/JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class"); Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; assertFalse(entry.needsReloading()); createJarFile(now + 60000); @@ -81,6 +83,30 @@ public class JarEntryRevisionTest extends XWorkTestCase { "jar:file:target/JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class", new ContainerProvidedURLStreamHandler()); Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; + assertFalse(entry.needsReloading()); + + createJarFile(now + 60000); + assertTrue(entry.needsReloading()); + } + + public void testNeedsReloadingWithContainerProvidedURLConnectionEmptyProtocol() throws Exception { + long now = System.currentTimeMillis(); + + createJarFile(now); + File targetDir = new File("target"); + String targetUrlStr = targetDir.toURI().toURL().toString(); + if (targetUrlStr.startsWith("file:")) { + targetUrlStr = targetUrlStr.substring(5);//emptying protocol; we expect framework will fix it + } + if (targetUrlStr.startsWith("/")) { + targetUrlStr = targetUrlStr.substring(1);//we expect framework will fix it also + } + URL url = new URL(null, + "zip:" + targetUrlStr + "JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class", + new ContainerProvidedURLStreamHandler()); + Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; assertFalse(entry.needsReloading()); createJarFile(now + 60000); @@ -107,7 +133,7 @@ public class JarEntryRevisionTest extends XWorkTestCase { */ private class ContainerProvidedURLConnection extends URLConnection { - protected ContainerProvidedURLConnection(URL url) { + ContainerProvidedURLConnection(URL url) { super(url); }