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/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