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.
- package/Dockerfile.hermes-slim +2 -5
- package/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +18 -6
- package/apps/filebrowser-container.yaml +164 -0
- package/apps/ollama-binary.yaml +44 -0
- package/apps/ollama-with-hollama-binary.yaml +45 -1
- package/apps/openclaw-binary.yaml +8 -0
- package/apps/openclaw-container.yaml +9 -1
- package/apps/openclaw-with-searxng-container.yaml +4 -0
- package/apps/searxng-container.yaml +5 -4
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/doctor.js +144 -16
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/panel.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +99 -1
- package/dist/config.js.map +1 -1
- package/dist/install.js +4 -4
- package/dist/install.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +64 -11
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +17 -0
- package/dist/routes/external-mounts.js +73 -0
- package/dist/routes/external-mounts.js.map +1 -0
- package/dist/routes/file-mounts.d.ts +13 -0
- package/dist/routes/file-mounts.js +90 -0
- package/dist/routes/file-mounts.js.map +1 -0
- package/dist/routes/files-organize.d.ts +28 -0
- package/dist/routes/files-organize.js +167 -0
- package/dist/routes/files-organize.js.map +1 -0
- package/dist/routes/files.d.ts +31 -0
- package/dist/routes/files.js +321 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/instances.js +87 -12
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/internal.d.ts +2 -0
- package/dist/routes/internal.js +59 -0
- package/dist/routes/internal.js.map +1 -0
- package/dist/routes/llm.js +29 -0
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +9 -9
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +1 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +17 -0
- package/dist/routes/webdav.js +114 -0
- package/dist/routes/webdav.js.map +1 -0
- package/dist/server.js +358 -6
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.d.ts +3 -0
- package/dist/services/agent-apps/catalog.js +40 -13
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +19 -2
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +3 -0
- package/dist/services/app/app-compiler.d.ts +1 -1
- package/dist/services/app/app-compiler.js +5 -5
- package/dist/services/app/app-compiler.js.map +1 -1
- package/dist/services/app/app-manager.d.ts +9 -0
- package/dist/services/app/app-manager.js +248 -43
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/custom-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +1 -0
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/ollama-manager.js +1 -1
- package/dist/services/app/ollama-manager.js.map +1 -1
- package/dist/services/app/openclaw-manager.js +37 -5
- package/dist/services/app/openclaw-manager.js.map +1 -1
- package/dist/services/app/platform-transform.d.ts +32 -0
- package/dist/services/app/platform-transform.js +65 -0
- package/dist/services/app/platform-transform.js.map +1 -0
- package/dist/services/app-passwords.d.ts +61 -0
- package/dist/services/app-passwords.js +173 -0
- package/dist/services/app-passwords.js.map +1 -0
- package/dist/services/backup-manager.d.ts +11 -0
- package/dist/services/backup-manager.js +220 -8
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/capability-endpoint-validator.js +26 -7
- package/dist/services/capability-endpoint-validator.js.map +1 -1
- package/dist/services/connection-apply.d.ts +2 -0
- package/dist/services/connection-apply.js +55 -1
- package/dist/services/connection-apply.js.map +1 -1
- package/dist/services/connection-resolver.js +1 -1
- package/dist/services/connection-resolver.js.map +1 -1
- package/dist/services/connection-transactor.d.ts +2 -0
- package/dist/services/connection-transactor.js +12 -2
- package/dist/services/connection-transactor.js.map +1 -1
- package/dist/services/external-mounts.d.ts +40 -0
- package/dist/services/external-mounts.js +187 -0
- package/dist/services/external-mounts.js.map +1 -0
- package/dist/services/files-manager.d.ts +252 -0
- package/dist/services/files-manager.js +1075 -0
- package/dist/services/files-manager.js.map +1 -0
- package/dist/services/files-mounts.d.ts +42 -0
- package/dist/services/files-mounts.js +207 -0
- package/dist/services/files-mounts.js.map +1 -0
- package/dist/services/instance-manager.js +90 -32
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +28 -0
- package/dist/services/llm-proxy/index.js +76 -3
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +6 -2
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/llm-proxy/validate-key.d.ts +41 -0
- package/dist/services/llm-proxy/validate-key.js +672 -0
- package/dist/services/llm-proxy/validate-key.js.map +1 -0
- package/dist/services/macos-launchd.d.ts +89 -0
- package/dist/services/macos-launchd.js +273 -0
- package/dist/services/macos-launchd.js.map +1 -0
- package/dist/services/nomad-manager.d.ts +11 -0
- package/dist/services/nomad-manager.js +343 -98
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/organize/applier.d.ts +46 -0
- package/dist/services/organize/applier.js +218 -0
- package/dist/services/organize/applier.js.map +1 -0
- package/dist/services/organize/rules.d.ts +57 -0
- package/dist/services/organize/rules.js +286 -0
- package/dist/services/organize/rules.js.map +1 -0
- package/dist/services/organize/scanner.d.ts +50 -0
- package/dist/services/organize/scanner.js +366 -0
- package/dist/services/organize/scanner.js.map +1 -0
- package/dist/services/organize/store.d.ts +14 -0
- package/dist/services/organize/store.js +82 -0
- package/dist/services/organize/store.js.map +1 -0
- package/dist/services/panel-manager.js +40 -11
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/process-manager.js +3 -2
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/custom.js +56 -0
- package/dist/services/runtime/adapters/custom.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +4 -3
- package/dist/services/runtime/adapters/hermes.js +166 -64
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
- package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
- package/dist/services/runtime/adapters/openclaw.js +1459 -49
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/instance.d.ts +1 -1
- package/dist/services/runtime/instance.js +1 -1
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
- package/dist/services/runtime/types.d.ts +31 -0
- package/dist/services/setup-manager.js +190 -68
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.js.map +1 -1
- package/dist/services/update-manager.js +32 -14
- package/dist/services/update-manager.js.map +1 -1
- package/dist/services/webdav/server.d.ts +24 -0
- package/dist/services/webdav/server.js +420 -0
- package/dist/services/webdav/server.js.map +1 -0
- package/dist/services/webdav/xml-builder.d.ts +73 -0
- package/dist/services/webdav/xml-builder.js +156 -0
- package/dist/services/webdav/xml-builder.js.map +1 -0
- package/dist/services/workspace-builder.d.ts +29 -0
- package/dist/services/workspace-builder.js +188 -0
- package/dist/services/workspace-builder.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/utils/path-locks.d.ts +30 -0
- package/dist/utils/path-locks.js +63 -0
- package/dist/utils/path-locks.js.map +1 -0
- package/dist/utils/path-safety.d.ts +41 -0
- package/dist/utils/path-safety.js +119 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/safe-write.d.ts +24 -0
- package/dist/utils/safe-write.js +82 -0
- package/dist/utils/safe-write.js.map +1 -0
- package/install/jishu-install.sh +247 -35
- package/install/jishu-uninstall.sh +45 -5
- package/package.json +20 -2
- package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
- package/public/assets/Dashboard-AuJESBlJ.js +1 -0
- package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
- package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
- package/public/assets/InitPassword-ka4wNpM5.js +1 -0
- package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
- package/public/assets/Login-aPajuQzf.js +1 -0
- package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
- package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
- package/public/assets/Settings-BYQnbLYL.js +1 -0
- package/public/assets/Setup-D05lwDOV.js +1 -0
- package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
- package/public/assets/index-HSXCsceK.css +1 -0
- package/public/assets/index-bnBu0nlQ.js +19 -0
- package/public/assets/registry-C_qeFTkZ.js +2 -0
- package/public/assets/usePolling-Bn93fe7M.js +1 -0
- package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
- package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
- package/public/index.html +4 -4
- package/scripts/check-app-spec.mjs +18 -4
- package/scripts/check-colima-launchd.mjs +230 -0
- package/scripts/check-new-file-tests.mjs +230 -0
- package/scripts/check-quarantine-expiry.mjs +105 -0
- package/scripts/perf/README.md +49 -0
- package/scripts/perf/auth.js +99 -0
- package/scripts/perf/config.js +63 -0
- package/scripts/perf/instances.js +143 -0
- package/scripts/perf/proxy.js +96 -0
- package/scripts/smoke/files-w1.sh +142 -0
- package/scripts/smoke-backend.mjs +122 -0
- package/scripts/smoke-post-publish.mjs +346 -0
- package/public/assets/Dashboard-rkWp-CXd.js +0 -1
- package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
- package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
- package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
- package/public/assets/Login-BItXqYAJ.js +0 -1
- package/public/assets/NewInstance-BousE6kY.js +0 -1
- package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
- package/public/assets/Settings-Bttc6QmM.js +0 -1
- package/public/assets/Setup-Bsxx1zgj.js +0 -1
- package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
- package/public/assets/index-8xZy1z5k.css +0 -1
- package/public/assets/index-Dw3HhUYE.js +0 -19
- package/public/assets/input-paste-CrNVAyOy.js +0 -1
- package/public/assets/providers-DtNXh9JD.js +0 -1
- package/public/assets/registry-5s2UB6is.js +0 -2
- 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
|
+
"&": "&",
|
|
99
|
+
"<": "<",
|
|
100
|
+
">": ">",
|
|
101
|
+
'"': """,
|
|
102
|
+
"'": "'",
|
|
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
|