Skip to content

Commit d960d1b

Browse files
authored
Fix Unicode and escaped paths in JAR and directory loading (#225)
* Fix Unicode and escaped paths in JAR and directory loading * Move URL path encoding into an internal Java utility class
1 parent f9eb19a commit d960d1b

13 files changed

Lines changed: 187 additions & 25 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rustjava-runtime/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dyn-clone = { workspace = true }
1313
hashbrown = { workspace = true }
1414
libm = { version = "^0.2", default-features = false }
1515
parking_lot = { workspace = true }
16+
percent-encoding = { version = "^2.3", features = ["alloc"], default-features = false }
1617
regex = { version = "1.13.1", default-features = false, features = ["unicode"] }
1718
tracing = { workspace = true }
1819

‎rustjava-runtime/src/classes/java/lang/class_loader.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ impl ClassLoader {
116116
let mut class_paths = Vec::new();
117117
let mut urls = Vec::new();
118118
for path in class_path.split(path_separator.as_str()) {
119-
class_paths.push(JavaLangString::from_rust_string(jvm, path).await?);
120-
119+
let path = JavaLangString::from_rust_string(jvm, path).await?;
120+
class_paths.push(path.clone());
121+
let path = jvm
122+
.invoke_static("org/rustjava/net/URLPath", "encode", "(Ljava/lang/String;)Ljava/lang/String;", (path,))
123+
.await?;
124+
let path = JavaLangString::to_rust_string(jvm, &path).await?;
121125
let path = JavaLangString::from_rust_string(jvm, &format!("file:{path}")).await?;
122126
let url = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (path,)).await?;
123127
urls.push(url);

‎rustjava-runtime/src/classes/java/net/jar_url_connection.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ impl JarURLConnection {
6161
.await?;
6262

6363
let entry = JavaLangString::from_rust_string(jvm, split[1]).await?;
64+
let entry: ClassInstanceRef<String> = jvm
65+
.invoke_static("org/rustjava/net/URLPath", "decode", "(Ljava/lang/String;)Ljava/lang/String;", (entry,))
66+
.await?;
6467
jvm.put_field(&mut this, "java/net/JarURLConnection", "entry", "Ljava/lang/String;", entry)
6568
.await?;
6669

‎rustjava-runtime/src/classes/java/net/url_class_loader.rs‎

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,29 +131,47 @@ impl URLClassLoader {
131131
let urls: Vec<ClassInstanceRef<URL>> = jvm.load_array(&urls, 0, jvm.array_length(&urls).await? as _).await?;
132132

133133
for url in urls {
134-
let file = jvm.invoke_virtual(&url, "java/net/URL", "getFile", "()Ljava/lang/String;", ()).await?;
134+
let file: ClassInstanceRef<String> = jvm.invoke_virtual(&url, "java/net/URL", "getFile", "()Ljava/lang/String;", ()).await?;
135+
let path = jvm
136+
.invoke_static(
137+
"org/rustjava/net/URLPath",
138+
"decode",
139+
"(Ljava/lang/String;)Ljava/lang/String;",
140+
(file.clone(),),
141+
)
142+
.await?;
135143
let file = JavaLangString::to_rust_string(jvm, &file).await?;
144+
let path = JavaLangString::to_rust_string(jvm, &path).await?;
136145

137-
let metadata = runtime.metadata(&file).await;
138-
if file.ends_with('/') || file.is_empty() || metadata.as_ref().is_ok_and(|metadata| metadata.r#type == FileType::Directory) {
146+
let metadata = runtime.metadata(&path).await;
147+
if path.ends_with('/') || path.is_empty() || metadata.as_ref().is_ok_and(|metadata| metadata.r#type == FileType::Directory) {
139148
// directory
140-
let final_path = if file.is_empty() {
149+
let final_path = if path.is_empty() {
141150
name_str.clone()
142-
} else if file.ends_with('/') {
143-
format!("{file}{name_str}")
151+
} else if path.ends_with('/') {
152+
format!("{path}{name_str}")
144153
} else {
145-
format!("{file}/{name_str}")
154+
format!("{path}/{name_str}")
146155
};
147156

148157
if runtime.metadata(&final_path).await.is_ok() {
158+
let final_path = JavaLangString::from_rust_string(jvm, &final_path).await?;
159+
let final_path: ClassInstanceRef<String> = jvm
160+
.invoke_static(
161+
"org/rustjava/net/URLPath",
162+
"encode",
163+
"(Ljava/lang/String;)Ljava/lang/String;",
164+
(final_path,),
165+
)
166+
.await?;
149167
let new_url = jvm
150168
.new_class(
151169
"java/net/URL",
152170
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
153171
(
154172
JavaLangString::from_rust_string(jvm, "file").await?,
155173
JavaLangString::from_rust_string(jvm, "").await?,
156-
JavaLangString::from_rust_string(jvm, &final_path).await?,
174+
final_path,
157175
),
158176
)
159177
.await?;
@@ -165,8 +183,12 @@ impl URLClassLoader {
165183
} else {
166184
// treat as jar
167185
let name_str = name_str.trim_start_matches('/');
168-
169-
let jar_url_str = format!("jar:file:{file}!/{name_str}"); // TODO url might not be file
186+
let entry = JavaLangString::from_rust_string(jvm, name_str).await?;
187+
let entry = jvm
188+
.invoke_static("org/rustjava/net/URLPath", "encode", "(Ljava/lang/String;)Ljava/lang/String;", (entry,))
189+
.await?;
190+
let entry = JavaLangString::to_rust_string(jvm, &entry).await?;
191+
let jar_url_str = format!("jar:file:{file}!/{entry}"); // TODO url might not be file
170192
let jar_url = JavaLangString::from_rust_string(jvm, &jar_url_str).await?;
171193
let jar_url = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (jar_url,)).await?;
172194
let connection: ClassInstanceRef<JarURLConnection> = jvm

‎rustjava-runtime/src/classes/org/rustjava/net.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ mod file_url_connection;
22
mod file_url_handler;
33
mod jar_url_connection;
44
mod jar_url_handler;
5+
mod url_path;
56

67
pub use self::{
78
file_url_connection::FileURLConnection, file_url_handler::FileURLHandler, jar_url_connection::JarURLConnection, jar_url_handler::JarURLHandler,
9+
url_path::URLPath,
810
};

‎rustjava-runtime/src/classes/org/rustjava/net/file_url_handler.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ impl FileURLHandler {
5252
tracing::debug!("org.rustjava.net.FileURLHandler::openConnection({this:?}, {url:?})");
5353

5454
let file: ClassInstanceRef<String> = jvm.invoke_virtual(&url, "java/net/URL", "getFile", "()Ljava/lang/String;", ()).await?;
55+
let file: ClassInstanceRef<String> = jvm
56+
.invoke_static("org/rustjava/net/URLPath", "decode", "(Ljava/lang/String;)Ljava/lang/String;", (file,))
57+
.await?;
5558
let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (file,)).await?;
5659

5760
let connection = jvm

‎rustjava-runtime/src/classes/org/rustjava/net/jar_url_connection.rs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,15 @@ impl JarURLConnection {
8989
if !cache.is_null() {
9090
Ok(cache)
9191
} else {
92-
let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (name.clone(),)).await?;
92+
let path: ClassInstanceRef<String> = jvm
93+
.invoke_static(
94+
"org/rustjava/net/URLPath",
95+
"decode",
96+
"(Ljava/lang/String;)Ljava/lang/String;",
97+
(name.clone(),),
98+
)
99+
.await?;
100+
let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?;
93101
let jar_file = jvm.new_class("java/util/jar/JarFile", "(Ljava/io/File;)V", (file,)).await?;
94102

95103
let _: ClassInstanceRef<Object> = jvm
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use alloc::{string::ToString, vec};
2+
3+
use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
4+
use jvm_class_proto::JavaMethodProto;
5+
use jvm_types::{ClassAccessFlags, MethodAccessFlags};
6+
use percent_encoding::{AsciiSet, CONTROLS, percent_decode_str, utf8_percent_encode};
7+
8+
use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String};
9+
10+
pub struct URLPath;
11+
12+
impl URLPath {
13+
const ESCAPED: &AsciiSet = &CONTROLS
14+
.add(b' ')
15+
.add(b'"')
16+
.add(b'#')
17+
.add(b'%')
18+
.add(b'<')
19+
.add(b'>')
20+
.add(b'?')
21+
.add(b'`')
22+
.add(b'{')
23+
.add(b'}')
24+
.add(b'!');
25+
26+
pub fn as_proto() -> RuntimeClassProto {
27+
RuntimeClassProto {
28+
name: "org/rustjava/net/URLPath",
29+
parent_class: Some("java/lang/Object"),
30+
interfaces: vec![],
31+
methods: vec![
32+
JavaMethodProto::new(
33+
"encode",
34+
"(Ljava/lang/String;)Ljava/lang/String;",
35+
Self::encode,
36+
MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC,
37+
),
38+
JavaMethodProto::new(
39+
"decode",
40+
"(Ljava/lang/String;)Ljava/lang/String;",
41+
Self::decode,
42+
MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC,
43+
),
44+
],
45+
fields: vec![],
46+
access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL,
47+
}
48+
}
49+
50+
async fn encode(jvm: &Jvm, _: &mut RuntimeContext, path: ClassInstanceRef<String>) -> Result<ClassInstanceRef<String>> {
51+
let path = JavaLangString::to_rust_string(jvm, &path).await?;
52+
Ok(
53+
JavaLangString::from_rust_string(jvm, &utf8_percent_encode(&path, Self::ESCAPED).to_string())
54+
.await?
55+
.into(),
56+
)
57+
}
58+
59+
async fn decode(jvm: &Jvm, _: &mut RuntimeContext, path: ClassInstanceRef<String>) -> Result<ClassInstanceRef<String>> {
60+
let path = JavaLangString::to_rust_string(jvm, &path).await?;
61+
match percent_decode_str(&path).decode_utf8() {
62+
Ok(path) => Ok(JavaLangString::from_rust_string(jvm, &path).await?.into()),
63+
Err(_) => Err(jvm.exception("java/lang/IllegalArgumentException", "Invalid UTF-8 in URL path").await),
64+
}
65+
}
66+
}

‎rustjava-runtime/src/loader.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
273273
crate::classes::org::rustjava::net::FileURLHandler::as_proto(),
274274
crate::classes::org::rustjava::net::JarURLConnection::as_proto(),
275275
crate::classes::org::rustjava::net::JarURLHandler::as_proto(),
276+
crate::classes::org::rustjava::net::URLPath::as_proto(),
276277
crate::classes::org::rustjava::lang::RustJarClassLoader::as_proto(),
277278
];
278279

0 commit comments

Comments
 (0)