Skip to content

Avoid percent-encoding for "(" and ")" characters.#43

Merged
C0D3-M4513R merged 1 commit into
vrchatapi:mainfrom
Powerbyte7:fix-encoding
Jul 5, 2026
Merged

Avoid percent-encoding for "(" and ")" characters.#43
C0D3-M4513R merged 1 commit into
vrchatapi:mainfrom
Powerbyte7:fix-encoding

Conversation

@Powerbyte7

Copy link
Copy Markdown
Contributor

This PR fixes URL encoding of InstanceID values.

The Rust SDK follows the application/x-www-form-urlencoded percent-encode set for encoding. However, the VRChat API rejects percent-encoded parentheses (%28 and %29) in InstanceID path parameters, returning a 400 "malformed url" response instead of accepting the RFC-compliant encoding.

For example, the following request fails:

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
{"error":{"message":"\"malformed url\"","status_code":400,"waf_code":26497}}

Using the same InstanceID without encoding the parentheses succeeds 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

I initially encountered this issue in the Rust SDK, but it also affects the C# and Java SDK:

// Unhandled exception. VRChat.API.Client.ApiException: Error calling GetShortName: {"error":{"message":"\"malformed url\"","status_code":400,"waf_code":26497}}
await vrchat.Instances.GetShortNameAsync(
    "wrld_ba913a96-fac4-4048-a062-9aa5db092812",
    "31124~group(grp_b2a19685-c53e-4c5a-9503-744a5373bf2c)~groupAccessType(plus)~region(use)"
);

To better understand the behavior, I tested every ASCII character in both encoded and unencoded form against multiple API endpoints. The following reserved characters consistently resulted in malformed requests when percent-encoded:

!$&'()*+,:;=@[\]

These all fall under reserved characters as defined in RFC3986 and are used for delimeters. The ~ character, despite my earlier assumption, does not exhibit this behavior when percent-encoded, which is consistent with it being part of the RFC 3986 unreserved character set.

The OpenAPI specification disallows the :/?#[]@!$&'()*+,;= characters from appearing in path, query, cookie and header parameters without encoding by default. It is possible to override this using allowReserved, but unfortunately that's exlusive to query parameters. The future OpenAPI 3.2 specification allows defining this for all relevant parameters, but it will take a while for it to be adopted.

Since modifying OpenAPI Generator itself is undesirable, I considered several alternatives:

  1. Use type: password for the Schema Object

This was my first attempt (See #584), but it's confusing and doesn't appear to be properly supported by generator templates.

  1. Removing url encoding.

This is an easy bandaid solution, but allowing strings to embed delimeters like "#" and "?" to adres any endpoint seems undesirable. The purpose of encoding is to prevent malicious strings from silently embedding delimeters, or developers from accidentally doing it themselves.

  1. Modifying the url encoding functions to accept "(" and ")".

This requires the SDKs to fork/patch url encoding functions like form_urlencoded::byte_serialize (Rust) or Uri.EscapeDataString (C#). RFC 3986 permits this behavior when a URI scheme explicitly treats reserved characters as data:

URI producing applications should percent-encode data octets that correspond to characters in the reserved set unless these characters are specifically allowed by the URI scheme to represent data in that component.

This preserves the safety benefits of percent-encoding for all other reserved characters while making the minimal compatibility change required for the VRChat API.

  1. Use type: string and combine it with format: x-vrc-no-encode for the Schema Object to conditionally skip encoding.

This would allow overriding the templates, by disabling percent-encoding in the mustache template if dataFormat is defined. It makes the behavior explicit and allows similar problems to be addressed in the future. But, like method 2 it allows adding delimeters in an unsafe way for these values.

  1. Use a vendor extension (x-vrc-no-encode: true) as part of the Schema Object to conditionally skip encoding.

Same as method 4, but a vendor extension avoids conflating the data format with the encoding. This is better as the data format and encoding are not directly related.

  1. Break spec by defining allowReserved for a path parameter.

Same as method 5. This aligns with the direction of OpenAPI 3.2, but it is invalid in OpenAPI 3.0/3.1 and would intentionally violate the current specification.

This PR adopts option 3. It makes the smallest possible change by preserving literal parentheses during URL encoding while continuing to percent-encode all other reserved characters. The SDK could also fork the byte_serialize function, but that'd require embedding unsafe Rust in the SDK.

I tested this patch on 1.20.7 as I'm having the following issue on main:

called `Result::unwrap()` on an `Err` value: ReqwestMiddleware(Reqwest(reqwest::Error { kind: Request, url: "https://api.vrchat.cloud/api/1/auth/user", source: hyper_util::client::legacy::Error(Connect, ConnectError("invalid URL, scheme is not http")) }))

Comment thread generate.sh Outdated
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
```
@C0D3-M4513R C0D3-M4513R merged commit 673f31a into vrchatapi:main Jul 5, 2026
@C0D3-M4513R

Copy link
Copy Markdown
Collaborator

Published as 1.20.8-nightly.17.1 - https://crates.io/crates/vrchatapi/1.20.8-nightly.17.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants