jishushell 0.4.30 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +18 -6
  4. package/apps/filebrowser-container.yaml +164 -0
  5. package/apps/ollama-binary.yaml +44 -0
  6. package/apps/ollama-with-hollama-binary.yaml +45 -1
  7. package/apps/openclaw-binary.yaml +8 -0
  8. package/apps/openclaw-container.yaml +9 -1
  9. package/apps/openclaw-with-searxng-container.yaml +4 -0
  10. package/apps/searxng-container.yaml +5 -4
  11. package/apps/weknora-container.yaml +471 -0
  12. package/dist/cli/doctor.js +144 -16
  13. package/dist/cli/doctor.js.map +1 -1
  14. package/dist/cli/panel.js.map +1 -1
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +99 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/install.js +4 -4
  19. package/dist/install.js.map +1 -1
  20. package/dist/routes/auth.js +2 -2
  21. package/dist/routes/auth.js.map +1 -1
  22. package/dist/routes/backup.js +64 -11
  23. package/dist/routes/backup.js.map +1 -1
  24. package/dist/routes/external-mounts.d.ts +17 -0
  25. package/dist/routes/external-mounts.js +73 -0
  26. package/dist/routes/external-mounts.js.map +1 -0
  27. package/dist/routes/file-mounts.d.ts +13 -0
  28. package/dist/routes/file-mounts.js +90 -0
  29. package/dist/routes/file-mounts.js.map +1 -0
  30. package/dist/routes/files-organize.d.ts +28 -0
  31. package/dist/routes/files-organize.js +167 -0
  32. package/dist/routes/files-organize.js.map +1 -0
  33. package/dist/routes/files.d.ts +31 -0
  34. package/dist/routes/files.js +321 -0
  35. package/dist/routes/files.js.map +1 -0
  36. package/dist/routes/instances.js +87 -12
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/routes/internal.d.ts +2 -0
  39. package/dist/routes/internal.js +59 -0
  40. package/dist/routes/internal.js.map +1 -0
  41. package/dist/routes/llm.js +29 -0
  42. package/dist/routes/llm.js.map +1 -1
  43. package/dist/routes/setup.js +9 -9
  44. package/dist/routes/setup.js.map +1 -1
  45. package/dist/routes/system.js +1 -1
  46. package/dist/routes/system.js.map +1 -1
  47. package/dist/routes/webdav.d.ts +17 -0
  48. package/dist/routes/webdav.js +114 -0
  49. package/dist/routes/webdav.js.map +1 -0
  50. package/dist/server.js +358 -6
  51. package/dist/server.js.map +1 -1
  52. package/dist/services/agent-apps/catalog.d.ts +3 -0
  53. package/dist/services/agent-apps/catalog.js +40 -13
  54. package/dist/services/agent-apps/catalog.js.map +1 -1
  55. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  56. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  57. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  58. package/dist/services/agent-apps/types.d.ts +3 -0
  59. package/dist/services/app/app-compiler.d.ts +1 -1
  60. package/dist/services/app/app-compiler.js +5 -5
  61. package/dist/services/app/app-compiler.js.map +1 -1
  62. package/dist/services/app/app-manager.d.ts +9 -0
  63. package/dist/services/app/app-manager.js +248 -43
  64. package/dist/services/app/app-manager.js.map +1 -1
  65. package/dist/services/app/custom-manager.js.map +1 -1
  66. package/dist/services/app/hermes-agent-manager.js +1 -0
  67. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  68. package/dist/services/app/ollama-manager.js +1 -1
  69. package/dist/services/app/ollama-manager.js.map +1 -1
  70. package/dist/services/app/openclaw-manager.js +37 -5
  71. package/dist/services/app/openclaw-manager.js.map +1 -1
  72. package/dist/services/app/platform-transform.d.ts +32 -0
  73. package/dist/services/app/platform-transform.js +65 -0
  74. package/dist/services/app/platform-transform.js.map +1 -0
  75. package/dist/services/app-passwords.d.ts +61 -0
  76. package/dist/services/app-passwords.js +173 -0
  77. package/dist/services/app-passwords.js.map +1 -0
  78. package/dist/services/backup-manager.d.ts +11 -0
  79. package/dist/services/backup-manager.js +220 -8
  80. package/dist/services/backup-manager.js.map +1 -1
  81. package/dist/services/capability-endpoint-validator.js +26 -7
  82. package/dist/services/capability-endpoint-validator.js.map +1 -1
  83. package/dist/services/connection-apply.d.ts +2 -0
  84. package/dist/services/connection-apply.js +55 -1
  85. package/dist/services/connection-apply.js.map +1 -1
  86. package/dist/services/connection-resolver.js +1 -1
  87. package/dist/services/connection-resolver.js.map +1 -1
  88. package/dist/services/connection-transactor.d.ts +2 -0
  89. package/dist/services/connection-transactor.js +12 -2
  90. package/dist/services/connection-transactor.js.map +1 -1
  91. package/dist/services/external-mounts.d.ts +40 -0
  92. package/dist/services/external-mounts.js +187 -0
  93. package/dist/services/external-mounts.js.map +1 -0
  94. package/dist/services/files-manager.d.ts +252 -0
  95. package/dist/services/files-manager.js +1075 -0
  96. package/dist/services/files-manager.js.map +1 -0
  97. package/dist/services/files-mounts.d.ts +42 -0
  98. package/dist/services/files-mounts.js +207 -0
  99. package/dist/services/files-mounts.js.map +1 -0
  100. package/dist/services/instance-manager.js +90 -32
  101. package/dist/services/instance-manager.js.map +1 -1
  102. package/dist/services/llm-proxy/index.d.ts +28 -0
  103. package/dist/services/llm-proxy/index.js +76 -3
  104. package/dist/services/llm-proxy/index.js.map +1 -1
  105. package/dist/services/llm-proxy/ssrf.js +6 -2
  106. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  107. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  108. package/dist/services/llm-proxy/validate-key.js +672 -0
  109. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  110. package/dist/services/macos-launchd.d.ts +89 -0
  111. package/dist/services/macos-launchd.js +273 -0
  112. package/dist/services/macos-launchd.js.map +1 -0
  113. package/dist/services/nomad-manager.d.ts +11 -0
  114. package/dist/services/nomad-manager.js +343 -98
  115. package/dist/services/nomad-manager.js.map +1 -1
  116. package/dist/services/organize/applier.d.ts +46 -0
  117. package/dist/services/organize/applier.js +218 -0
  118. package/dist/services/organize/applier.js.map +1 -0
  119. package/dist/services/organize/rules.d.ts +57 -0
  120. package/dist/services/organize/rules.js +286 -0
  121. package/dist/services/organize/rules.js.map +1 -0
  122. package/dist/services/organize/scanner.d.ts +50 -0
  123. package/dist/services/organize/scanner.js +366 -0
  124. package/dist/services/organize/scanner.js.map +1 -0
  125. package/dist/services/organize/store.d.ts +14 -0
  126. package/dist/services/organize/store.js +82 -0
  127. package/dist/services/organize/store.js.map +1 -0
  128. package/dist/services/panel-manager.js +40 -11
  129. package/dist/services/panel-manager.js.map +1 -1
  130. package/dist/services/process-manager.js +3 -2
  131. package/dist/services/process-manager.js.map +1 -1
  132. package/dist/services/runtime/adapters/custom.js +56 -0
  133. package/dist/services/runtime/adapters/custom.js.map +1 -1
  134. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  135. package/dist/services/runtime/adapters/hermes.js +166 -64
  136. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  137. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  138. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  139. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  140. package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
  141. package/dist/services/runtime/adapters/openclaw.js +1459 -49
  142. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  143. package/dist/services/runtime/instance.d.ts +1 -1
  144. package/dist/services/runtime/instance.js +1 -1
  145. package/dist/services/runtime/instance.js.map +1 -1
  146. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  147. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  148. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  149. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  150. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  151. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  152. package/dist/services/runtime/types.d.ts +31 -0
  153. package/dist/services/setup-manager.js +190 -68
  154. package/dist/services/setup-manager.js.map +1 -1
  155. package/dist/services/suggestions.js.map +1 -1
  156. package/dist/services/update-manager.js +32 -14
  157. package/dist/services/update-manager.js.map +1 -1
  158. package/dist/services/webdav/server.d.ts +24 -0
  159. package/dist/services/webdav/server.js +420 -0
  160. package/dist/services/webdav/server.js.map +1 -0
  161. package/dist/services/webdav/xml-builder.d.ts +73 -0
  162. package/dist/services/webdav/xml-builder.js +156 -0
  163. package/dist/services/webdav/xml-builder.js.map +1 -0
  164. package/dist/services/workspace-builder.d.ts +29 -0
  165. package/dist/services/workspace-builder.js +188 -0
  166. package/dist/services/workspace-builder.js.map +1 -0
  167. package/dist/types.d.ts +61 -0
  168. package/dist/utils/path-locks.d.ts +30 -0
  169. package/dist/utils/path-locks.js +63 -0
  170. package/dist/utils/path-locks.js.map +1 -0
  171. package/dist/utils/path-safety.d.ts +41 -0
  172. package/dist/utils/path-safety.js +119 -0
  173. package/dist/utils/path-safety.js.map +1 -0
  174. package/dist/utils/safe-write.d.ts +24 -0
  175. package/dist/utils/safe-write.js +82 -0
  176. package/dist/utils/safe-write.js.map +1 -0
  177. package/install/jishu-install.sh +247 -35
  178. package/install/jishu-uninstall.sh +45 -5
  179. package/package.json +20 -2
  180. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  181. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  182. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
  183. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  184. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  185. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  186. package/public/assets/Login-aPajuQzf.js +1 -0
  187. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  188. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  189. package/public/assets/Settings-BYQnbLYL.js +1 -0
  190. package/public/assets/Setup-D05lwDOV.js +1 -0
  191. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  192. package/public/assets/index-HSXCsceK.css +1 -0
  193. package/public/assets/index-bnBu0nlQ.js +19 -0
  194. package/public/assets/registry-C_qeFTkZ.js +2 -0
  195. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  196. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
  197. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
  198. package/public/index.html +4 -4
  199. package/scripts/check-app-spec.mjs +18 -4
  200. package/scripts/check-colima-launchd.mjs +230 -0
  201. package/scripts/check-new-file-tests.mjs +230 -0
  202. package/scripts/check-quarantine-expiry.mjs +105 -0
  203. package/scripts/perf/README.md +49 -0
  204. package/scripts/perf/auth.js +99 -0
  205. package/scripts/perf/config.js +63 -0
  206. package/scripts/perf/instances.js +143 -0
  207. package/scripts/perf/proxy.js +96 -0
  208. package/scripts/smoke/files-w1.sh +142 -0
  209. package/scripts/smoke-backend.mjs +122 -0
  210. package/scripts/smoke-post-publish.mjs +346 -0
  211. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  212. package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
  213. package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
  214. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  215. package/public/assets/Login-BItXqYAJ.js +0 -1
  216. package/public/assets/NewInstance-BousE6kY.js +0 -1
  217. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
  218. package/public/assets/Settings-Bttc6QmM.js +0 -1
  219. package/public/assets/Setup-Bsxx1zgj.js +0 -1
  220. package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
  221. package/public/assets/index-8xZy1z5k.css +0 -1
  222. package/public/assets/index-Dw3HhUYE.js +0 -19
  223. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  224. package/public/assets/providers-DtNXh9JD.js +0 -1
  225. package/public/assets/registry-5s2UB6is.js +0 -2
  226. package/public/assets/usePolling-Do5Erqm_.js +0 -1
@@ -0,0 +1,420 @@
1
+ /**
2
+ * WebDAV server logic for the AI NAS (M3 W4 PR-3).
3
+ *
4
+ * Implements a focused subset of RFC 4918 — the methods iOS Files,
5
+ * macOS Finder, Windows Explorer, and FX (Android) actually issue:
6
+ *
7
+ * OPTIONS capability advertisement
8
+ * PROPFIND list (Depth: 0 / 1)
9
+ * GET stream a file
10
+ * HEAD same headers as GET, no body
11
+ * PUT upload (octet-stream pass-through; overwrites are normal)
12
+ * DELETE soft-delete to .trash via FilesManager
13
+ * MKCOL create directory
14
+ * MOVE rename / move within the WebDAV root
15
+ * COPY shallow file copy (no recursive copy in this PR)
16
+ *
17
+ * Locking (LOCK / UNLOCK / PROPPATCH) is intentionally NOT implemented;
18
+ * we return 501 — clients fall back to optimistic concurrency.
19
+ *
20
+ * All request paths are validated through the FilesManager surface so
21
+ * we share path-safety, symlink defense, and the audit log with the
22
+ * /api/files routes. Authentication runs upstream (see routes/webdav.ts).
23
+ */
24
+ import * as fs from "node:fs";
25
+ import * as path from "node:path";
26
+ import { FilesError, } from "../files-manager.js";
27
+ import { buildMultistatus, parseDepth, } from "./xml-builder.js";
28
+ import { isAllowed } from "../app-passwords.js";
29
+ import { resolveSafe } from "../../utils/path-safety.js";
30
+ import { atomicWriteStream } from "../../utils/safe-write.js";
31
+ import { withPathLock } from "../../utils/path-locks.js";
32
+ import { FILES_ROOT } from "../../config.js";
33
+ // ── Path extraction ────────────────────────────────
34
+ /**
35
+ * Strip the mount prefix from req.url and decode each segment, then
36
+ * normalize: leading / trailing slash removed, "." collapsed. Returns
37
+ * the files/-relative path the rest of the WebDAV server uses.
38
+ */
39
+ export function extractRel(reqUrl, mountPrefix) {
40
+ const cleanPrefix = mountPrefix.replace(/\/+$/, "");
41
+ let urlPath = reqUrl;
42
+ // Strip query string — WebDAV doesn't use it
43
+ const q = urlPath.indexOf("?");
44
+ if (q >= 0)
45
+ urlPath = urlPath.slice(0, q);
46
+ if (urlPath.startsWith(cleanPrefix))
47
+ urlPath = urlPath.slice(cleanPrefix.length);
48
+ if (urlPath.startsWith("/"))
49
+ urlPath = urlPath.slice(1);
50
+ if (urlPath.endsWith("/"))
51
+ urlPath = urlPath.slice(0, -1);
52
+ // Decode each segment so `%E7%AC%94%E8%AE%B0` becomes `笔记`
53
+ return urlPath
54
+ .split("/")
55
+ .map((s) => {
56
+ try {
57
+ return decodeURIComponent(s);
58
+ }
59
+ catch {
60
+ return s;
61
+ }
62
+ })
63
+ .filter((s) => s !== "")
64
+ .join("/");
65
+ }
66
+ // ── Auth scope check ───────────────────────────────
67
+ function checkScope(ctx, rel, needsWrite) {
68
+ return isAllowed(ctx.authedAs, rel, needsWrite);
69
+ }
70
+ function send403(reply, reason) {
71
+ reply
72
+ .code(403)
73
+ .header("Content-Type", "text/plain; charset=utf-8")
74
+ .send(`forbidden: ${reason ?? "scope mismatch"}`);
75
+ }
76
+ // ── OPTIONS ────────────────────────────────────────
77
+ export async function handleOptions(_ctx, _req, reply) {
78
+ reply
79
+ .code(200)
80
+ .header("Allow", "OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, MKCOL, MOVE, COPY")
81
+ .header("DAV", "1")
82
+ .header("MS-Author-Via", "DAV") // Windows Explorer compatibility
83
+ .send();
84
+ }
85
+ // ── PROPFIND ───────────────────────────────────────
86
+ export async function handlePropfind(ctx, req, reply) {
87
+ const rel = extractRel(req.url, ctx.mountPrefix);
88
+ const scope = checkScope(ctx, rel, false);
89
+ if (!scope.allowed)
90
+ return send403(reply, scope.reason);
91
+ const depth = parseDepth(req.headers.depth);
92
+ const targetAbs = resolveSafe(FILES_ROOT, rel);
93
+ let stat;
94
+ try {
95
+ const ls = fs.lstatSync(targetAbs);
96
+ if (ls.isSymbolicLink()) {
97
+ reply.code(403).send("symlinks not exposed via WebDAV");
98
+ return;
99
+ }
100
+ stat = fs.statSync(targetAbs);
101
+ }
102
+ catch (e) {
103
+ if (e?.code === "ENOENT") {
104
+ reply.code(404).send();
105
+ return;
106
+ }
107
+ throw e;
108
+ }
109
+ const resources = [
110
+ statToResource(rel, stat, basename(rel) || "files"),
111
+ ];
112
+ if (depth >= 1 && stat.isDirectory()) {
113
+ let entries;
114
+ try {
115
+ entries = fs.readdirSync(targetAbs);
116
+ }
117
+ catch {
118
+ entries = [];
119
+ }
120
+ for (const name of entries) {
121
+ if (name.startsWith("."))
122
+ continue; // hidden / .trash never exposed
123
+ const childAbs = path.join(targetAbs, name);
124
+ let childStat;
125
+ try {
126
+ const ls = fs.lstatSync(childAbs);
127
+ if (ls.isSymbolicLink())
128
+ continue;
129
+ childStat = fs.statSync(childAbs);
130
+ }
131
+ catch {
132
+ continue;
133
+ }
134
+ const childRel = rel ? `${rel}/${name}` : name;
135
+ const childScope = checkScope(ctx, childRel, false);
136
+ if (!childScope.allowed)
137
+ continue; // hide outside-scope children
138
+ resources.push(statToResource(childRel, childStat, name));
139
+ }
140
+ }
141
+ const xml = buildMultistatus(ctx.mountPrefix, resources);
142
+ reply
143
+ .code(207)
144
+ .header("Content-Type", 'application/xml; charset="utf-8"')
145
+ .header("DAV", "1")
146
+ .send(xml);
147
+ }
148
+ function statToResource(rel, stat, displayName) {
149
+ const isDir = stat.isDirectory();
150
+ return {
151
+ pathRel: rel,
152
+ displayName,
153
+ isDir,
154
+ size: isDir ? 0 : stat.size,
155
+ mtime: Math.floor(stat.mtimeMs / 1000),
156
+ etag: `W/"${stat.size}-${Math.floor(stat.mtimeMs)}"`,
157
+ mime: isDir ? undefined : guessMime(displayName),
158
+ };
159
+ }
160
+ function basename(rel) {
161
+ if (!rel)
162
+ return "";
163
+ const idx = rel.lastIndexOf("/");
164
+ return idx === -1 ? rel : rel.slice(idx + 1);
165
+ }
166
+ const MIME_BY_EXT = {
167
+ ".txt": "text/plain; charset=utf-8",
168
+ ".md": "text/markdown; charset=utf-8",
169
+ ".json": "application/json",
170
+ ".pdf": "application/pdf",
171
+ ".jpg": "image/jpeg",
172
+ ".jpeg": "image/jpeg",
173
+ ".png": "image/png",
174
+ ".webp": "image/webp",
175
+ ".gif": "image/gif",
176
+ ".mp4": "video/mp4",
177
+ ".mov": "video/quicktime",
178
+ ".mp3": "audio/mpeg",
179
+ ".zip": "application/zip",
180
+ };
181
+ function guessMime(name) {
182
+ const ext = path.extname(name).toLowerCase();
183
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
184
+ }
185
+ // ── GET / HEAD ─────────────────────────────────────
186
+ export async function handleGet(ctx, req, reply, bodyless) {
187
+ const rel = extractRel(req.url, ctx.mountPrefix);
188
+ const scope = checkScope(ctx, rel, false);
189
+ if (!scope.allowed)
190
+ return send403(reply, scope.reason);
191
+ if (rel === "") {
192
+ reply.code(403).send("cannot GET the WebDAV root");
193
+ return;
194
+ }
195
+ try {
196
+ const r = await ctx.filesManager.readStream(rel);
197
+ reply
198
+ .header("Content-Type", r.mime)
199
+ .header("Content-Length", String(r.size))
200
+ .header("ETag", r.etag)
201
+ .header("Last-Modified", new Date(r.mtime * 1000).toUTCString())
202
+ .header("Accept-Ranges", "bytes");
203
+ if (bodyless) {
204
+ reply.code(200).send();
205
+ }
206
+ else {
207
+ reply.code(200).send(r.openStream());
208
+ }
209
+ }
210
+ catch (e) {
211
+ sendFilesError(reply, e);
212
+ }
213
+ }
214
+ // ── PUT ────────────────────────────────────────────
215
+ export async function handlePut(ctx, req, reply) {
216
+ const rel = extractRel(req.url, ctx.mountPrefix);
217
+ const scope = checkScope(ctx, rel, true);
218
+ if (!scope.allowed)
219
+ return send403(reply, scope.reason);
220
+ if (rel === "") {
221
+ reply.code(405).send("cannot PUT the WebDAV root");
222
+ return;
223
+ }
224
+ // Content-Length is mandatory for the same reason as /api/files.
225
+ const cl = req.headers["content-length"];
226
+ const clNum = typeof cl === "string" ? Number.parseInt(cl, 10) : Number.NaN;
227
+ if (!Number.isFinite(clNum) || clNum < 0) {
228
+ reply.code(411).send("Content-Length required");
229
+ return;
230
+ }
231
+ const body = req.raw;
232
+ if (!body) {
233
+ reply.code(400).send("missing body");
234
+ return;
235
+ }
236
+ try {
237
+ // WebDAV PUT default-overwrites; matches RFC 4918 §9.7.
238
+ const result = await ctx.filesManager.writeStream(rel, body, {
239
+ overwrite: true,
240
+ expectedSize: clNum,
241
+ });
242
+ // 201 if newly created, 204 if overwritten — FilesManager doesn't
243
+ // currently distinguish; default to 201 (Finder accepts both).
244
+ reply
245
+ .code(201)
246
+ .header("ETag", result.etag)
247
+ .header("Content-Length", "0")
248
+ .send();
249
+ }
250
+ catch (e) {
251
+ sendFilesError(reply, e);
252
+ }
253
+ }
254
+ // ── DELETE ─────────────────────────────────────────
255
+ export async function handleDelete(ctx, req, reply) {
256
+ const rel = extractRel(req.url, ctx.mountPrefix);
257
+ const scope = checkScope(ctx, rel, true);
258
+ if (!scope.allowed)
259
+ return send403(reply, scope.reason);
260
+ if (rel === "") {
261
+ reply.code(405).send("cannot DELETE the WebDAV root");
262
+ return;
263
+ }
264
+ try {
265
+ await ctx.filesManager.remove(rel);
266
+ reply.code(204).send();
267
+ }
268
+ catch (e) {
269
+ sendFilesError(reply, e);
270
+ }
271
+ }
272
+ // ── MKCOL ──────────────────────────────────────────
273
+ export async function handleMkcol(ctx, req, reply) {
274
+ const rel = extractRel(req.url, ctx.mountPrefix);
275
+ const scope = checkScope(ctx, rel, true);
276
+ if (!scope.allowed)
277
+ return send403(reply, scope.reason);
278
+ if (rel === "") {
279
+ reply.code(405).send("cannot MKCOL the WebDAV root");
280
+ return;
281
+ }
282
+ // RFC 4918 §9.3: MKCOL must reject a request with a body.
283
+ const cl = Number.parseInt(req.headers["content-length"], 10);
284
+ if (Number.isFinite(cl) && cl > 0) {
285
+ reply.code(415).send("MKCOL must not include a body");
286
+ return;
287
+ }
288
+ try {
289
+ await ctx.filesManager.mkdir(rel);
290
+ reply.code(201).send();
291
+ }
292
+ catch (e) {
293
+ sendFilesError(reply, e);
294
+ }
295
+ }
296
+ // ── MOVE ───────────────────────────────────────────
297
+ export async function handleMove(ctx, req, reply) {
298
+ const fromRel = extractRel(req.url, ctx.mountPrefix);
299
+ const toRel = parseDestinationHeader(req, ctx.mountPrefix);
300
+ if (toRel === null) {
301
+ reply.code(400).send("invalid Destination header");
302
+ return;
303
+ }
304
+ const fromScope = checkScope(ctx, fromRel, true);
305
+ if (!fromScope.allowed)
306
+ return send403(reply, fromScope.reason);
307
+ const toScope = checkScope(ctx, toRel, true);
308
+ if (!toScope.allowed)
309
+ return send403(reply, toScope.reason);
310
+ const overwrite = String(req.headers.overwrite ?? "T")
311
+ .trim()
312
+ .toUpperCase() !== "F";
313
+ try {
314
+ await ctx.filesManager.move(fromRel, toRel, overwrite);
315
+ // 201 if the destination was newly created, 204 if overwritten.
316
+ // Without knowing pre-state we default to 201 (clients accept it).
317
+ reply.code(201).header("Content-Length", "0").send();
318
+ }
319
+ catch (e) {
320
+ sendFilesError(reply, e);
321
+ }
322
+ }
323
+ // ── COPY ───────────────────────────────────────────
324
+ export async function handleCopy(ctx, req, reply) {
325
+ const fromRel = extractRel(req.url, ctx.mountPrefix);
326
+ const toRel = parseDestinationHeader(req, ctx.mountPrefix);
327
+ if (toRel === null) {
328
+ reply.code(400).send("invalid Destination header");
329
+ return;
330
+ }
331
+ const fromScope = checkScope(ctx, fromRel, false);
332
+ if (!fromScope.allowed)
333
+ return send403(reply, fromScope.reason);
334
+ const toScope = checkScope(ctx, toRel, true);
335
+ if (!toScope.allowed)
336
+ return send403(reply, toScope.reason);
337
+ const overwrite = String(req.headers.overwrite ?? "T")
338
+ .trim()
339
+ .toUpperCase() !== "F";
340
+ // For W4 we only support file copy (not recursive directory copy).
341
+ // Finder relies primarily on MOVE; recursive COPY is rare. Returning
342
+ // 502 Bad Gateway with a clear body lets clients fall back.
343
+ const fromAbs = resolveSafe(FILES_ROOT, fromRel);
344
+ let stat;
345
+ try {
346
+ stat = fs.statSync(fromAbs);
347
+ }
348
+ catch {
349
+ reply.code(404).send();
350
+ return;
351
+ }
352
+ if (stat.isDirectory()) {
353
+ reply
354
+ .code(502)
355
+ .send("recursive directory COPY is not supported in this build");
356
+ return;
357
+ }
358
+ const toAbs = resolveSafe(FILES_ROOT, toRel);
359
+ try {
360
+ if (!overwrite && fs.existsSync(toAbs)) {
361
+ reply.code(412).send();
362
+ return;
363
+ }
364
+ fs.mkdirSync(path.dirname(toAbs), { recursive: true });
365
+ // Stream copy via atomicWriteStream so we get tmp+rename atomicity
366
+ await withPathLock(toAbs, async () => {
367
+ await atomicWriteStream(toAbs, fs.createReadStream(fromAbs));
368
+ });
369
+ reply.code(201).header("Content-Length", "0").send();
370
+ }
371
+ catch (e) {
372
+ sendFilesError(reply, e);
373
+ }
374
+ }
375
+ function parseDestinationHeader(req, mountPrefix) {
376
+ const raw = req.headers.destination;
377
+ if (typeof raw !== "string" || !raw)
378
+ return null;
379
+ // Destination may be a full URL (https://host/webdav/x) or a path-only
380
+ // value. We strip everything up to and including the mount prefix.
381
+ let dest = raw;
382
+ try {
383
+ const u = new URL(raw);
384
+ dest = u.pathname;
385
+ }
386
+ catch {
387
+ // Not a full URL — assume path-only
388
+ }
389
+ const cleanPrefix = mountPrefix.replace(/\/+$/, "");
390
+ if (!dest.startsWith(cleanPrefix))
391
+ return null;
392
+ let rest = dest.slice(cleanPrefix.length);
393
+ if (rest.startsWith("/"))
394
+ rest = rest.slice(1);
395
+ if (rest.endsWith("/"))
396
+ rest = rest.slice(0, -1);
397
+ return rest
398
+ .split("/")
399
+ .map((s) => {
400
+ try {
401
+ return decodeURIComponent(s);
402
+ }
403
+ catch {
404
+ return s;
405
+ }
406
+ })
407
+ .filter((s) => s !== "")
408
+ .join("/");
409
+ }
410
+ // ── Error mapping ──────────────────────────────────
411
+ function sendFilesError(reply, e) {
412
+ if (e instanceof FilesError) {
413
+ reply.code(e.httpStatus).send(e.message);
414
+ return;
415
+ }
416
+ const err = e;
417
+ console.error("[webdav] unexpected:", err.message);
418
+ reply.code(500).send(err.message ?? "internal error");
419
+ }
420
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/services/webdav/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * WebDAV XML serialization (M3 W4 PR-2).
3
+ *
4
+ * We serialize a tiny subset of RFC 4918 PROPFIND multistatus responses
5
+ * — exactly what iOS Files / macOS Finder / Windows Explorer / WinSCP
6
+ * actually inspect. We deliberately avoid pulling an XML library:
7
+ * outputs are template-driven and the surface is < 200 lines.
8
+ *
9
+ * Properties supported per resource:
10
+ * D:displayname
11
+ * D:resourcetype (<D:collection/> for dirs, empty for files)
12
+ * D:getcontentlength (files only)
13
+ * D:getlastmodified (RFC 1123)
14
+ * D:creationdate (ISO 8601 with timezone)
15
+ * D:getetag (weak, "W/\"size-mtime\"")
16
+ * D:getcontenttype (files only)
17
+ * D:supportedlock (empty — we do not implement locking)
18
+ * D:lockdiscovery (empty)
19
+ *
20
+ * Properties NOT supported (clients tolerate missing): D:quota-*,
21
+ * D:executable, D:source — return 404 in propstat if explicitly asked.
22
+ *
23
+ * Caller is responsible for path safety on `href` — we URL-encode but
24
+ * do not validate the path is inside the WebDAV root.
25
+ */
26
+ export interface DavResource {
27
+ /**
28
+ * Path relative to the WebDAV root, WITHOUT the leading `/webdav`
29
+ * mount prefix. For the root itself pass `""`. For a directory
30
+ * `notes`, pass `"notes"` — the builder appends a trailing slash
31
+ * for collections automatically.
32
+ */
33
+ pathRel: string;
34
+ displayName: string;
35
+ isDir: boolean;
36
+ size: number;
37
+ /** Unix seconds. */
38
+ mtime: number;
39
+ /** Already-formed weak ETag, e.g. `W/"123-456789"`. */
40
+ etag: string;
41
+ /** Files only; ignored for directories. */
42
+ mime?: string;
43
+ }
44
+ /**
45
+ * Build a complete <D:multistatus> XML document for a PROPFIND.
46
+ *
47
+ * @param mountPrefix the absolute URL prefix the client used to reach
48
+ * us, e.g. `/webdav` (must NOT end in `/`)
49
+ * @param resources ordered list; the first entry should be the
50
+ * target resource itself (Depth: 0 returns only
51
+ * this), followed by children (Depth: 1)
52
+ */
53
+ export declare function buildMultistatus(mountPrefix: string, resources: DavResource[]): string;
54
+ /**
55
+ * Build a `<D:multistatus>` for a single 404 — used when MOVE/COPY
56
+ * destination doesn't exist, or PROPFIND on missing path.
57
+ */
58
+ export declare function buildNotFoundMultistatus(mountPrefix: string, pathRel: string): string;
59
+ export declare function escapeXml(s: string): string;
60
+ /**
61
+ * Encode one path segment per RFC 3986. Path-segment-safe, but
62
+ * percent-encodes anything outside the allowed set so spaces, CJK
63
+ * characters etc. survive transit.
64
+ */
65
+ export declare function encodePathSegment(seg: string): string;
66
+ export declare function rfc1123(unixSec: number): string;
67
+ export declare function iso8601(unixSec: number): string;
68
+ /**
69
+ * Parse a Depth header. Returns 0, 1, or "infinity"; defaults to
70
+ * infinity per RFC 4918, but we cap at 1 because directory walks at
71
+ * arbitrary depth are a DOS surface.
72
+ */
73
+ export declare function parseDepth(header: string | undefined): 0 | 1;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * WebDAV XML serialization (M3 W4 PR-2).
3
+ *
4
+ * We serialize a tiny subset of RFC 4918 PROPFIND multistatus responses
5
+ * — exactly what iOS Files / macOS Finder / Windows Explorer / WinSCP
6
+ * actually inspect. We deliberately avoid pulling an XML library:
7
+ * outputs are template-driven and the surface is < 200 lines.
8
+ *
9
+ * Properties supported per resource:
10
+ * D:displayname
11
+ * D:resourcetype (<D:collection/> for dirs, empty for files)
12
+ * D:getcontentlength (files only)
13
+ * D:getlastmodified (RFC 1123)
14
+ * D:creationdate (ISO 8601 with timezone)
15
+ * D:getetag (weak, "W/\"size-mtime\"")
16
+ * D:getcontenttype (files only)
17
+ * D:supportedlock (empty — we do not implement locking)
18
+ * D:lockdiscovery (empty)
19
+ *
20
+ * Properties NOT supported (clients tolerate missing): D:quota-*,
21
+ * D:executable, D:source — return 404 in propstat if explicitly asked.
22
+ *
23
+ * Caller is responsible for path safety on `href` — we URL-encode but
24
+ * do not validate the path is inside the WebDAV root.
25
+ */
26
+ /**
27
+ * Build a complete <D:multistatus> XML document for a PROPFIND.
28
+ *
29
+ * @param mountPrefix the absolute URL prefix the client used to reach
30
+ * us, e.g. `/webdav` (must NOT end in `/`)
31
+ * @param resources ordered list; the first entry should be the
32
+ * target resource itself (Depth: 0 returns only
33
+ * this), followed by children (Depth: 1)
34
+ */
35
+ export function buildMultistatus(mountPrefix, resources) {
36
+ const responses = resources.map((r) => buildResponse(mountPrefix, r)).join("");
37
+ return (`<?xml version="1.0" encoding="utf-8"?>\n` +
38
+ `<D:multistatus xmlns:D="DAV:">${responses}</D:multistatus>\n`);
39
+ }
40
+ function buildResponse(mountPrefix, r) {
41
+ const href = buildHref(mountPrefix, r);
42
+ const propLines = [
43
+ `<D:displayname>${escapeXml(r.displayName)}</D:displayname>`,
44
+ r.isDir
45
+ ? `<D:resourcetype><D:collection/></D:resourcetype>`
46
+ : `<D:resourcetype/>`,
47
+ `<D:getlastmodified>${rfc1123(r.mtime)}</D:getlastmodified>`,
48
+ `<D:creationdate>${iso8601(r.mtime)}</D:creationdate>`,
49
+ `<D:getetag>${escapeXml(r.etag)}</D:getetag>`,
50
+ `<D:supportedlock/>`,
51
+ `<D:lockdiscovery/>`,
52
+ ];
53
+ if (!r.isDir) {
54
+ propLines.push(`<D:getcontentlength>${r.size}</D:getcontentlength>`);
55
+ if (r.mime) {
56
+ propLines.push(`<D:getcontenttype>${escapeXml(r.mime)}</D:getcontenttype>`);
57
+ }
58
+ }
59
+ return (`<D:response>` +
60
+ `<D:href>${href}</D:href>` +
61
+ `<D:propstat>` +
62
+ `<D:prop>${propLines.join("")}</D:prop>` +
63
+ `<D:status>HTTP/1.1 200 OK</D:status>` +
64
+ `</D:propstat>` +
65
+ `</D:response>`);
66
+ }
67
+ /**
68
+ * URL-encode each path segment (NOT the slashes between them) and
69
+ * append a trailing slash for collections — required by RFC 4918 so
70
+ * Finder / iOS Files render the icon as a directory rather than a
71
+ * zero-byte file.
72
+ */
73
+ function buildHref(mountPrefix, r) {
74
+ const cleanPrefix = mountPrefix.replace(/\/+$/, "");
75
+ const segs = (r.pathRel || "").split("/").filter(Boolean);
76
+ const encoded = segs.map(encodePathSegment).join("/");
77
+ let href = encoded ? `${cleanPrefix}/${encoded}` : `${cleanPrefix}/`;
78
+ if (r.isDir && !href.endsWith("/"))
79
+ href += "/";
80
+ return href;
81
+ }
82
+ /**
83
+ * Build a `<D:multistatus>` for a single 404 — used when MOVE/COPY
84
+ * destination doesn't exist, or PROPFIND on missing path.
85
+ */
86
+ export function buildNotFoundMultistatus(mountPrefix, pathRel) {
87
+ const href = encodeURI(`${mountPrefix.replace(/\/+$/, "")}/${pathRel}`);
88
+ return (`<?xml version="1.0" encoding="utf-8"?>\n` +
89
+ `<D:multistatus xmlns:D="DAV:">` +
90
+ `<D:response>` +
91
+ `<D:href>${escapeXml(href)}</D:href>` +
92
+ `<D:status>HTTP/1.1 404 Not Found</D:status>` +
93
+ `</D:response>` +
94
+ `</D:multistatus>\n`);
95
+ }
96
+ // ── Helpers ────────────────────────────────────────
97
+ const XML_ESCAPES = {
98
+ "&": "&amp;",
99
+ "<": "&lt;",
100
+ ">": "&gt;",
101
+ '"': "&quot;",
102
+ "'": "&apos;",
103
+ };
104
+ export function escapeXml(s) {
105
+ return s.replace(/[&<>"']/g, (c) => XML_ESCAPES[c] ?? c);
106
+ }
107
+ /**
108
+ * Encode one path segment per RFC 3986. Path-segment-safe, but
109
+ * percent-encodes anything outside the allowed set so spaces, CJK
110
+ * characters etc. survive transit.
111
+ */
112
+ export function encodePathSegment(seg) {
113
+ // encodeURIComponent encodes too aggressively (escapes /, ?, #) but
114
+ // for a single segment that's exactly what we want — we then assemble
115
+ // segments with literal `/` ourselves.
116
+ return encodeURIComponent(seg)
117
+ .replace(/'/g, "%27")
118
+ .replace(/\(/g, "%28")
119
+ .replace(/\)/g, "%29");
120
+ }
121
+ const RFC1123_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
122
+ const RFC1123_MONTHS = [
123
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
124
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
125
+ ];
126
+ export function rfc1123(unixSec) {
127
+ const d = new Date(unixSec * 1000);
128
+ const dayName = RFC1123_DAYS[d.getUTCDay()];
129
+ const day = String(d.getUTCDate()).padStart(2, "0");
130
+ const monthName = RFC1123_MONTHS[d.getUTCMonth()];
131
+ const year = d.getUTCFullYear();
132
+ const hh = String(d.getUTCHours()).padStart(2, "0");
133
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
134
+ const ss = String(d.getUTCSeconds()).padStart(2, "0");
135
+ return `${dayName}, ${day} ${monthName} ${year} ${hh}:${mm}:${ss} GMT`;
136
+ }
137
+ export function iso8601(unixSec) {
138
+ return new Date(unixSec * 1000).toISOString();
139
+ }
140
+ /**
141
+ * Parse a Depth header. Returns 0, 1, or "infinity"; defaults to
142
+ * infinity per RFC 4918, but we cap at 1 because directory walks at
143
+ * arbitrary depth are a DOS surface.
144
+ */
145
+ export function parseDepth(header) {
146
+ if (header === undefined || header === null)
147
+ return 1;
148
+ const s = String(header).trim().toLowerCase();
149
+ if (s === "0")
150
+ return 0;
151
+ if (s === "1")
152
+ return 1;
153
+ // "infinity" defaults to 1 — we do not walk arbitrary depth
154
+ return 1;
155
+ }
156
+ //# sourceMappingURL=xml-builder.js.map