Skip to content

Commit ffb36ab

Browse files
authored
Avoid percent-encoding for "(" and ")" characters.
The VRChat API does not accept percent-encoded variants of InstanceID, specifically the characters "(" and ")". This causes an error when attempting the following API call: ``` https://api.vrchat.cloud/api/1/instances/wrld_28aab3e4-953f-4c85-9223-ef29a0873a6e:31124%7Egroup%28grp_b2a19685-c53e-4c5a-9503-744a5373bf2c%29%7EgroupAccessType%28plus%29%7Eregion%28use%29/shortName ``` ```json {"error":{"message":"\"malformed url\"","status_code":400,"waf_code":26497}} ``` When using the raw string without encoding, the API yields a `200` response as expected. ``` https://vrchat.com/api/1/instances/wrld_28aab3e4-953f-4c85-9223-ef29a0873a6e:31124~group(grp_b2a19685-c53e-4c5a-9503-744a5373bf2c)~groupAccessType(plus)~region(use)/shortName ```
1 parent af1fae7 commit ffb36ab

6 files changed

Lines changed: 83 additions & 2 deletions

File tree

generate.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ printf "\n[dev-dependencies]\ntokio = { version = '1', features = ['macros', 'rt
3232

3333
find src/ -type f -name "*.rs" -exec sed -i 's/models::models/models/g' {} +
3434
find src/ -type f -name "*.rs" -exec sed -i -E "s/(::)?std::path::PathBuf/crate::patches::better_file_upload::File<'_>/g" {} +
35+
find src/ -type f -name "*.rs" -exec sed -i -E "s/(::)?url::form_urlencoded::byte_serialize/crate::patches::urlencode/g" {} +
3536

3637
rm -r src/patches
3738
cp -r patches src/patches

patches/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
pub mod better_file_upload;
1+
pub mod better_file_upload;
2+
pub mod percent_encoding;

patches/percent_encoding.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
fn hex_digit(b: u8) -> Option<u8> {
3+
match b {
4+
b'0'..=b'9' => Some(b - b'0'),
5+
b'a'..=b'f' => Some(b - b'a' + 10),
6+
b'A'..=b'F' => Some(b - b'A' + 10),
7+
_ => None,
8+
}
9+
}
10+
11+
fn parse_percent_encoded(bytes: &[u8; 3]) -> Option<u8> {
12+
if bytes[0] != b'%' { return None; }
13+
let hi = hex_digit(bytes[1])?;
14+
let lo = hex_digit(bytes[2])?;
15+
Some((hi << 4) | lo)
16+
}
17+
18+
pub fn urlencode(bytes: &[u8]) -> String {
19+
::url::form_urlencoded::byte_serialize(bytes)
20+
.map(|string| {
21+
debug_assert!(
22+
!string.starts_with('%') || string.len() == 3,
23+
"the iterator should yield percent-encoded strings of exactly 3 bytes, or unescaped strings"
24+
);
25+
26+
let parsed = match string.as_bytes().try_into() {
27+
Ok(bytes) => parse_percent_encoded(bytes),
28+
Err(_) => None,
29+
};
30+
31+
// The VRChat API deviates from the application/x-www-form-urlencoded percent-encode set, for values like the InstanceID.
32+
// The characters bellow should remain unchanged for URI parameters, or requests will be rejected as malformed.
33+
match parsed {
34+
Some(b'(') => "(",
35+
Some(b')') => ")",
36+
_ => string
37+
}
38+
})
39+
}

src/apis/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl<T> From<std::io::Error> for Error<T> {
6767
}
6868

6969
pub fn urlencode<T: AsRef<str>>(s: T) -> String {
70-
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
70+
crate::patches::urlencode(s.as_ref().as_bytes()).collect()
7171
}
7272

7373
pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {

src/patches/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod better_file_upload;
2+
pub mod percent_encoding;

src/patches/percent_encoding.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
fn hex_digit(b: u8) -> Option<u8> {
3+
match b {
4+
b'0'..=b'9' => Some(b - b'0'),
5+
b'a'..=b'f' => Some(b - b'a' + 10),
6+
b'A'..=b'F' => Some(b - b'A' + 10),
7+
_ => None,
8+
}
9+
}
10+
11+
fn parse_percent_encoded(bytes: &[u8; 3]) -> Option<u8> {
12+
if bytes[0] != b'%' { return None; }
13+
let hi = hex_digit(bytes[1])?;
14+
let lo = hex_digit(bytes[2])?;
15+
Some((hi << 4) | lo)
16+
}
17+
18+
pub fn urlencode(bytes: &[u8]) -> String {
19+
::url::form_urlencoded::byte_serialize(bytes)
20+
.map(|string| {
21+
debug_assert!(
22+
!string.starts_with('%') || string.len() == 3,
23+
"the iterator should yield percent-encoded strings of exactly 3 bytes, or unescaped strings"
24+
);
25+
26+
let parsed = match string.as_bytes().try_into() {
27+
Ok(bytes) => parse_percent_encoded(bytes),
28+
Err(_) => None,
29+
};
30+
31+
// The VRChat API deviates from the application/x-www-form-urlencoded percent-encode set, for values like the InstanceID.
32+
// The characters bellow should remain unchanged for URI parameters, or requests will be rejected as malformed.
33+
match parsed {
34+
Some(b'(') => "(",
35+
Some(b')') => ")",
36+
_ => string
37+
}
38+
})
39+
}

0 commit comments

Comments
 (0)