Issue Description
Stalwart returns urn:ietf:params:jmap:error:notRequest (HTTP 400) for an otherwise-valid JMAP request whenever the method invocation name — the first element of a methodCalls triple — contains a JSON-escaped forward slash (\/) instead of a literal /.
Per RFC 8259 §7, \/ is an explicitly permitted escape for the solidus (U+002F). A JSON parser must treat "Core\/echo" and "Core/echo" as the identical string Core/echo. Stalwart accepts \/ inside JSON values but rejects it in a method name, which indicates the method name is being matched before (or without) applying JSON string unescaping to that token.
Expected Behavior
"Core\/echo" and "Core/echo" are the same JSON string per RFC 8259 §7, so the escaped form should behave identically to the unescaped one: HTTP 200 with the echoed response.
Actual Behavior
The request is rejected with HTTP 400 urn:ietf:params:jmap:error:notRequest, treating the entire otherwise-valid request as malformed. The same \/ escape inside an argument value decodes correctly — only the method-name token is matched without unescaping.
Reproduction Steps
Core/echo is a safe no-op. The three requests differ by exactly one character; credential redacted as USER:PASS.
-
Literal
/in the method name → 200 OK:
curl -s -u ‘USER:PASS’ -H ‘Content-Type: application/json’ https://HOST/jmap/ --data-binary ‘{“using”:[“urn:ietf:params:jmap:core”],“methodCalls”:[[“Core/echo”,{“hello”:“world”},“c1”]]}’
→ {“methodResponses”:[[“Core/echo”,{“hello”:“world”},“c1”]],…} -
JSON-escaped
\/in the method NAME → 400 notRequest (the bug):
…methodCalls":[[“Core/echo”,{“hello”:“world”},“c1”]]}’
→ HTTP 400 {“type”:“urn:ietf:params:jmap:error:notRequest”,“status”:400,…} -
JSON-escaped
\/in an argument VALUE → 200 OK (decoded fine):
…methodCalls":[[“Core/echo”,{“path”:“a/b”},“c1”]]}’
→ {“methodResponses”:[[“Core/echo”,{“path”:“a/b”},“c1”]],…} (the / became /)
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
Real-world impact: Apple’s Foundation JSONSerialization always escapes / as \/ with no opt-out, so any JMAP client built on it (a great deal of Swift/Objective-C code) is rejected on every request, while JSONEncoder / Python json.dumps / Go encoding/json are unaffected — which makes this confusing to diagnose across clients.
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on