You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openoffice.apache.org by ar...@apache.org on 2026/10/03 08:20:08 UTC
(openoffice) 03/03: Fix acceptance of local files
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 <pi...@github.copernik.eu>
AuthorDate: Fri Oct 2 21:57:00 2026 +0200
Fix acceptance of local files
Assisted-By: Claude Opus 5.5 <no...@anthropic.com>
---
.../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"