Skip to content

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

Closed
Powerbyte7 wants to merge 1 commit into
vrchatapi:mainfrom
Powerbyte7:fix-encoding
Closed

Avoid percent-encoding for "(" and ")" characters.#37
Powerbyte7 wants to merge 1 commit into
vrchatapi:mainfrom
Powerbyte7:fix-encoding

Conversation

@Powerbyte7

Copy link
Copy Markdown

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
{"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

See vrchatapi/vrchatapi-rust#43 for more details.

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
```
binn added a commit that referenced this pull request Sep 3, 2026
Two fixes that the template restructuring in the previous commit makes
straightforward.

Parentheses (#37, by @Powerbyte7)

  VRChat answers 400 "malformed url" when "(" and ")" arrive percent-encoded,
  and instance IDs contain them:

    wrld_0000:12345~group(grp_0000)~groupAccessType(plus)~region(use)

  #37 restored them with a sed over the generated WebRequestPathBuilder.cs.
  That file comes from templates/WebRequestPathBuilder.mustache, so the fix
  goes there instead, behind one Escape helper used by both the path and query
  paths rather than a chained Replace at each call site.

  Un-escaping is safe because Uri.EscapeDataString encodes "%" first, so a
  literal "%28" in a value becomes "%2528" and cannot be corrupted.

XML docs (#32)

  Twelve VRChatClientBuilder members carried empty <summary>, <param> and
  <returns> tags. These do not raise CS1591, because a tag is present, so they
  survived the warning sweep while being just as useless to a caller. Written
  out, including what WithApplication is for and why VRChat wants it.

  While writing them: WithAuthCookie set the twoFactorAuth key from the auth
  argument. Two-factor cookies passed to it were discarded and the auth token
  was stored twice.

  Left alone, because it changes authentication behaviour and cannot be
  verified without a live account: WithAuthCookie stores cookie values with
  AddApiKeyPrefix rather than AddApiKey, so GetApiKeyWithPrefix returns
  "<token> ", the token with a trailing space, since no key value is ever set.

Regenerated and rebuilt: still 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpDekr34WgDkSS5myqf8m9
@binn

binn commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Thanks for tracking this down @Powerbyte7 — the repro in the description made it easy to confirm.

Your fix is incorporated into #39, with credit. The only change is where it lives: src/VRChat.API/Client/WebRequestPathBuilder.cs is generated from templates/WebRequestPathBuilder.mustache, so #39 puts it in the template rather than a sed over the output. That PR removes 17 of the 19 post-generation seds for the same reason — each one keys on a string the generator may rename, and sed exits 0 when it matches nothing, so a stale one fails silently.

Both call sites now go through one helper:

private static string Escape(string value)
{
    return Uri.EscapeDataString(value).Replace("%28", "(").Replace("%29", ")");
}

Worth noting for anyone reading later: this is safe precisely because Uri.EscapeDataString encodes % first, so a literal %28 inside a value becomes %2528 and the replacement cannot corrupt it.

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