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"

Reply via email to