@filebox/webdav-server 1.0.0 → 1.0.1

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/index.js DELETED
@@ -1,88 +0,0 @@
1
- const Commands = require("./operations/commands");
2
- const createContext = require("./context");
3
-
4
- const StatusCodes = {
5
- 200: "OK",
6
- 201: "Created",
7
- 204: "No Content",
8
- 207: "Multi Status",
9
- 302: "Moved Temporarily",
10
- 401: "Unauthorized",
11
- 403: "Forbidden",
12
- 404: "Not Found",
13
- 409: "Conflict",
14
- 423: "Locked",
15
- 500: "Internal Server Error",
16
- };
17
-
18
- const VirtualDriver = {
19
- async get() {
20
- return {
21
- status: "200",
22
- body: "Hello World!",
23
- headers: {
24
- "Content-Type": "application/json",
25
- },
26
- };
27
- },
28
- // 其他方法...
29
- };
30
-
31
- class WebDAVServer {
32
- constructor(
33
- { driver, base, redirect, auth } = { redirect: false, auth: () => true },
34
- ) {
35
- this.methods = {};
36
- this.driver = driver || VirtualDriver;
37
- this.base = base || "";
38
- this.auth = auth;
39
- this.config = { redirect };
40
- const commands = Commands;
41
- for (const k in commands) {
42
- if (k === "NotImplemented") {
43
- this.unknownMethod = commands[k];
44
- } else {
45
- this.methods[k.toLowerCase()] = commands[k];
46
- }
47
- }
48
- this.allows = Object.keys(this.methods).map((i) => i.toUpperCase());
49
- }
50
-
51
- async request(req, options) {
52
- const ctx = createContext(
53
- req,
54
- options?.base || this.base,
55
- this.allows,
56
- options.temp,
57
- );
58
-
59
- if (
60
- !(ctx.method == "options" && !ctx.path) &&
61
- !this?.auth(ctx.auth.user, ctx.auth.pass)
62
- ) {
63
- return {
64
- headers: {
65
- "X-WebDAV-Status": `401 ${StatusCodes[401]}`,
66
- "www-Authenticate": 'Basic realm="Restricted"',
67
- },
68
- status: "401",
69
- };
70
- }
71
-
72
- // ctx.driver = this.driver;
73
- ctx.driver = options.driver;
74
- ctx.config = { ...this.config, temp: options.temp };
75
- const method = this.methods[ctx.method] || this.unknownMethod;
76
- const res = await method(ctx);
77
- res.headers = res.headers ?? {};
78
- if (res.status) {
79
- // res.headers["X-WebDAV-Status"] =
80
- // res.status + " " + StatusCodes[res.status];
81
- }
82
- return res;
83
- }
84
- }
85
-
86
- module.exports = {
87
- WebDAVServer,
88
- };
@@ -1,31 +0,0 @@
1
- const Get = require("./get");
2
- const Put = require("./put");
3
- const Post = require("./post");
4
- const Head = require("./head");
5
- const Move = require("./move");
6
- const Lock = require("./lock");
7
- const Copy = require("./copy");
8
- const Mkcol = require("./mkcol");
9
- const Unlock = require("./unlock");
10
- const Delete = require("./delete");
11
- const Options = require("./options");
12
- const Propfind = require("./propfind");
13
- const Proppatch = require("./proppatch");
14
- const NotImplemented = require("./not-implemented");
15
-
16
- module.exports = {
17
- NotImplemented,
18
- Proppatch,
19
- Propfind,
20
- Options,
21
- Delete,
22
- // Unlock,
23
- Mkcol,
24
- Copy,
25
- // Lock,
26
- Move,
27
- Head,
28
- // Post,
29
- Put,
30
- Get,
31
- };
@@ -1,37 +0,0 @@
1
- module.exports = async (ctx) => {
2
- try {
3
- const dst = new URL(ctx.req.headers?.destination).pathname.replace(
4
- ctx.base,
5
- "",
6
- );
7
- const src = ctx.path;
8
- if (src === dst) {
9
- return { status: "403" };
10
- }
11
-
12
- const stat = await ctx.driver.stat(src);
13
- if (!stat || !stat.file) {
14
- return { status: "404" };
15
- }
16
-
17
- const data = await ctx.driver.download(src);
18
- if (!data || !data.length) {
19
- return { status: "404" };
20
- }
21
-
22
- const { basename, dirname } = require("pathe");
23
- const upath = dirname(dst);
24
- const name = basename(dst);
25
- const resp = await fetch(data[0].url);
26
- await ctx.driver.upload(upath, {
27
- name,
28
- size: parseInt(resp.headers.get("content-length") || "0"),
29
- data: resp.body,
30
- mimeType: stat.mime || "application/octet-stream",
31
- });
32
-
33
- return { status: "201" };
34
- } catch (_) {
35
- return { status: "502" };
36
- }
37
- };
@@ -1,12 +0,0 @@
1
- module.exports = async (ctx) => {
2
- try {
3
- await ctx.driver.remove(ctx.path);
4
- return {
5
- status: "204",
6
- };
7
- } catch (_) {
8
- return {
9
- status: "502",
10
- };
11
- }
12
- };
package/operations/get.js DELETED
@@ -1,34 +0,0 @@
1
- async function loadProxy() {
2
- const proxy = await import("@filebox/proxy");
3
- return proxy.default || proxy;
4
- }
5
-
6
- module.exports = async (ctx) => {
7
- try {
8
- const decodedPath = decodeURIComponent(ctx.path);
9
- const stat = await ctx.driver.stat(decodedPath);
10
- if (!stat || !stat.file) {
11
- return {
12
- status: "405",
13
- body: "405 Method not allowed",
14
- };
15
- }
16
- const data = await ctx.driver.download(decodedPath);
17
- if (!data || !data.length || !data[0].url) {
18
- return {
19
- status: "404",
20
- body: "file not found",
21
- };
22
- }
23
- const proxy = await loadProxy();
24
- return proxy(ctx.req, {
25
- ...data[0],
26
- fileName: decodedPath,
27
- });
28
- } catch (_) {
29
- return {
30
- status: "500",
31
- body: "Internal Server Error",
32
- };
33
- }
34
- };
@@ -1,23 +0,0 @@
1
- module.exports = async (ctx) => {
2
- try {
3
- console.log(ctx.path, "head", 22222);
4
- const stat = ctx.driver.stat(ctx.path);
5
- const headers = {
6
- "Content-Type": "application/octet-stream",
7
- "Content-Length": stat.size,
8
- "Last-Modified": stat.mtime,
9
- "Accept-Ranges": "bytes",
10
- };
11
-
12
- return {
13
- status: "200",
14
- headers: headers,
15
- body: null,
16
- };
17
- } catch (error) {
18
- console.log(error);
19
- return {
20
- status: "404",
21
- };
22
- }
23
- };
@@ -1 +0,0 @@
1
- module.exports = {};
@@ -1,14 +0,0 @@
1
- module.exports = async (ctx) => {
2
- try {
3
- await ctx.driver.mkdir(ctx.path);
4
- return {
5
- status: "201",
6
- body: null,
7
- };
8
- } catch (_) {
9
- return {
10
- status: "500",
11
- body: null,
12
- };
13
- }
14
- };
@@ -1,46 +0,0 @@
1
- const { basename, dirname } = require("pathe");
2
-
3
- module.exports = async (ctx) => {
4
- try {
5
- const dst = new URL(ctx.req.headers?.destination).pathname.replace(
6
- ctx.base,
7
- "",
8
- );
9
- const src = ctx.path;
10
-
11
- if (!dst) {
12
- return {
13
- status: "400",
14
- body: "Missing source or destination URI",
15
- };
16
- }
17
-
18
- const normalizedSrc = src.endsWith("/") ? src.slice(0, -1) : src;
19
- const normalizedDst = dst.endsWith("/") ? dst.slice(0, -1) : dst;
20
-
21
- if (normalizedSrc === normalizedDst) {
22
- return { status: "403" };
23
- }
24
-
25
- const isRename =
26
- normalizedSrc.substring(0, normalizedSrc.lastIndexOf("/")) ===
27
- normalizedDst.substring(0, normalizedDst.lastIndexOf("/"));
28
-
29
- if (isRename) {
30
- // 处理重命名
31
- await ctx.driver?.rename(normalizedSrc, basename(normalizedDst));
32
- } else {
33
- // 处理移动
34
- await ctx.driver?.move(normalizedSrc, dirname(normalizedDst));
35
- }
36
-
37
- return {
38
- status: "201",
39
- };
40
- } catch (_) {
41
- return {
42
- status: "502",
43
- body: null,
44
- };
45
- }
46
- };
@@ -1,10 +0,0 @@
1
- const commands = require("./commands");
2
-
3
- module.exports = async (ctx) => {
4
- return {
5
- status: "405 Method not allowed",
6
- headers: {
7
- Allow: Object.keys(commands).join(", "),
8
- },
9
- };
10
- };
@@ -1,17 +0,0 @@
1
- module.exports = async (ctx) => {
2
- const dav = [1];
3
-
4
- if (ctx.allows?.includes("LOCK")) {
5
- dav.push(2);
6
- }
7
-
8
- return {
9
- headers: {
10
- // For Microsoft clients
11
- "MS-Author-Via": "DAV",
12
- DAV: dav.join(", "),
13
- Allow: ctx.allows?.join(", ") || "",
14
- },
15
- status: "200",
16
- };
17
- };
@@ -1,10 +0,0 @@
1
- const commands = require("./commands");
2
-
3
- module.exports = async (ctx) => {
4
- return {
5
- status: "405 Method not allowed",
6
- headers: {
7
- Allow: Object.keys(commands).join(", "),
8
- },
9
- };
10
- };
@@ -1,323 +0,0 @@
1
- const parseXML = require("./shared");
2
- const xml2js = require("xml2js");
3
-
4
- const DEFAULT_PROPS = [
5
- "displayname",
6
- "getcontentlength",
7
- "resourcetype",
8
- "getcontenttype",
9
- "creationdate",
10
- "getlastmodified",
11
- ];
12
-
13
- /**
14
- * Parse props from webdav request
15
- *
16
- * @param {object} [data]
17
- * @return {object|boolean}
18
- */
19
- // const propParse = (data) => {
20
- // if (!data) return {
21
- // ns: { prefix: 'D', uri: 'DAV:' },
22
- // prop: [...DEFAULT_PROPS]
23
- // }
24
- // let prop = [...DEFAULT_PROPS]
25
- // const prefix = Object.keys(data.propfind.$).find(i => i.startsWith('xmlns:'))?.split(':')[1] || ''
26
- // const uri = data.propfind.$?.[`xmlns${prefix ? `:${prefix}` : ''}`] || ''
27
- // if (data.propfind.hasOwnProperty('prop')) {
28
- // prop = Object.keys(data.propfind.prop)
29
- // }
30
- // return { ns: { prefix, uri }, prop }
31
- // }
32
-
33
- const propParse = (data) => {
34
- if (!data) {
35
- return {
36
- ns: { prefix: "D", uri: "DAV:" },
37
- prop: [...DEFAULT_PROPS],
38
- };
39
- }
40
-
41
- let prop = [...DEFAULT_PROPS];
42
- const ns = { prefix: "D", uri: "DAV:" };
43
-
44
- if (data.propfind.hasOwnProperty("prop")) {
45
- if (data.propfind.prop.hasOwnProperty("allprop")) {
46
- return {
47
- ns,
48
- prop: [...DEFAULT_PROPS],
49
- };
50
- }
51
-
52
- prop = Object.keys(data.propfind.prop);
53
- }
54
-
55
- return { ns, prop };
56
- };
57
-
58
- /**
59
- * Create webdav responese xml by data and props options
60
- *
61
- * @param {object} [data] file data
62
- * @param {object} [options]
63
- * @param {object} [options.props] Available props
64
- * @param {object} [options.path] Current folder path
65
- * @param {object} [options.ns]
66
- * @return {string} XML string
67
- */
68
-
69
- // const convData = (files, options) => {
70
- // const {
71
- // path,
72
- // base = "",
73
- // depth,
74
- // prop,
75
- // ns: { prefix, uri },
76
- // } = options;
77
- // console.log(options, 2222);
78
- // console.log(files, 222);
79
- // return files.map((file) => {
80
- // const item = {};
81
- // for (const key of prop) {
82
- // item[key] = file.name
83
- // .replace(/&/g, "&")
84
- // .replace(/</g, "&lt;")
85
- // .replace(/>/g, "&gt;")
86
- // .replace(/"/g, "&quot;")
87
- // .replace(/'/g, "&apos;");
88
-
89
- // if (key == "getcontentlength") {
90
- // item[key] = parseInt(file.size || 0);
91
- // } else if (key == "resourcetype") {
92
- // item[key] = file.type == "folder" ? { collection: "" } : "";
93
- // // } else if (key == 'getcontenttype') {
94
- // // item[key] = file.mime
95
- // } else if (key == "creationdate" && file.ctime) {
96
- // item[key] = new Date(file.ctime).toUTCString();
97
- // } else if (key == "getlastmodified" && file.lastModifiedDateTime) {
98
- // item[key] = new Date(file.lastModifiedDateTime).toUTCString();
99
- // }
100
- // }
101
-
102
- // const href = (
103
- // base +
104
- // path +
105
- // (depth == "0" ? "" : "/" + encodeURIComponent(file.name))
106
- // ).replace(/\/{2,}/g, "/");
107
- // //if (file.type == 'file' && file.download_url) href = file.download_url
108
- // return {
109
- // href,
110
- // propstat: {
111
- // status: "HTTP/1.1 200 OK",
112
- // prop: item,
113
- // },
114
- // };
115
- // });
116
- // };
117
-
118
- const convData = (files, options) => {
119
- const {
120
- path,
121
- base = "",
122
- depth,
123
- prop,
124
- ns: { prefix, uri },
125
- } = options;
126
- // console.log(prop, 1111)
127
- files = files.data || files.list || files;
128
- return files.map((file) => {
129
- const item = {};
130
- for (const key of prop) {
131
- // Encode special characters for XML for displayname property
132
- if (key === "displayname") {
133
- item[key] = file.name
134
- .replace(/&/g, "&amp;")
135
- .replace(/</g, "&lt;")
136
- .replace(/>/g, "&gt;")
137
- .replace(/"/g, "&quot;")
138
- .replace(/'/g, "&apos;");
139
- }
140
-
141
- // Add the file size
142
- if (key === "getcontentlength") {
143
- item[key] = file.byte || 0;
144
- }
145
-
146
- // Add the resource type
147
- if (key === "resourcetype") {
148
- item[key] = file.type === "folder" ? { collection: "" } : "";
149
- }
150
-
151
- // Add the content type
152
- if (key == "getcontenttype") {
153
- item[key] = file.mime;
154
- }
155
-
156
- if (key === "getetag") {
157
- const stamp =
158
- file.etag || file.hash || file.mtime || file.updatedAt || "";
159
- item[key] =
160
- `"${Buffer.from(`${file.name || ""}:${file.byte || 0}:${stamp}`).toString("base64url")}"`;
161
- }
162
-
163
- // Add the creation date
164
- if (key === "creationdate" && file.ctime) {
165
- item[key] = new Date(file.ctime).toUTCString();
166
- }
167
-
168
- // Add the last modified date
169
- if (key === "getlastmodified" && file.mtime) {
170
- item[key] = new Date(file.mtime).toUTCString();
171
- }
172
-
173
- // Set the available quota bytes to indicate unlimited
174
- if (key === "quota-available-bytes") {
175
- item[key] = -1;
176
- }
177
-
178
- // Set the used quota bytes to indicate unlimited
179
- if (key === "quota-used-bytes") {
180
- item[key] = -1;
181
- }
182
- }
183
-
184
- // Construct the href.
185
- // The collection itself uses base+path; its children append /name.
186
- // Collections get a trailing slash (RFC 4918 §5.2).
187
- const isSelf = file._self === true;
188
- let href = (
189
- isSelf ? base + path : base + path + "/" + encodeURIComponent(file.name)
190
- ).replace(/\/{2,}/g, "/");
191
- if (file.type === "folder" && !href.endsWith("/")) href += "/";
192
- // Return the WebDAV propstat object
193
- return {
194
- href,
195
- propstat: {
196
- status: "HTTP/1.1 200 OK",
197
- prop: item,
198
- },
199
- };
200
- });
201
- };
202
-
203
- const fixNs = (data, prefix) => {
204
- if (!prefix) return data;
205
- Object.keys(data).forEach((key) => {
206
- const val = data[key];
207
- if (key != "$" && prefix) {
208
- if (Array.isArray(val) || typeof val == "object") {
209
- fixNs(val, prefix);
210
- }
211
- delete data[key];
212
- data[`${prefix}:${key}`] = val;
213
- } else {
214
- if (val.xmlns) {
215
- val[`xmlns:${prefix}`] = val.xmlns;
216
- delete val.xmlns;
217
- }
218
- }
219
- });
220
- return data;
221
- };
222
-
223
- const createXML = (data, options) => {
224
- const {
225
- ns: { prefix, uri },
226
- } = options;
227
-
228
- const obj = {
229
- multistatus: {
230
- response: convData(data || [], options),
231
- },
232
- };
233
- if (uri) {
234
- obj.multistatus.$ = {
235
- xmlns: uri,
236
- };
237
- }
238
-
239
- const builder = new xml2js.Builder({
240
- renderOpts: { pretty: false },
241
- xmldec: { version: "1.0", encoding: "UTF-8" },
242
- });
243
-
244
- const xml = builder.buildObject(fixNs(obj, prefix));
245
- return xml;
246
- };
247
-
248
- module.exports = async (ctx) => {
249
- const options = Object.assign(
250
- {
251
- path: ctx.path,
252
- base: ctx.base,
253
- depth: ctx.depth,
254
- },
255
- propParse(await parseXML(ctx.req)),
256
- );
257
-
258
- let data = {};
259
- data.item = await ctx.driver.stat(ctx.path);
260
- // console.log(data.item);
261
- if (ctx.depth == "1") {
262
- try {
263
- data.files = await ctx.driver.list(ctx.path);
264
- } catch (error) {
265
- return { status: "500" };
266
- }
267
- }
268
-
269
- if (!data) return { status: "404" };
270
-
271
- if (data.error) {
272
- if (data.error.code == 401) {
273
- // Windows seems to require this being the last header sent
274
- // (changed according to PECL bug #3138)
275
- return {
276
- headers: {
277
- "WWW-Authenticate": `Basic realm="ShareList WebDAV"`,
278
- },
279
- status: "401",
280
- };
281
- } else {
282
- return {
283
- status: "404",
284
- };
285
- }
286
- }
287
-
288
- //return itself
289
- // Mark the requested resource as "self" so its href is built as base+path
290
- // (children append /name). Depth:1 must include the collection itself
291
- // followed by its members (RFC 4918 §9.1).
292
- const selfItem = Object.assign({}, data.item, { _self: true });
293
- if (ctx.depth == "0") {
294
- return {
295
- status: "207",
296
- headers: {
297
- "content-type": 'text/xml; charset="utf-8"',
298
- },
299
- body: createXML([selfItem], options),
300
- };
301
- } else if (ctx.depth == "1") {
302
- const listResult = data.files;
303
- const children = Array.isArray(listResult)
304
- ? listResult
305
- : Array.isArray(listResult && listResult.data)
306
- ? listResult.data
307
- : Array.isArray(listResult && listResult.list)
308
- ? listResult.list
309
- : [];
310
- return {
311
- status: "207",
312
- headers: {
313
- // "content-type": 'text/xml; charset="utf-8"',
314
- "content-type": 'application/xml; charset="utf-8"',
315
- },
316
- body: createXML([selfItem].concat(children), options),
317
- };
318
- } else if (ctx.depth == "infinity") {
319
- return {
320
- status: "404",
321
- };
322
- }
323
- };
@@ -1,5 +0,0 @@
1
- module.exports = async (ctx) => {
2
- return {
3
- status: "200",
4
- };
5
- };