snet

my 2nd stalwart and precise bugreport

Aug 19th, 2026 (edited)
37
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.18 KB | None | 0 0
  1. **Title:** WebDAV PROPFIND `<D:href>` is not percent-encoded for FileNode objects created via JMAP, breaking WebDAV clients (spec violation of RFC 3986)
  2.  
  3. ## Summary
  4. When a file/folder (`FileNode`) is created through the **JMAP** API (`FileNode/set`), the resulting object's `<D:href>` in subsequent WebDAV `PROPFIND` responses contains the **raw, unencoded name** (including literal spaces and raw UTF-8 bytes for non-ASCII characters) instead of a properly percent-encoded path segment.
  5.  
  6. Objects created directly via **WebDAV** (`MKCOL`/`PUT`) are, by contrast, always correctly percent-encoded in their `href`.
  7.  
  8. This produces a `PROPFIND` response containing an invalid URI (per RFC 3986, spaces and non-ASCII bytes in a URI path must be percent-encoded), which appears to cause spec-compliant WebDAV clients to silently fail to display or access the affected resource.
  9.  
  10. Note: this only manifests for names containing characters that require percent-encoding (spaces, non-ASCII characters, etc.). A JMAP-created object with a plain alphanumeric name (nothing to encode) displays and behaves correctly in WebDAV clients, which is consistent with the root cause being a missing encoding step specifically, rather than JMAP-created objects being broken in WebDAV generally.
  11.  
  12. ## Reproduction steps
  13. 1. Authenticate to a Stalwart account with JMAP access
  14. 2. Create a folder via JMAP `FileNode/set`, e.g.:
  15. ```json
  16. {
  17. "using": [
  18. "urn:ietf:params:jmap:core",
  19. "urn:ietf:params:jmap:filenode",
  20. "urn:ietf:params:jmap:principals:owner"
  21. ],
  22. "methodCalls": [[
  23. "FileNode/set",
  24. {
  25. "accountId": "f",
  26. "create": { "dir0": { "name": "builtin fest folder" } }
  27. },
  28. "fns0"
  29. ]]
  30. }
  31. ```
  32. 3. Server responds with success and a real object id:
  33. ```json
  34. {
  35. "methodResponses": [[
  36. "FileNode/set",
  37. {
  38. "accountId": "f",
  39. "oldState": "s0ewq",
  40. "newState": "s0iwq",
  41. "created": { "dir0": { "id": "b7z" } }
  42. },
  43. "fns0"
  44. ]]
  45. }
  46. ```
  47. 4. Immediately after, query the same account's file storage via WebDAV:
  48. ```bash
  49. curl -u "[email protected]:password" -X PROPFIND \
  50. "https://mail.example.com/dav/file/[email protected]/" \
  51. -H "Depth: 1"
  52. ```
  53. 5. The response includes the newly created folder with an **unencoded space** in the href:
  54. ```xml
  55. <D:href>/dav/file/user%40domain.com/builtin fest folder/</D:href>
  56. ```
  57. Note the account name itself is correctly encoded (`%40` for `@`), but the folder name segment is not (`builtin fest folder` should be `builtin%20fest%20folder`).
  58.  
  59. ## Second reproduction (non-ASCII characters)
  60. The same behavior was independently reproduced with a folder created with an Arabic name via the same JMAP flow:
  61. ```xml
  62. <D:href>/dav/file/user%40domain.com/فثسف يسنتا سنتاي/</D:href>
  63. ```
  64. Here, the raw UTF-8 bytes are embedded directly in the href rather than being percent-encoded (expected: `%D9%81%D8%AB%D8%B3%D9%81...` etc., consistent with how every WebDAV-created resource with non-ASCII characters is correctly encoded elsewhere in the same account).
  65.  
  66. ## Control: WebDAV-created objects in the same account are correctly encoded, ruling out a timing/indexing-delay explanation
  67. To rule out the possibility that this is simply an indexing delay (i.e., that hrefs are eventually re-encoded correctly after some background process runs), a fresh WebDAV `MKCOL` with a space in the name was created and checked **immediately**, using the same account and the same timing as the JMAP test above:
  68. ```bash
  69. curl -u "[email protected]:password" -X MKCOL \
  70. "https://mail.example.com/dav/file/[email protected]/webdav%20fresh%20test/"
  71. # -> 201 Created
  72.  
  73. curl -u "[email protected]:password" -X PROPFIND \
  74. "https://mail.example.com/dav/file/[email protected]/" -H "Depth: 1"
  75. ```
  76. Result — correctly encoded immediately, no delay:
  77. ```xml
  78. <D:href>/dav/file/user%40domain.com/webdav%20fresh%20test/</D:href>
  79. ```
  80. This confirms the variable is genuinely the **creation path** (JMAP vs. WebDAV), not elapsed time or a background reindex — the WebDAV-created object with a space is encoded correctly the instant it's queried, while the JMAP-created object with a space is not, under identical timing conditions.
  81.  
  82. For further comparison, a folder created via a standard WebDAV `MKCOL` request without special characters in the same account:
  83. ```bash
  84. curl -u "[email protected]:password" -X MKCOL \
  85. "https://mail.example.com/dav/file/[email protected]/test-folder-curl/"
  86. ```
  87. returns a properly encoded href on subsequent PROPFIND:
  88. ```xml
  89. <D:href>/dav/file/user%40domain.com/test-folder-curl/</D:href>
  90. ```
  91. And every one of ~1,800 files/folders migrated into this same account via a third-party WebDAV client (rclone) — including many with spaces and Arabic names — are all correctly percent-encoded, e.g.:
  92. ```xml
  93. <D:href>/dav/file/user%40domain.com/eSpare%20Catalog/</D:href>
  94. <D:href>/dav/file/user%40domain.com/%D8%AC%D8%AF%D9%88%D9%84%20%D9%85%D8%AA%D8%A7%D8%A8%D8%B9%D8%A9%20%D8%B9%D9%85%D9%84%D8%A7%D8%A1.xlsx</D:href>
  95. ```
  96. This confirms the encoding logic differs specifically based on the **creation path** (JMAP vs. WebDAV), not on the presence of special characters generally.
  97.  
  98. ## Downstream impact observed
  99. 1. **`Depth: infinity` PROPFIND traversal appears to break** at or around the malformed href — a recursive listing that should include the JMAP-created folder does not surface it, while a `Depth: 1` listing on the immediate parent does include it (with the malformed href).
  100. 2. **WinSCP** (tested as a standards-compliant WebDAV client control) does not display the JMAP-created folder in its listing at all, while it correctly displays every WebDAV-created resource in the same directory, including ones with spaces and Arabic names.
  101. 3. This is suspected to be the underlying cause of a related issue observed in a JMAP-based webmail client (Bulwark Webmail): objects created via JMAP `FileNode/set` return a successful response with a real object id and an advanced `state`, but the client's subsequent listing/refresh (which appears to rely on WebDAV `PROPFIND`, or shares the same href-serialization code path) never displays the newly created object, even after manual refresh, navigation, or a full page reload.
  102.  
  103. ## Suggested fix
  104. When serializing a `FileNode`'s path into a WebDAV `<D:href>`, the server should percent-encode each path segment consistently, regardless of whether the underlying object was created via the JMAP or WebDAV API. The encoding logic used for WebDAV-created resources (which is correct) should be applied uniformly to all resources when generating `PROPFIND`/other WebDAV responses.
  105.  
  106. ## Environment
  107. - Stalwart Mail Server version: **v0.16.18** (also reproduced on v0.16.11 prior to upgrading, ruling out a version-specific regression)
  108. - Deployment: Docker (`stalwartlabs/stalwart:v0.16`)
  109. - Storage backend: default (RocksDB)
  110. - File access path tested: `/dav/file/{account}`
  111. - Client used for JMAP creation: Bulwark Webmail v1.8.1 (browser DevTools Network tab used to capture the exact JMAP request/response shown above)
  112. - Client used for WebDAV verification: `curl` (raw PROPFIND) and WinSCP (independent GUI WebDAV client)
Tags: bug
Advertisement
Add Comment
Please, Sign In to add comment