This is an automated email from the ASF dual-hosted git repository. ardovm pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit bb3a5a855831b861943c55afcadf2a8cf42eff01 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Fri Oct 2 21:57:00 2026 +0200 Fix acceptance of local files Assisted-By: Claude Opus 5.5 <[email protected]> --- .../main/java/com/sun/star/comp/sdbc/Tools.java | 95 +++++------------ main/connectivity/qa/complex/sdbc/ToolsTest.java | 44 ++++---- main/jvmaccess/source/classpath.cxx | 116 +++++++-------------- 3 files changed, 84 insertions(+), 171 deletions(-) diff --git a/main/connectivity/java/sdbc_jdbc/src/main/java/com/sun/star/comp/sdbc/Tools.java b/main/connectivity/java/sdbc_jdbc/src/main/java/com/sun/star/comp/sdbc/Tools.java index a9e23d6618..02144190d0 100644 --- a/main/connectivity/java/sdbc_jdbc/src/main/java/com/sun/star/comp/sdbc/Tools.java +++ b/main/connectivity/java/sdbc_jdbc/src/main/java/com/sun/star/comp/sdbc/Tools.java @@ -20,13 +20,11 @@ *************************************************************/ package com.sun.star.comp.sdbc; +import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; +import java.net.URLDecoder; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -46,15 +44,6 @@ public class Tools { private static final Logger LOGGER = Logger.getLogger(Tools.class.getName()); - /** - * URL schemes that resolve to the local filesystem or the running JVM image. - * - * <p>jvmaccess/source/classpath.cxx enforces the same allow-list in C++ for - * the UNO bootstrap class path; keep the two in sync.</p> - */ - private static final Set<String> LOCAL_PROTOCOLS = - Collections.unmodifiableSet(new HashSet<>(Arrays.asList("file", "jrt", "jmod"))); - public static SQLException toUnoException(Object source, Throwable throwable) { return toUnoException(source, throwable, 0); } @@ -153,26 +142,21 @@ public class Tools { /** * Appends a class path entry to the list of URLs used to build a class loader. * - * <p>Only local entries or a jar: wrapping a local entry are added; a file: entry must - * in addition name a path on this machine. A malformed or non-local entry is logged and - * skipped.</p> + * <p>Only a file: entry naming a path on this machine is added. + * A malformed or non-local entry is logged and skipped.</p> * * @param urls the list of class path URLs to append to * @param url the class path entry to parse and validate */ public static void addClassPathURL(Collection<URL> urls, String url) { URL javaURL; - URL effectiveURL; try { javaURL = new URL(url); - effectiveURL = getEffectiveURL(javaURL); } catch (MalformedURLException e) { LOGGER.log(Level.WARNING, e, () -> "Skipping malformed class path entry: " + url); return; } - String protocol = effectiveURL.getProtocol(); - if (LOCAL_PROTOCOLS.contains(protocol) - && (!"file".equals(protocol) || isLocalFileLocation(effectiveURL))) { + if (isLocalFileUrl(javaURL)) { LOGGER.fine(() -> "Adding class path entry: " + url); urls.add(javaURL); } else { @@ -181,65 +165,34 @@ public class Tools { } /** - * Returns the URL that actually locates the resource. - * - * <p>Since {@code jar:} only wraps another URL, that wrapped URL is returned. - * For any other URL the URL itself is returned.</p> - * - * @param url the class path URL to inspect - * @return the effective URL - * @throws MalformedURLException if the wrapped jar: URL cannot be parsed - */ - private static URL getEffectiveURL(URL url) throws MalformedURLException { - if (!"jar".equals(url.getProtocol())) { - return url; - } - String path = url.getPath(); - int separator = path.lastIndexOf("!/"); - return new URL(separator == -1 ? path : path.substring(0, separator)); - } - - /** - * Tells whether a file: URL names a path on this machine. + * Tells whether a class path entry is a file: URL naming a path on this machine. * - * <p>The host must be empty or {@code localhost}, and the path must not name another - * machine by itself. The path is judged decoded, because the file: handler - * percent-decodes it and, on Windows, turns slashes into backslashes before opening - * it; a leading escaped slash or backslash would otherwise reach the file system as a - * reference to a share.</p> + * <p>jvmaccess/source/classpath.cxx enforces the same check in C++; keep the two in sync.</p> * - * @param url a URL with the file: scheme + * @param url the class path entry * @return whether the URL names a local path */ - private static boolean isLocalFileLocation(URL url) { + private static boolean isLocalFileUrl(URL url) { + if (!"file".equals(url.getProtocol())) { + return false; + } + // The authority, if any, must be empty or localhost. String host = url.getHost(); if (host != null && !host.isEmpty() && !"localhost".equalsIgnoreCase(host)) { return false; } - String path = percentDecoded(url.getPath()); + // The decoded path must start with exactly one slash, + // which also rules out a path without a leading slash, + // and must contain no backslash at all. + String path; + try { + // URLDecoder decodes form data, so a literal '+' is escaped to keep it a '+'. + path = URLDecoder.decode(url.getPath().replace("+", "%2B"), "UTF-8"); + } catch (UnsupportedEncodingException | java.lang.IllegalArgumentException e) { + // A malformed escape, which the file: handler cannot decode either. + return false; + } return path.length() >= 2 && path.charAt(0) == '/' && path.charAt(1) != '/' && path.indexOf('\\') == -1; } - - /** - * Undoes one level of %HH escapes, as the file: handler does. Only the ASCII - * separators matter to the caller, so each escape becomes the character of its byte - * value; a malformed escape is kept. - */ - private static String percentDecoded(String s) { - StringBuilder buf = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c == '%' && s.length() - i > 2) { - int hi = Character.digit(s.charAt(i + 1), 16); - int lo = Character.digit(s.charAt(i + 2), 16); - if (hi != -1 && lo != -1) { - c = (char) (hi * 16 + lo); - i += 2; - } - } - buf.append(c); - } - return buf.toString(); - } } diff --git a/main/connectivity/qa/complex/sdbc/ToolsTest.java b/main/connectivity/qa/complex/sdbc/ToolsTest.java index ed0b6720a9..f6cfa4c97e 100644 --- a/main/connectivity/qa/complex/sdbc/ToolsTest.java +++ b/main/connectivity/qa/complex/sdbc/ToolsTest.java @@ -23,12 +23,10 @@ package complex.sdbc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; -import org.junit.Assume; import org.junit.Test; import com.sun.star.comp.sdbc.Tools; @@ -47,16 +45,6 @@ public final class ToolsTest { return urls; } - /** True if the running JRE has a URL stream handler for the given scheme. */ - private static boolean schemeSupported(String scheme) { - try { - new URL(scheme + ":/probe"); - return true; - } catch (MalformedURLException e) { - return false; - } - } - @Test public void testAddClassPathURLAddsLocalFileEntry() { List<URL> urls = collect("file:/opt/a.jar"); @@ -65,14 +53,14 @@ public final class ToolsTest { } @Test - public void testAddClassPathURLAddsJarWrappedLocalFile() { - assertEquals(1, collect("jar:file:/opt/a.jar!/").size()); + public void testAddClassPathURLTreatsSchemeCaseInsensitively() { + assertEquals(1, collect("FILE:/opt/a.jar").size()); } @Test - public void testAddClassPathURLTreatsJarInnerSchemeCaseInsensitively() { - // URL.getPath() does not normalize the wrapped URL. - assertEquals(1, collect("jar:FILE:/opt/a.jar!/").size()); + public void testAddClassPathURLSkipsJarWrappedLocalFile() { + // A JAR file is added by its own file: URL; a jar: URL is never needed. + assertTrue(collect("jar:file:/opt/a.jar!/").isEmpty()); } @Test @@ -166,14 +154,24 @@ public final class ToolsTest { } @Test - public void testAddClassPathURLAddsJrtSchemeWhenSupported() { - Assume.assumeTrue(schemeSupported("jrt")); - assertEquals(1, collect("jrt:/java.base/module-info.class").size()); + public void testAddClassPathURLAddsPathWithPlus() { + List<URL> urls = collect("file:/opt/c++/a.jar"); + assertEquals(1, urls.size()); + assertEquals("/opt/c++/a.jar", urls.get(0).getPath()); } @Test - public void testAddClassPathURLAddsJmodSchemeWhenSupported() { - Assume.assumeTrue(schemeSupported("jmod")); - assertEquals(1, collect("jmod:/x").size()); + public void testAddClassPathURLSkipsMalformedEscape() { + assertTrue(collect( + "file:/opt/%zz.jar", + "file:/opt/a.jar%").isEmpty()); + } + + @Test + public void testAddClassPathURLSkipsJvmImageSchemes() { + // Unsupported schemes fail to parse and are skipped as well. + assertTrue(collect( + "jrt:/java.base/module-info.class", + "jmod:/x").isEmpty()); } } diff --git a/main/jvmaccess/source/classpath.cxx b/main/jvmaccess/source/classpath.cxx index 100f509590..1ae940335d 100644 --- a/main/jvmaccess/source/classpath.cxx +++ b/main/jvmaccess/source/classpath.cxx @@ -34,10 +34,12 @@ #include "com/sun/star/uno/XComponentContext.hpp" #include "com/sun/star/uno/XInterface.hpp" #include "com/sun/star/uri/UriReferenceFactory.hpp" +#include "com/sun/star/uri/XUriReference.hpp" +#include "com/sun/star/uri/XUriReferenceFactory.hpp" #include "com/sun/star/uri/XVndSunStarExpandUrlReference.hpp" #include "com/sun/star/util/XMacroExpander.hpp" #include "osl/diagnose.h" -#include "rtl/ustrbuf.hxx" +#include "rtl/uri.hxx" #include "rtl/ustring.hxx" #include "sal/types.h" @@ -50,88 +52,48 @@ namespace { namespace css = ::com::sun::star; #if defined SOLAR_JAVA -int hexDigitValue(sal_Unicode c) -{ - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } - if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } - return -1; -} - -// Undoes one level of %HH escapes, as the JDK's file: handler does before it -// opens a path. Only the ASCII separators matter to the caller, so each escape -// simply becomes the code unit of its byte value; a malformed escape is kept. -::rtl::OUString percentDecoded(::rtl::OUString const & s) -{ - sal_Int32 const n = s.getLength(); - ::rtl::OUStringBuffer buf(n); - for (sal_Int32 i = 0; i != n; ++i) { - sal_Unicode c = s[i]; - if (c == '%' && n - i > 2) { - int const hi = hexDigitValue(s[i + 1]); - int const lo = hexDigitValue(s[i + 2]); - if (hi != -1 && lo != -1) { - c = static_cast< sal_Unicode >(hi * 16 + lo); - i += 2; - } - } - buf.append(c); - } - return buf.makeStringAndClear(); -} - -// Whether the part of a file: URL after the scheme names a path on this -// machine: the authority, if any, must be empty or localhost, and the path must -// not name another machine by itself. The path is judged decoded, because the -// JDK's file: handler percent-decodes it and, on Windows, turns slashes into -// backslashes before opening it -- a leading escaped slash or backslash would -// otherwise reach the file system as a reference to a share. -bool isLocalFileLocation(::rtl::OUString const & afterScheme) +// Whether a class path entry is a file: URL naming a path on this machine. +// +// On Windows, Java opens a file: URL as a UNC path, i.e. a file on another +// machine, in two cases: +// +// - if its authority names a host, e.g. file://host/share/a.jar; +// - if its path (or opaque part), once percent-decoded and with backslashes +// read as slashes, starts with two slashes, e.g. file:////host/share/a.jar, +// file:/%5C%5Chost/share/a.jar or file:%5C%5Chost/share/a.jar. +// +// com.sun.star.comp.sdbc.Tools enforces the same check on the Java side; keep +// the two in sync. +bool isLocalFileUrl( + css::uno::Reference< css::uri::XUriReferenceFactory > const & factory, + ::rtl::OUString const & url) { - ::rtl::OUString rest(afterScheme); - if (rest.indexOf('\\') != -1) { + css::uno::Reference< css::uri::XUriReference > const uriRef( + factory->parse(url)); + if (!uriRef.is() + || !uriRef->getScheme().equalsIgnoreAsciiCaseAsciiL( + RTL_CONSTASCII_STRINGPARAM("file"))) + { return false; } - if (rest.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("//"))) { - sal_Int32 const end = rest.indexOf('/', 2); - ::rtl::OUString const authority( - end == -1 ? rest.copy(2) : rest.copy(2, end - 2)); + // The authority, if any, must be empty or localhost. + if (uriRef->hasAuthority()) { + ::rtl::OUString const authority(uriRef->getAuthority()); if (authority.getLength() != 0 && !authority.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM("localhost"))) { return false; } - rest = end == -1 ? ::rtl::OUString() : rest.copy(end); - } - rest = percentDecoded(rest); - return rest.getLength() >= 2 && rest[0] == '/' && rest[1] != '/' - && rest.indexOf('\\') == -1; -} - -// URL schemes that resolve to the local file system or the running JVM image, -// optionally wrapped in a jar: URL; a file: URL must in addition name a path on -// this machine. -// -// com.sun.star.comp.sdbc.Tools enforces the same allow-list on the Java side; -// keep the two in sync. -bool isLocalClassPathUrl(::rtl::OUString const & url) -{ - ::rtl::OUString rest(url); - if (rest.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("jar:"))) { - rest = rest.copy(RTL_CONSTASCII_LENGTH("jar:")); - } - if (rest.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("file:"))) { - return isLocalFileLocation(rest.copy(RTL_CONSTASCII_LENGTH("file:"))); } - return rest.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("jrt:")) - || rest.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("jmod:")); + // The decoded path must start with exactly one slash, which also rules out + // the opaque form, and must contain no backslash at all. + ::rtl::OUString const path( + ::rtl::Uri::decode( + uriRef->getPath(), rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8)); + return path.getLength() >= 2 && path[0] == '/' && path[1] != '/' + && path.indexOf('\\') == -1; } #endif @@ -153,14 +115,14 @@ void * ::jvmaccess::ClassPath::doTranslateToUrls( if (ctorUrl == 0) { return 0; } + css::uno::Reference< css::uri::XUriReferenceFactory > const factory( + css::uri::UriReferenceFactory::create(context)); ::std::vector< jobject > urls; for (::sal_Int32 i = 0; i != -1;) { ::rtl::OUString url(classPath.getToken(0, ' ', i)); if (url.getLength() != 0) { css::uno::Reference< css::uri::XVndSunStarExpandUrlReference > - expUrl( - css::uri::UriReferenceFactory::create(context)->parse(url), - css::uno::UNO_QUERY); + expUrl(factory->parse(url), css::uno::UNO_QUERY); if (expUrl.is()) { css::uno::Reference< css::util::XMacroExpander > expander( context->getValueByName( @@ -181,7 +143,7 @@ void * ::jvmaccess::ClassPath::doTranslateToUrls( } } // Add only local entries; a non-local one is logged and skipped. - if (!isLocalClassPathUrl(url)) + if (!isLocalFileUrl(factory, url)) { OSL_TRACE( "jvmaccess::ClassPath: skipping non-local class path"
