zen-fs-webdav 0.1.0
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/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/index.d.mts +278 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +768 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +752 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +2374 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
// zen-fs-webdav - https://github.com/weijia/zen-fs-webdav
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/errors.ts
|
|
10
|
+
var WebDAVError = class _WebDAVError extends Error {
|
|
11
|
+
constructor(message, status, cause) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "WebDAVError";
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
Object.setPrototypeOf(this, _WebDAVError.prototype);
|
|
17
|
+
}
|
|
18
|
+
toString() {
|
|
19
|
+
if (typeof this.status === "number") {
|
|
20
|
+
return `${this.name}: ${this.message} (Status: ${this.status})`;
|
|
21
|
+
}
|
|
22
|
+
return `${this.name}: ${this.message}`;
|
|
23
|
+
}
|
|
24
|
+
static fromResponse(response, message) {
|
|
25
|
+
const msg = message ? `${message}: ${response.status} ${response.statusText}` : `${response.status} ${response.statusText}`;
|
|
26
|
+
return new _WebDAVError(msg, response.status);
|
|
27
|
+
}
|
|
28
|
+
static fromError(error) {
|
|
29
|
+
if (error instanceof _WebDAVError) {
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
const msg = error.message || "Unknown WebDAV error";
|
|
34
|
+
return new _WebDAVError(msg, void 0, error);
|
|
35
|
+
}
|
|
36
|
+
return new _WebDAVError("Unknown WebDAV error");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var NotFoundError = class _NotFoundError extends WebDAVError {
|
|
40
|
+
constructor(path) {
|
|
41
|
+
super(`\u6587\u4EF6\u672A\u627E\u5230: ${path}`, 404);
|
|
42
|
+
this.name = "NotFoundError";
|
|
43
|
+
Object.setPrototypeOf(this, _NotFoundError.prototype);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var AuthenticationError = class _AuthenticationError extends WebDAVError {
|
|
47
|
+
constructor(message = "\u8BA4\u8BC1\u5931\u8D25") {
|
|
48
|
+
super(message, 401);
|
|
49
|
+
this.name = "AuthenticationError";
|
|
50
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var AuthorizationError = class _AuthorizationError extends WebDAVError {
|
|
54
|
+
constructor(message = "\u6743\u9650\u88AB\u62D2\u7EDD") {
|
|
55
|
+
super(message, 403);
|
|
56
|
+
this.name = "AuthorizationError";
|
|
57
|
+
Object.setPrototypeOf(this, _AuthorizationError.prototype);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var ServerError = class _ServerError extends WebDAVError {
|
|
61
|
+
constructor(message = "\u670D\u52A1\u5668\u9519\u8BEF", statusCode = 500) {
|
|
62
|
+
super(message, statusCode);
|
|
63
|
+
this.name = "ServerError";
|
|
64
|
+
Object.setPrototypeOf(this, _ServerError.prototype);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var FileExistsError = class _FileExistsError extends WebDAVError {
|
|
68
|
+
constructor(path) {
|
|
69
|
+
super(`\u6587\u4EF6\u5DF2\u5B58\u5728: ${path}`, 412);
|
|
70
|
+
this.name = "FileExistsError";
|
|
71
|
+
Object.setPrototypeOf(this, _FileExistsError.prototype);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var TimeoutError = class _TimeoutError extends WebDAVError {
|
|
75
|
+
constructor(message = "\u8FDE\u63A5\u8D85\u65F6") {
|
|
76
|
+
super(message, 408);
|
|
77
|
+
this.name = "TimeoutError";
|
|
78
|
+
Object.setPrototypeOf(this, _TimeoutError.prototype);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var NetworkError = class _NetworkError extends WebDAVError {
|
|
82
|
+
constructor(originalError, message = "\u7F51\u7EDC\u9519\u8BEF") {
|
|
83
|
+
super(originalError ? `${message}: ${originalError.message}` : message, 0, originalError);
|
|
84
|
+
this.name = "NetworkError";
|
|
85
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var ArgumentError = class _ArgumentError extends WebDAVError {
|
|
89
|
+
constructor(message) {
|
|
90
|
+
super(`\u65E0\u6548\u53C2\u6570: ${message}`);
|
|
91
|
+
this.name = "ArgumentError";
|
|
92
|
+
Object.setPrototypeOf(this, _ArgumentError.prototype);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/constants.ts
|
|
97
|
+
var WebDAVNamespace = {
|
|
98
|
+
DAV: "DAV:",
|
|
99
|
+
CALDAV: "urn:ietf:params:xml:ns:caldav",
|
|
100
|
+
CARDDAV: "urn:ietf:params:xml:ns:carddav",
|
|
101
|
+
OWNCLOUD: "http://owncloud.org/ns",
|
|
102
|
+
NEXTCLOUD: "http://nextcloud.org/ns"
|
|
103
|
+
};
|
|
104
|
+
var XML_NAMESPACE_PREFIX = {
|
|
105
|
+
"d": WebDAVNamespace.DAV,
|
|
106
|
+
"oc": WebDAVNamespace.OWNCLOUD,
|
|
107
|
+
"nc": WebDAVNamespace.NEXTCLOUD
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/utils.ts
|
|
111
|
+
import { XMLParser } from "fast-xml-parser";
|
|
112
|
+
function normalizePath(path) {
|
|
113
|
+
if (!path.startsWith("/")) {
|
|
114
|
+
path = "/" + path;
|
|
115
|
+
}
|
|
116
|
+
path = path.replace(/\/+/g, "/");
|
|
117
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
118
|
+
path = path.slice(0, -1);
|
|
119
|
+
}
|
|
120
|
+
return path;
|
|
121
|
+
}
|
|
122
|
+
function createBasicAuthHeader(username, password) {
|
|
123
|
+
const btoa = (str) => {
|
|
124
|
+
if (typeof window !== "undefined" && window.btoa) {
|
|
125
|
+
return window.btoa(str);
|
|
126
|
+
} else if (typeof Buffer !== "undefined") {
|
|
127
|
+
return Buffer.from(str).toString("base64");
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error("Base64 encoding not available");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return `Basic ${btoa(`${username}:${password}`)}`;
|
|
133
|
+
}
|
|
134
|
+
function getContentType(filename) {
|
|
135
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
136
|
+
switch (ext) {
|
|
137
|
+
case "txt":
|
|
138
|
+
return "text/plain";
|
|
139
|
+
case "html":
|
|
140
|
+
case "htm":
|
|
141
|
+
return "text/html";
|
|
142
|
+
case "json":
|
|
143
|
+
return "application/json";
|
|
144
|
+
case "xml":
|
|
145
|
+
return "application/xml";
|
|
146
|
+
case "jpg":
|
|
147
|
+
case "jpeg":
|
|
148
|
+
return "image/jpeg";
|
|
149
|
+
case "png":
|
|
150
|
+
return "image/png";
|
|
151
|
+
case "gif":
|
|
152
|
+
return "image/gif";
|
|
153
|
+
case "pdf":
|
|
154
|
+
return "application/pdf";
|
|
155
|
+
case "csv":
|
|
156
|
+
return "text/csv";
|
|
157
|
+
case "js":
|
|
158
|
+
return "application/javascript";
|
|
159
|
+
case "css":
|
|
160
|
+
return "text/css";
|
|
161
|
+
case "zip":
|
|
162
|
+
return "application/zip";
|
|
163
|
+
default:
|
|
164
|
+
return "application/octet-stream";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function getDirFromPath(path) {
|
|
168
|
+
if (!path || path === "/") return "/";
|
|
169
|
+
const normalized = path.replace(/\/+$/, "");
|
|
170
|
+
const idx = normalized.lastIndexOf("/");
|
|
171
|
+
if (idx <= 0) return "/";
|
|
172
|
+
return normalized.slice(0, idx) || "/";
|
|
173
|
+
}
|
|
174
|
+
function parseWebDAVXml(xml, basePath) {
|
|
175
|
+
const decode = __require("he").decode;
|
|
176
|
+
const result = [];
|
|
177
|
+
if (!xml) return result;
|
|
178
|
+
const parser = new XMLParser({
|
|
179
|
+
ignoreAttributes: false,
|
|
180
|
+
attributeNamePrefix: "@_",
|
|
181
|
+
trimValues: true
|
|
182
|
+
});
|
|
183
|
+
const json = parser.parse(xml);
|
|
184
|
+
function getCaseInsensitive(obj, ...keys) {
|
|
185
|
+
if (!obj) return void 0;
|
|
186
|
+
for (const key of keys) {
|
|
187
|
+
if (obj[key] !== void 0) return obj[key];
|
|
188
|
+
const found = Object.keys(obj).find((k) => k.toLowerCase() === key.toLowerCase());
|
|
189
|
+
if (found) return obj[found];
|
|
190
|
+
}
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
const multistatus = getCaseInsensitive(json, "d:multistatus", "multistatus");
|
|
194
|
+
const responses = getCaseInsensitive(multistatus, "d:response", "response") || [];
|
|
195
|
+
const arr = Array.isArray(responses) ? responses : [responses];
|
|
196
|
+
const isBasePathFile = basePath && !basePath.endsWith("/");
|
|
197
|
+
for (const item of arr) {
|
|
198
|
+
const href = decode(getCaseInsensitive(item, "d:href", "href") || "");
|
|
199
|
+
const propstat = getCaseInsensitive(item, "d:propstat", "propstat", "d:prop", "prop") || {};
|
|
200
|
+
const prop = getCaseInsensitive(propstat, "d:prop", "prop") || propstat;
|
|
201
|
+
const resourcetype = getCaseInsensitive(prop, "d:resourcetype", "resourcetype");
|
|
202
|
+
const collection = resourcetype && getCaseInsensitive(resourcetype, "d:collection", "collection");
|
|
203
|
+
const isDirectory = !!(resourcetype && collection !== void 0);
|
|
204
|
+
let name;
|
|
205
|
+
if (isBasePathFile) {
|
|
206
|
+
name = href.split("/").filter(Boolean).pop() || "";
|
|
207
|
+
} else {
|
|
208
|
+
name = href.replace(basePath, "").replace(/^\//, "").replace(/\/$/, "");
|
|
209
|
+
}
|
|
210
|
+
if (!name) continue;
|
|
211
|
+
result.push({
|
|
212
|
+
path: href,
|
|
213
|
+
name,
|
|
214
|
+
isDirectory,
|
|
215
|
+
isFile: !isDirectory,
|
|
216
|
+
size: parseInt(String(getCaseInsensitive(prop, "d:getcontentlength", "getcontentlength") ?? "0"), 10),
|
|
217
|
+
lastModified: getCaseInsensitive(prop, "d:getlastmodified", "getlastmodified") ? new Date(String(getCaseInsensitive(prop, "d:getlastmodified", "getlastmodified"))) : void 0
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
function joinUrl(base, ...paths) {
|
|
223
|
+
let url = base;
|
|
224
|
+
let basePath = "";
|
|
225
|
+
try {
|
|
226
|
+
const u = new URL(base);
|
|
227
|
+
basePath = u.pathname.replace(/\/+$/, "");
|
|
228
|
+
} catch {
|
|
229
|
+
basePath = base.startsWith("/") ? base.replace(/\/+$/, "") : "";
|
|
230
|
+
}
|
|
231
|
+
if (paths.length > 0 && basePath) {
|
|
232
|
+
let p = paths[0];
|
|
233
|
+
if (p) {
|
|
234
|
+
const baseFirst = basePath.split("/").filter(Boolean)[0];
|
|
235
|
+
const pParts = p.split("/").filter(Boolean);
|
|
236
|
+
if (baseFirst && pParts[0] === baseFirst) {
|
|
237
|
+
pParts.shift();
|
|
238
|
+
p = pParts.join("/");
|
|
239
|
+
if (p && !p.startsWith("/")) p = "/" + p;
|
|
240
|
+
paths[0] = p;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const p of paths) {
|
|
245
|
+
if (!p) continue;
|
|
246
|
+
if (!url.endsWith("/")) url += "/";
|
|
247
|
+
url += p.startsWith("/") ? p.slice(1) : p;
|
|
248
|
+
}
|
|
249
|
+
url = url.replace(/([^:]\/)\/+/g, "$1");
|
|
250
|
+
return url;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/webdav-fs.ts
|
|
254
|
+
var WebDAVFS = class {
|
|
255
|
+
/**
|
|
256
|
+
* 创建WebDAV文件系统实例
|
|
257
|
+
* @param options WebDAV选项
|
|
258
|
+
*/
|
|
259
|
+
constructor(options) {
|
|
260
|
+
if (!options.baseUrl) {
|
|
261
|
+
throw new ArgumentError("\u5FC5\u987B\u63D0\u4F9BbaseUrl");
|
|
262
|
+
}
|
|
263
|
+
this.baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl;
|
|
264
|
+
if (options.username && options.password) {
|
|
265
|
+
this.auth = { username: options.username, password: options.password };
|
|
266
|
+
} else {
|
|
267
|
+
this.auth = void 0;
|
|
268
|
+
}
|
|
269
|
+
this.timeout = options.timeout || 3e4;
|
|
270
|
+
this.headers = options.headers || {};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 创建请求头
|
|
274
|
+
* @param customHeaders 自定义请求头
|
|
275
|
+
* @returns 合并后的请求头
|
|
276
|
+
*/
|
|
277
|
+
createHeaders(customHeaders) {
|
|
278
|
+
const headers = {
|
|
279
|
+
...this.headers,
|
|
280
|
+
...customHeaders
|
|
281
|
+
};
|
|
282
|
+
if (this.auth) {
|
|
283
|
+
headers["Authorization"] = createBasicAuthHeader(this.auth.username, this.auth.password);
|
|
284
|
+
}
|
|
285
|
+
return headers;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 执行HTTP请求
|
|
289
|
+
* @param method HTTP方法
|
|
290
|
+
* @param path 请求路径
|
|
291
|
+
* @param options 请求选项
|
|
292
|
+
* @returns 响应对象
|
|
293
|
+
*/
|
|
294
|
+
async request(method, path, options = {}) {
|
|
295
|
+
const normalizedPath = normalizePath(path);
|
|
296
|
+
const url = joinUrl(this.baseUrl, normalizedPath);
|
|
297
|
+
const headers = this.createHeaders(options.headers);
|
|
298
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
299
|
+
const timeoutId = controller ? setTimeout(() => controller.abort(), this.timeout) : void 0;
|
|
300
|
+
try {
|
|
301
|
+
const response = await fetch(url, {
|
|
302
|
+
method,
|
|
303
|
+
headers,
|
|
304
|
+
body: options.body,
|
|
305
|
+
signal: controller?.signal,
|
|
306
|
+
credentials: "include"
|
|
307
|
+
});
|
|
308
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
309
|
+
const responseHeaders = {};
|
|
310
|
+
response.headers.forEach((value, key) => {
|
|
311
|
+
responseHeaders[key.toLowerCase()] = value;
|
|
312
|
+
});
|
|
313
|
+
let data;
|
|
314
|
+
if (options.responseType === "arraybuffer") {
|
|
315
|
+
data = await response.arrayBuffer();
|
|
316
|
+
} else if (options.responseType === "blob") {
|
|
317
|
+
data = await response.blob();
|
|
318
|
+
} else if (options.responseType === "json") {
|
|
319
|
+
data = await response.json();
|
|
320
|
+
} else {
|
|
321
|
+
data = await response.text();
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
data,
|
|
325
|
+
status: response.status,
|
|
326
|
+
headers: responseHeaders
|
|
327
|
+
};
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
330
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
331
|
+
throw new TimeoutError(`\u8BF7\u6C42\u8D85\u65F6: ${url}`);
|
|
332
|
+
} else {
|
|
333
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
334
|
+
throw new NetworkError(error instanceof Error ? error : new Error(String(error)), `\u7F51\u7EDC\u9519\u8BEF: ${errorMessage}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 处理响应错误
|
|
340
|
+
* @param status HTTP状态码
|
|
341
|
+
* @param path 请求路径
|
|
342
|
+
* @param error 原始错误
|
|
343
|
+
*/
|
|
344
|
+
handleResponseError(status, path, error) {
|
|
345
|
+
switch (status) {
|
|
346
|
+
case 401:
|
|
347
|
+
throw new AuthenticationError(`\u8BA4\u8BC1\u5931\u8D25: ${path}`);
|
|
348
|
+
case 403:
|
|
349
|
+
throw new AuthorizationError(`\u65E0\u6743\u9650\u8BBF\u95EE: ${path}`);
|
|
350
|
+
case 404:
|
|
351
|
+
throw new NotFoundError(`\u8D44\u6E90\u4E0D\u5B58\u5728: ${path}`);
|
|
352
|
+
case 409:
|
|
353
|
+
throw new FileExistsError(`\u8D44\u6E90\u5DF2\u5B58\u5728: ${path}`);
|
|
354
|
+
default:
|
|
355
|
+
throw new ServerError(`\u670D\u52A1\u5668\u9519\u8BEF (${status}): ${path}`, status);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 读取文件内容
|
|
360
|
+
* @param path 文件路径
|
|
361
|
+
* @param options 读取选项
|
|
362
|
+
* @returns 文件内容
|
|
363
|
+
*/
|
|
364
|
+
async readFile(path, options = {}) {
|
|
365
|
+
const normalizedPath = normalizePath(path);
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.request("GET", normalizedPath, {
|
|
368
|
+
headers: options.headers,
|
|
369
|
+
responseType: "arraybuffer"
|
|
370
|
+
});
|
|
371
|
+
if (response.status >= 400) {
|
|
372
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
373
|
+
}
|
|
374
|
+
const arrayBuffer = response.data;
|
|
375
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
376
|
+
const buffer = Buffer.from(uint8Array);
|
|
377
|
+
if (options.encoding) {
|
|
378
|
+
return buffer.toString(options.encoding);
|
|
379
|
+
} else {
|
|
380
|
+
return buffer;
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error instanceof WebDAVError) {
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
throw new WebDAVError(`\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${normalizedPath}`, void 0, error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 写入文件内容
|
|
391
|
+
* @param path 文件路径
|
|
392
|
+
* @param data 文件内容
|
|
393
|
+
* @param options 写入选项
|
|
394
|
+
* @returns 操作结果
|
|
395
|
+
*/
|
|
396
|
+
async writeFile(path, data, options = {}) {
|
|
397
|
+
const normalizedPath = normalizePath(path);
|
|
398
|
+
const getFilenameFromPath = (p) => {
|
|
399
|
+
const parts = p.split("/");
|
|
400
|
+
return parts[parts.length - 1] || "";
|
|
401
|
+
};
|
|
402
|
+
const contentType = options.contentType || getContentType(getFilenameFromPath(normalizedPath));
|
|
403
|
+
if (options.overwrite === false) {
|
|
404
|
+
const exists = await this.exists(normalizedPath);
|
|
405
|
+
if (exists) {
|
|
406
|
+
throw new FileExistsError(normalizedPath);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const headers = {
|
|
410
|
+
"Content-Type": contentType
|
|
411
|
+
};
|
|
412
|
+
try {
|
|
413
|
+
const response = await this.request("PUT", normalizedPath, {
|
|
414
|
+
headers,
|
|
415
|
+
body: data
|
|
416
|
+
});
|
|
417
|
+
if (response.status >= 400) {
|
|
418
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
success: response.status >= 200 && response.status < 300,
|
|
422
|
+
statusCode: response.status
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error instanceof WebDAVError) {
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
throw new WebDAVError(`\u5199\u5165\u6587\u4EF6\u5931\u8D25: ${normalizedPath}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 删除文件
|
|
433
|
+
* @param path 文件路径
|
|
434
|
+
* @returns 操作结果
|
|
435
|
+
*/
|
|
436
|
+
async deleteFile(path) {
|
|
437
|
+
const normalizedPath = normalizePath(path);
|
|
438
|
+
try {
|
|
439
|
+
const stat = await this.stat(normalizedPath);
|
|
440
|
+
if (stat.isDirectory) {
|
|
441
|
+
throw new ArgumentError(`\u8DEF\u5F84\u6307\u5411\u4E00\u4E2A\u76EE\u5F55\uFF0C\u8BF7\u4F7F\u7528rmdir\u65B9\u6CD5: ${normalizedPath}`);
|
|
442
|
+
}
|
|
443
|
+
const response = await this.request("DELETE", normalizedPath);
|
|
444
|
+
if (response.status >= 400) {
|
|
445
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
success: response.status >= 200 && response.status < 300,
|
|
449
|
+
statusCode: response.status
|
|
450
|
+
};
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (error instanceof WebDAVError) {
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
throw new WebDAVError(`\u5220\u9664\u6587\u4EF6\u5931\u8D25: ${normalizedPath}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 读取目录内容
|
|
460
|
+
* @param path 目录路径
|
|
461
|
+
* @param options 读取选项
|
|
462
|
+
* @returns 文件统计信息数组
|
|
463
|
+
*/
|
|
464
|
+
async readDir(path, options = {}) {
|
|
465
|
+
const normalizedPath = normalizePath(path);
|
|
466
|
+
try {
|
|
467
|
+
const headers = {
|
|
468
|
+
"Depth": options.recursive ? "infinity" : "1",
|
|
469
|
+
"Content-Type": "application/xml"
|
|
470
|
+
};
|
|
471
|
+
const response = await this.request("PROPFIND", normalizedPath, {
|
|
472
|
+
headers
|
|
473
|
+
});
|
|
474
|
+
if (response.status >= 400) {
|
|
475
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
476
|
+
}
|
|
477
|
+
const files = parseWebDAVXml(response.data, normalizedPath);
|
|
478
|
+
const result = files.filter((file) => {
|
|
479
|
+
if (file.path === normalizedPath) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (!options.includeHidden && file.name.startsWith(".")) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
});
|
|
487
|
+
return result;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error instanceof WebDAVError) {
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
throw new WebDAVError(`\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25: ${normalizedPath}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* 创建目录
|
|
497
|
+
* @param path 目录路径
|
|
498
|
+
* @param options 创建选项
|
|
499
|
+
* @returns 操作结果
|
|
500
|
+
*/
|
|
501
|
+
async mkdir(path, options = {}) {
|
|
502
|
+
const normalizedPath = normalizePath(path);
|
|
503
|
+
try {
|
|
504
|
+
try {
|
|
505
|
+
const stat = await this.stat(normalizedPath);
|
|
506
|
+
if (stat.isDirectory) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
throw new FileExistsError(normalizedPath);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (!(error instanceof NotFoundError)) {
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (options.recursive !== false) {
|
|
516
|
+
const parentDir = getDirFromPath(normalizedPath);
|
|
517
|
+
if (parentDir !== "/" && parentDir !== normalizedPath) {
|
|
518
|
+
try {
|
|
519
|
+
await this.stat(parentDir);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error instanceof NotFoundError) {
|
|
522
|
+
await this.mkdir(parentDir, options);
|
|
523
|
+
} else {
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const response = await this.request("MKCOL", normalizedPath);
|
|
530
|
+
if (response.status >= 400) {
|
|
531
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (error instanceof WebDAVError) {
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
throw new WebDAVError(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${normalizedPath}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* 删除文件或目录,行为类似于 Node.js 的 fs.rm
|
|
543
|
+
* @param path 路径
|
|
544
|
+
* @param options { recursive?: boolean, force?: boolean }
|
|
545
|
+
*/
|
|
546
|
+
async rm(path, options) {
|
|
547
|
+
const { recursive = false, force = false } = options || {};
|
|
548
|
+
try {
|
|
549
|
+
const stat = await this.stat(path);
|
|
550
|
+
if (stat.isDirectory) {
|
|
551
|
+
if (recursive) {
|
|
552
|
+
const files = await this.readDir(path);
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
const childPath = path.replace(/\/$/, "") + "/" + file.name;
|
|
555
|
+
await this.rm(childPath, { recursive: true, force });
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
const files = await this.readDir(path);
|
|
559
|
+
if (files.length > 0) {
|
|
560
|
+
throw new WebDAVError(`Directory not empty: ${path}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
await this._delete(path);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
if (force && (err instanceof Error && "code" in err && err.code === "ENOENT" || err instanceof Error && "status" in err && err.status === 404)) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* 兼容旧的 rmdir 方法,内部重定向到 rm
|
|
574
|
+
* @deprecated 请使用 rm
|
|
575
|
+
*/
|
|
576
|
+
async rmdir(path, options) {
|
|
577
|
+
let opts = {};
|
|
578
|
+
if (typeof options === "boolean") {
|
|
579
|
+
opts.recursive = options;
|
|
580
|
+
} else if (typeof options === "object" && options !== null) {
|
|
581
|
+
opts = options;
|
|
582
|
+
}
|
|
583
|
+
return this.rm(path, opts);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* 删除文件或目录
|
|
587
|
+
* @param path 文件或目录路径
|
|
588
|
+
* @returns 操作结果
|
|
589
|
+
*/
|
|
590
|
+
async _delete(path) {
|
|
591
|
+
const normalizedPath = normalizePath(path);
|
|
592
|
+
try {
|
|
593
|
+
const response = await this.request("DELETE", normalizedPath);
|
|
594
|
+
if (response.status >= 400) {
|
|
595
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
success: response.status >= 200 && response.status < 300,
|
|
599
|
+
statusCode: response.status
|
|
600
|
+
};
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if (error instanceof WebDAVError) {
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
throw new WebDAVError(`\u5220\u9664\u5931\u8D25: ${normalizedPath}`, void 0, error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* 获取文件或目录的统计信息
|
|
610
|
+
* @param path 文件或目录路径
|
|
611
|
+
* @returns 文件统计信息
|
|
612
|
+
*/
|
|
613
|
+
async stat(path) {
|
|
614
|
+
const normalizedPath = normalizePath(path);
|
|
615
|
+
try {
|
|
616
|
+
const headers = {
|
|
617
|
+
"Depth": "0",
|
|
618
|
+
"Content-Type": "application/xml"
|
|
619
|
+
};
|
|
620
|
+
const response = await this.request("PROPFIND", normalizedPath, {
|
|
621
|
+
headers
|
|
622
|
+
});
|
|
623
|
+
if (response.status === 404) {
|
|
624
|
+
throw new NotFoundError(normalizedPath);
|
|
625
|
+
} else if (response.status >= 400) {
|
|
626
|
+
this.handleResponseError(response.status, normalizedPath);
|
|
627
|
+
}
|
|
628
|
+
const files = parseWebDAVXml(response.data, normalizedPath);
|
|
629
|
+
if (files.length === 0) {
|
|
630
|
+
throw new NotFoundError(normalizedPath);
|
|
631
|
+
}
|
|
632
|
+
const stat = files[0];
|
|
633
|
+
return stat;
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (error instanceof WebDAVError) {
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
throw new WebDAVError(`\u83B7\u53D6\u6587\u4EF6\u4FE1\u606F\u5931\u8D25: ${normalizedPath}`, void 0, error);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* 检查文件或目录是否存在
|
|
643
|
+
* @param path 文件或目录路径
|
|
644
|
+
* @returns 是否存在
|
|
645
|
+
*/
|
|
646
|
+
async exists(path) {
|
|
647
|
+
try {
|
|
648
|
+
await this.stat(path);
|
|
649
|
+
return true;
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (error instanceof NotFoundError) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* 复制文件或目录
|
|
659
|
+
* @param source 源路径
|
|
660
|
+
* @param destination 目标路径
|
|
661
|
+
* @param overwrite 是否覆盖已存在的文件
|
|
662
|
+
* @returns 操作结果
|
|
663
|
+
*/
|
|
664
|
+
async copy(source, destination, overwrite = true) {
|
|
665
|
+
const normalizedSource = normalizePath(source);
|
|
666
|
+
const normalizedDestination = normalizePath(destination);
|
|
667
|
+
try {
|
|
668
|
+
await this.stat(normalizedSource);
|
|
669
|
+
if (!overwrite) {
|
|
670
|
+
const exists = await this.exists(normalizedDestination);
|
|
671
|
+
if (exists) {
|
|
672
|
+
throw new FileExistsError(normalizedDestination);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const headers = {
|
|
676
|
+
"Destination": joinUrl(this.baseUrl, normalizedDestination),
|
|
677
|
+
"Overwrite": overwrite ? "T" : "F"
|
|
678
|
+
};
|
|
679
|
+
const response = await this.request("COPY", normalizedSource, {
|
|
680
|
+
headers
|
|
681
|
+
});
|
|
682
|
+
if (response.status >= 400) {
|
|
683
|
+
this.handleResponseError(response.status, normalizedSource);
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
success: response.status >= 200 && response.status < 300,
|
|
687
|
+
statusCode: response.status
|
|
688
|
+
};
|
|
689
|
+
} catch (error) {
|
|
690
|
+
if (error instanceof WebDAVError) {
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
throw new WebDAVError(`\u590D\u5236\u5931\u8D25: ${normalizedSource} -> ${normalizedDestination}`, void 0, error);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* 移动文件或目录
|
|
698
|
+
* @param source 源路径
|
|
699
|
+
* @param destination 目标路径
|
|
700
|
+
* @param overwrite 是否覆盖已存在的文件
|
|
701
|
+
* @returns 操作结果
|
|
702
|
+
*/
|
|
703
|
+
async move(source, destination, overwrite = true) {
|
|
704
|
+
const normalizedSource = normalizePath(source);
|
|
705
|
+
const normalizedDestination = normalizePath(destination);
|
|
706
|
+
try {
|
|
707
|
+
await this.stat(normalizedSource);
|
|
708
|
+
if (!overwrite) {
|
|
709
|
+
const exists = await this.exists(normalizedDestination);
|
|
710
|
+
if (exists) {
|
|
711
|
+
throw new FileExistsError(normalizedDestination);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const headers = {
|
|
715
|
+
"Destination": joinUrl(this.baseUrl, normalizedDestination),
|
|
716
|
+
"Overwrite": overwrite ? "T" : "F"
|
|
717
|
+
};
|
|
718
|
+
const response = await this.request("MOVE", normalizedSource, {
|
|
719
|
+
headers
|
|
720
|
+
});
|
|
721
|
+
if (response.status >= 400) {
|
|
722
|
+
this.handleResponseError(response.status, normalizedSource);
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
success: response.status >= 200 && response.status < 300,
|
|
726
|
+
statusCode: response.status
|
|
727
|
+
};
|
|
728
|
+
} catch (error) {
|
|
729
|
+
if (error instanceof WebDAVError) {
|
|
730
|
+
throw error;
|
|
731
|
+
}
|
|
732
|
+
throw new WebDAVError(`\u79FB\u52A8\u5931\u8D25: ${normalizedSource} -> ${normalizedDestination}`, void 0, error);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* 删除文件(fs/unlink 兼容方法)
|
|
737
|
+
* @param path 文件路径
|
|
738
|
+
*/
|
|
739
|
+
async unlink(path) {
|
|
740
|
+
await this.deleteFile(path);
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
// src/webdav.ts
|
|
745
|
+
function createWebDAVFileSystem(options) {
|
|
746
|
+
return new WebDAVFS(options);
|
|
747
|
+
}
|
|
748
|
+
export {
|
|
749
|
+
WebDAVError,
|
|
750
|
+
createWebDAVFileSystem
|
|
751
|
+
};
|
|
752
|
+
//# sourceMappingURL=index.mjs.map
|