zen-fs-webdav 0.1.0 → 0.1.2
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.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +115 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -34
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +126 -39
- package/dist/index.umd.js.map +1 -1
- package/package.json +7 -6
package/dist/index.d.mts
CHANGED
|
@@ -275,4 +275,6 @@ declare class WebDAVError extends Error {
|
|
|
275
275
|
static fromError(error: unknown): WebDAVError;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
declare function parseWebDAVXml(xml: string, basePath: string): Stats[];
|
|
279
|
+
|
|
280
|
+
export { type CopyOptions, type FileEntry, type MkdirOptions, type MoveOptions, type ReadFileOptions, type ReaddirOptions, type RmdirOptions, type Stats, WebDAVError, type WebDAVFileSystem, type WebDAVOptions, type WebDAVRequestOptions, type WriteFileOptions, createWebDAVFileSystem, parseWebDAVXml };
|
package/dist/index.d.ts
CHANGED
|
@@ -275,4 +275,6 @@ declare class WebDAVError extends Error {
|
|
|
275
275
|
static fromError(error: unknown): WebDAVError;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
declare function parseWebDAVXml(xml: string, basePath: string): Stats[];
|
|
279
|
+
|
|
280
|
+
export { type CopyOptions, type FileEntry, type MkdirOptions, type MoveOptions, type ReadFileOptions, type ReaddirOptions, type RmdirOptions, type Stats, WebDAVError, type WebDAVFileSystem, type WebDAVOptions, type WebDAVRequestOptions, type WriteFileOptions, createWebDAVFileSystem, parseWebDAVXml };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// zen-fs-webdav - https://github.com/weijia/zen-fs-webdav
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
9
|
var __export = (target, all) => {
|
|
8
10
|
for (var name in all)
|
|
@@ -16,13 +18,22 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/index.ts
|
|
22
32
|
var index_exports = {};
|
|
23
33
|
__export(index_exports, {
|
|
24
34
|
WebDAVError: () => WebDAVError,
|
|
25
|
-
createWebDAVFileSystem: () => createWebDAVFileSystem
|
|
35
|
+
createWebDAVFileSystem: () => createWebDAVFileSystem,
|
|
36
|
+
parseWebDAVXml: () => parseWebDAVXml
|
|
26
37
|
});
|
|
27
38
|
module.exports = __toCommonJS(index_exports);
|
|
28
39
|
|
|
@@ -129,6 +140,7 @@ var XML_NAMESPACE_PREFIX = {
|
|
|
129
140
|
|
|
130
141
|
// src/utils.ts
|
|
131
142
|
var import_fast_xml_parser = require("fast-xml-parser");
|
|
143
|
+
var he = __toESM(require("he"));
|
|
132
144
|
function normalizePath(path) {
|
|
133
145
|
if (!path.startsWith("/")) {
|
|
134
146
|
path = "/" + path;
|
|
@@ -192,7 +204,7 @@ function getDirFromPath(path) {
|
|
|
192
204
|
return normalized.slice(0, idx) || "/";
|
|
193
205
|
}
|
|
194
206
|
function parseWebDAVXml(xml, basePath) {
|
|
195
|
-
const
|
|
207
|
+
const decode2 = he.decode;
|
|
196
208
|
const result = [];
|
|
197
209
|
if (!xml) return result;
|
|
198
210
|
const parser = new import_fast_xml_parser.XMLParser({
|
|
@@ -207,6 +219,12 @@ function parseWebDAVXml(xml, basePath) {
|
|
|
207
219
|
if (obj[key] !== void 0) return obj[key];
|
|
208
220
|
const found = Object.keys(obj).find((k) => k.toLowerCase() === key.toLowerCase());
|
|
209
221
|
if (found) return obj[found];
|
|
222
|
+
const keyWithoutPrefix = key.includes(":") ? key.split(":").pop() : key;
|
|
223
|
+
const foundWithNamespace = Object.keys(obj).find((k) => {
|
|
224
|
+
const kWithoutPrefix = k.includes(":") ? k.split(":").pop() : k;
|
|
225
|
+
return kWithoutPrefix.toLowerCase() === keyWithoutPrefix.toLowerCase();
|
|
226
|
+
});
|
|
227
|
+
if (foundWithNamespace) return obj[foundWithNamespace];
|
|
210
228
|
}
|
|
211
229
|
return void 0;
|
|
212
230
|
}
|
|
@@ -215,12 +233,15 @@ function parseWebDAVXml(xml, basePath) {
|
|
|
215
233
|
const arr = Array.isArray(responses) ? responses : [responses];
|
|
216
234
|
const isBasePathFile = basePath && !basePath.endsWith("/");
|
|
217
235
|
for (const item of arr) {
|
|
218
|
-
const
|
|
236
|
+
const hrefRaw = getCaseInsensitive(item, "d:href", "href");
|
|
237
|
+
const href = decode2(typeof hrefRaw === "string" ? hrefRaw : hrefRaw ? String(hrefRaw) : "");
|
|
219
238
|
const propstat = getCaseInsensitive(item, "d:propstat", "propstat", "d:prop", "prop") || {};
|
|
220
239
|
const prop = getCaseInsensitive(propstat, "d:prop", "prop") || propstat;
|
|
221
240
|
const resourcetype = getCaseInsensitive(prop, "d:resourcetype", "resourcetype");
|
|
222
241
|
const collection = resourcetype && getCaseInsensitive(resourcetype, "d:collection", "collection");
|
|
223
|
-
const
|
|
242
|
+
const getcontentlength = getCaseInsensitive(prop, "d:getcontentlength", "getcontentlength");
|
|
243
|
+
const hasCollection = !!(resourcetype && typeof resourcetype === "object" && collection !== void 0);
|
|
244
|
+
const isDirectory = hasCollection || !getcontentlength && href.endsWith("/");
|
|
224
245
|
let name;
|
|
225
246
|
if (isBasePathFile) {
|
|
226
247
|
name = href.split("/").filter(Boolean).pop() || "";
|
|
@@ -271,6 +292,29 @@ function joinUrl(base, ...paths) {
|
|
|
271
292
|
}
|
|
272
293
|
|
|
273
294
|
// src/webdav-fs.ts
|
|
295
|
+
var TextDecoderFallback = class {
|
|
296
|
+
constructor(encoding) {
|
|
297
|
+
this.encoding = encoding;
|
|
298
|
+
}
|
|
299
|
+
decode(buffer) {
|
|
300
|
+
const uint8Array = new Uint8Array(buffer);
|
|
301
|
+
return Array.from(uint8Array).map((byte) => String.fromCharCode(byte)).join("");
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
var TextDecoderImpl;
|
|
305
|
+
if (typeof TextDecoder !== "undefined") {
|
|
306
|
+
TextDecoderImpl = TextDecoder;
|
|
307
|
+
} else {
|
|
308
|
+
try {
|
|
309
|
+
if (typeof global !== "undefined" && global.TextDecoder) {
|
|
310
|
+
TextDecoderImpl = global.TextDecoder;
|
|
311
|
+
} else {
|
|
312
|
+
TextDecoderImpl = TextDecoderFallback;
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
TextDecoderImpl = TextDecoderFallback;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
274
318
|
var WebDAVFS = class {
|
|
275
319
|
/**
|
|
276
320
|
* 创建WebDAV文件系统实例
|
|
@@ -347,11 +391,10 @@ var WebDAVFS = class {
|
|
|
347
391
|
};
|
|
348
392
|
} catch (error) {
|
|
349
393
|
if (timeoutId) clearTimeout(timeoutId);
|
|
350
|
-
if (error
|
|
394
|
+
if (error.name === "AbortError") {
|
|
351
395
|
throw new TimeoutError(`\u8BF7\u6C42\u8D85\u65F6: ${url}`);
|
|
352
396
|
} else {
|
|
353
|
-
|
|
354
|
-
throw new NetworkError(error instanceof Error ? error : new Error(String(error)), `\u7F51\u7EDC\u9519\u8BEF: ${errorMessage}`);
|
|
397
|
+
throw new NetworkError(error, `\u7F51\u7EDC\u9519\u8BEF: ${error.message}`);
|
|
355
398
|
}
|
|
356
399
|
}
|
|
357
400
|
}
|
|
@@ -378,11 +421,17 @@ var WebDAVFS = class {
|
|
|
378
421
|
/**
|
|
379
422
|
* 读取文件内容
|
|
380
423
|
* @param path 文件路径
|
|
381
|
-
* @param
|
|
424
|
+
* @param encodingOrOptions 编码字符串或读取选项对象
|
|
382
425
|
* @returns 文件内容
|
|
383
426
|
*/
|
|
384
|
-
async readFile(path,
|
|
427
|
+
async readFile(path, encodingOrOptions) {
|
|
385
428
|
const normalizedPath = normalizePath(path);
|
|
429
|
+
let options = {};
|
|
430
|
+
if (typeof encodingOrOptions === "string") {
|
|
431
|
+
options = { encoding: encodingOrOptions };
|
|
432
|
+
} else if (encodingOrOptions) {
|
|
433
|
+
options = encodingOrOptions;
|
|
434
|
+
}
|
|
386
435
|
try {
|
|
387
436
|
const response = await this.request("GET", normalizedPath, {
|
|
388
437
|
headers: options.headers,
|
|
@@ -391,13 +440,16 @@ var WebDAVFS = class {
|
|
|
391
440
|
if (response.status >= 400) {
|
|
392
441
|
this.handleResponseError(response.status, normalizedPath);
|
|
393
442
|
}
|
|
394
|
-
const arrayBuffer = response.data;
|
|
395
|
-
const uint8Array = new Uint8Array(arrayBuffer);
|
|
396
|
-
const buffer = Buffer.from(uint8Array);
|
|
443
|
+
const arrayBuffer = response.data instanceof ArrayBuffer ? response.data : response.data.buffer || response.data;
|
|
397
444
|
if (options.encoding) {
|
|
398
|
-
|
|
445
|
+
const decoder = new TextDecoderImpl(options.encoding);
|
|
446
|
+
return decoder.decode(arrayBuffer);
|
|
399
447
|
} else {
|
|
400
|
-
|
|
448
|
+
if (typeof Buffer !== "undefined") {
|
|
449
|
+
return Buffer.from(arrayBuffer);
|
|
450
|
+
} else {
|
|
451
|
+
return new Uint8Array(arrayBuffer);
|
|
452
|
+
}
|
|
401
453
|
}
|
|
402
454
|
} catch (error) {
|
|
403
455
|
if (error instanceof WebDAVError) {
|
|
@@ -520,34 +572,47 @@ var WebDAVFS = class {
|
|
|
520
572
|
*/
|
|
521
573
|
async mkdir(path, options = {}) {
|
|
522
574
|
const normalizedPath = normalizePath(path);
|
|
575
|
+
let pathExists = false;
|
|
576
|
+
let isDirectory = false;
|
|
523
577
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
578
|
+
const stat = await this.stat(normalizedPath);
|
|
579
|
+
pathExists = true;
|
|
580
|
+
isDirectory = stat.isDirectory;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
pathExists = false;
|
|
583
|
+
}
|
|
584
|
+
if (pathExists) {
|
|
585
|
+
if (isDirectory) {
|
|
586
|
+
if (options.recursive) {
|
|
527
587
|
return;
|
|
528
588
|
}
|
|
529
589
|
throw new FileExistsError(normalizedPath);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
throw error;
|
|
533
|
-
}
|
|
590
|
+
} else {
|
|
591
|
+
throw new FileExistsError(normalizedPath);
|
|
534
592
|
}
|
|
535
|
-
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
if (options.recursive) {
|
|
536
596
|
const parentDir = getDirFromPath(normalizedPath);
|
|
537
597
|
if (parentDir !== "/" && parentDir !== normalizedPath) {
|
|
538
598
|
try {
|
|
539
599
|
await this.stat(parentDir);
|
|
540
600
|
} catch (error) {
|
|
541
|
-
|
|
542
|
-
await this.mkdir(parentDir, options);
|
|
543
|
-
} else {
|
|
544
|
-
throw error;
|
|
545
|
-
}
|
|
601
|
+
await this.mkdir(parentDir, options);
|
|
546
602
|
}
|
|
547
603
|
}
|
|
548
604
|
}
|
|
549
605
|
const response = await this.request("MKCOL", normalizedPath);
|
|
550
606
|
if (response.status >= 400) {
|
|
607
|
+
if (response.status === 405 && options.recursive) {
|
|
608
|
+
try {
|
|
609
|
+
const stat = await this.stat(normalizedPath);
|
|
610
|
+
if (stat.isDirectory) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
} catch (e) {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
551
616
|
this.handleResponseError(response.status, normalizedPath);
|
|
552
617
|
}
|
|
553
618
|
return;
|
|
@@ -583,7 +648,8 @@ var WebDAVFS = class {
|
|
|
583
648
|
}
|
|
584
649
|
await this._delete(path);
|
|
585
650
|
} catch (err) {
|
|
586
|
-
|
|
651
|
+
const error = err;
|
|
652
|
+
if (force && (error.code === "ENOENT" || error.status === 404)) {
|
|
587
653
|
return;
|
|
588
654
|
}
|
|
589
655
|
throw err;
|
|
@@ -759,6 +825,27 @@ var WebDAVFS = class {
|
|
|
759
825
|
async unlink(path) {
|
|
760
826
|
await this.deleteFile(path);
|
|
761
827
|
}
|
|
828
|
+
// ============================================
|
|
829
|
+
// Node.js fs.promises 兼容接口
|
|
830
|
+
// 这些方法使 WebDAVFS 可以直接作为 IFileSystem 使用
|
|
831
|
+
// ============================================
|
|
832
|
+
/**
|
|
833
|
+
* 读取目录(fs.promises.readdir 兼容)
|
|
834
|
+
* @param path 目录路径
|
|
835
|
+
* @returns 文件名数组
|
|
836
|
+
*/
|
|
837
|
+
async readdir(path) {
|
|
838
|
+
const stats = await this.readDir(path);
|
|
839
|
+
return stats.map((stat) => stat.name);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* 重命名/移动文件(fs.promises.rename 兼容)
|
|
843
|
+
* @param oldPath 原路径
|
|
844
|
+
* @param newPath 新路径
|
|
845
|
+
*/
|
|
846
|
+
async rename(oldPath, newPath) {
|
|
847
|
+
await this.move(oldPath, newPath, true);
|
|
848
|
+
}
|
|
762
849
|
};
|
|
763
850
|
|
|
764
851
|
// src/webdav.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/constants.ts","../src/utils.ts","../src/webdav-fs.ts","../src/webdav.ts"],"sourcesContent":["/**\r\n * zen-fs-webdav\r\n * 一个简单、现代的 WebDAV 客户端库,提供类似文件系统的 API\r\n */\r\n\r\n// 导出主类\r\nexport { WebDAVFileSystem } from './WebDAVFileSystem';\r\nexport { createWebDAVFileSystem } from './webdav';\r\n\r\n// 导出错误类\r\nexport { WebDAVError } from './errors';\r\n\r\n// 导出类型定义\r\nexport {\r\n WebDAVOptions,\r\n WebDAVRequestOptions,\r\n FileEntry,\r\n Stats,\r\n ReadFileOptions,\r\n WriteFileOptions,\r\n MkdirOptions,\r\n RmdirOptions,\r\n ReaddirOptions,\r\n CopyOptions,\r\n MoveOptions,\r\n} from './types';","/**\r\n * WebDAV文件系统库的错误类型定义\r\n */\r\n\r\n/**\r\n * WebDAV错误基类\r\n */\r\nexport class WebDAVError extends Error {\r\n /**\r\n * HTTP状态码\r\n */\r\n status?: number;\r\n /**\r\n * 原始错误对象(如果有)\r\n */\r\n cause?: unknown;\r\n\r\n constructor(message: string, status?: number, cause?: unknown) {\r\n super(message);\r\n this.name = 'WebDAVError';\r\n this.status = status;\r\n this.cause = cause;\r\n // 兼容 TS 的 Error 子类\r\n Object.setPrototypeOf(this, WebDAVError.prototype);\r\n }\r\n\r\n toString(): string {\r\n if (typeof this.status === 'number') {\r\n return `${this.name}: ${this.message} (Status: ${this.status})`;\r\n }\r\n return `${this.name}: ${this.message}`;\r\n }\r\n\r\n static fromResponse(response: { status: number; statusText: string }, message?: string): WebDAVError {\r\n const msg = message\r\n ? `${message}: ${response.status} ${response.statusText}`\r\n : `${response.status} ${response.statusText}`;\r\n return new WebDAVError(msg, response.status);\r\n }\r\n\r\n static fromError(error: unknown): WebDAVError {\r\n if (error instanceof WebDAVError) {\r\n return error;\r\n }\r\n if (error instanceof Error) {\r\n const msg = error.message || 'Unknown WebDAV error';\r\n return new WebDAVError(msg, undefined, error);\r\n }\r\n return new WebDAVError('Unknown WebDAV error');\r\n }\r\n}\r\n\r\n/**\r\n * 文件或目录不存在错误\r\n */\r\nexport class NotFoundError extends WebDAVError {\r\n constructor(path: string) {\r\n super(`文件未找到: ${path}`, 404);\r\n this.name = 'NotFoundError';\r\n Object.setPrototypeOf(this, NotFoundError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 认证错误\r\n */\r\nexport class AuthenticationError extends WebDAVError {\r\n constructor(message = '认证失败') {\r\n super(message, 401);\r\n this.name = 'AuthenticationError';\r\n Object.setPrototypeOf(this, AuthenticationError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 授权错误\r\n */\r\nexport class AuthorizationError extends WebDAVError {\r\n constructor(message = '权限被拒绝') {\r\n super(message, 403);\r\n this.name = 'AuthorizationError';\r\n Object.setPrototypeOf(this, AuthorizationError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 服务器错误\r\n */\r\nexport class ServerError extends WebDAVError {\r\n constructor(message = '服务器错误', statusCode = 500) {\r\n super(message, statusCode);\r\n this.name = 'ServerError';\r\n Object.setPrototypeOf(this, ServerError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 文件已存在错误\r\n */\r\nexport class FileExistsError extends WebDAVError {\r\n constructor(path: string) {\r\n super(`文件已存在: ${path}`, 412);\r\n this.name = 'FileExistsError';\r\n Object.setPrototypeOf(this, FileExistsError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 锁定错误\r\n */\r\nexport class LockError extends WebDAVError {\r\n constructor(path: string, message = '资源被锁定') {\r\n super(`${message}: ${path}`, 423);\r\n this.name = 'LockError';\r\n Object.setPrototypeOf(this, LockError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 超时错误\r\n */\r\nexport class TimeoutError extends WebDAVError {\r\n constructor(message = '连接超时') {\r\n super(message, 408);\r\n this.name = 'TimeoutError';\r\n Object.setPrototypeOf(this, TimeoutError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 网络错误\r\n */\r\nexport class NetworkError extends WebDAVError {\r\n constructor(originalError?: Error, message = '网络错误') {\r\n super(originalError ? `${message}: ${originalError.message}` : message, 0, originalError);\r\n this.name = 'NetworkError';\r\n Object.setPrototypeOf(this, NetworkError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 参数错误\r\n */\r\nexport class ArgumentError extends WebDAVError {\r\n constructor(message: string) {\r\n super(`无效参数: ${message}`);\r\n this.name = 'ArgumentError';\r\n Object.setPrototypeOf(this, ArgumentError.prototype);\r\n }\r\n}","/**\n * WebDAV 相关常量\n */\n\n/**\n * WebDAV 方法\n */\nexport enum WebDAVMethod {\n GET = 'GET',\n PUT = 'PUT',\n POST = 'POST',\n DELETE = 'DELETE',\n PROPFIND = 'PROPFIND',\n PROPPATCH = 'PROPPATCH',\n MKCOL = 'MKCOL',\n COPY = 'COPY',\n MOVE = 'MOVE',\n LOCK = 'LOCK',\n UNLOCK = 'UNLOCK',\n}\n\n/**\n * WebDAV 命名空间\n */\nexport const WebDAVNamespace = {\n DAV: 'DAV:',\n CALDAV: 'urn:ietf:params:xml:ns:caldav',\n CARDDAV: 'urn:ietf:params:xml:ns:carddav',\n OWNCLOUD: 'http://owncloud.org/ns',\n NEXTCLOUD: 'http://nextcloud.org/ns',\n};\n\n/**\n * WebDAV 属性名称\n */\nexport const WebDAVPropName = {\n RESOURCE_TYPE: 'resourcetype',\n DISPLAY_NAME: 'displayname',\n GET_CONTENT_LENGTH: 'getcontentlength',\n GET_CONTENT_TYPE: 'getcontenttype',\n GET_LAST_MODIFIED: 'getlastmodified',\n CREATION_DATE: 'creationdate',\n ETAG: 'getetag',\n};\n\n/**\n * WebDAV 资源类型\n */\nexport enum WebDAVResourceType {\n FILE = 'file',\n DIRECTORY = 'directory',\n COLLECTION = 'collection',\n}\n\n/**\n * WebDAV 深度\n */\nexport enum WebDAVDepth {\n /**\n * 仅当前资源\n */\n ZERO = '0',\n \n /**\n * 当前资源及其直接子资源\n */\n ONE = '1',\n \n /**\n * 当前资源及其所有后代资源\n */\n INFINITY = 'infinity',\n}\n\n/**\n * 默认超时时间(毫秒)\n */\nexport const DEFAULT_TIMEOUT = 30000;\n\n/**\n * 默认请求头\n */\nexport const DEFAULT_HEADERS = {\n 'Content-Type': 'application/xml; charset=utf-8',\n 'Accept': 'application/xml, */*',\n};\n\n/**\n * XML 命名空间前缀\n */\nexport const XML_NAMESPACE_PREFIX = {\n 'd': WebDAVNamespace.DAV,\n 'oc': WebDAVNamespace.OWNCLOUD,\n 'nc': WebDAVNamespace.NEXTCLOUD,\n};\n\n/**\n * 错误消息\n */\nexport const ErrorMessage = {\n INVALID_URL: '无效的 URL',\n TIMEOUT: '请求超时',\n NETWORK_ERROR: '网络错误',\n UNAUTHORIZED: '未授权',\n FORBIDDEN: '禁止访问',\n NOT_FOUND: '资源不存在',\n METHOD_NOT_ALLOWED: '方法不允许',\n CONFLICT: '资源冲突',\n PRECONDITION_FAILED: '前提条件失败',\n INTERNAL_SERVER_ERROR: '服务器内部错误',\n NOT_IMPLEMENTED: '未实现',\n BAD_GATEWAY: '错误的网关',\n SERVICE_UNAVAILABLE: '服务不可用',\n GATEWAY_TIMEOUT: '网关超时',\n};\n\n/**\n * HTTP 状态码\n */\nexport enum HttpStatus {\n OK = 200,\n CREATED = 201,\n NO_CONTENT = 204,\n MULTI_STATUS = 207,\n BAD_REQUEST = 400,\n UNAUTHORIZED = 401,\n FORBIDDEN = 403,\n NOT_FOUND = 404,\n METHOD_NOT_ALLOWED = 405,\n CONFLICT = 409,\n PRECONDITION_FAILED = 412,\n INTERNAL_SERVER_ERROR = 500,\n NOT_IMPLEMENTED = 501,\n BAD_GATEWAY = 502,\n SERVICE_UNAVAILABLE = 503,\n GATEWAY_TIMEOUT = 504,\n}","import { WebDAVNamespace, WebDAVPropName, WebDAVResourceType } from './constants';\r\nimport { Stats } from './types';\r\nimport { WebDAVError } from './errors';\r\nimport { XMLParser } from 'fast-xml-parser'\r\n\r\n/**\r\n * 规范化路径,确保以 / 开头,不以 / 结尾(除非是根路径)\r\n * @param path 路径\r\n * @returns 规范化后的路径\r\n */\r\nexport function normalizePath(path: string): string {\r\n // 确保路径以 / 开头\r\n if (!path.startsWith('/')) {\r\n path = '/' + path;\r\n }\r\n // 合并多个连续的斜杠为一个\r\n path = path.replace(/\\/+/g, '/');\r\n // 如果不是根路径,则确保不以 / 结尾\r\n if (path.length > 1 && path.endsWith('/')) {\r\n path = path.slice(0, -1);\r\n }\r\n return path;\r\n}\r\n\r\n// /**\r\n// * 连接路径\r\n// * @param base 基础路径\r\n// * @param paths 要连接的路径\r\n// * @returns 连接后的路径\r\n// */\r\n// export function joinPaths(base: string, ...paths: string[]): string {\r\n// let result = base;\r\n \r\n// for (const path of paths) {\r\n// if (!path) continue;\r\n \r\n// if (result.endsWith('/')) {\r\n// result = result + (path.startsWith('/') ? path.slice(1) : path);\r\n// } else {\r\n// result = result + (path.startsWith('/') ? path : '/' + path);\r\n// }\r\n// }\r\n \r\n// return normalizePath(result);\r\n// }\r\n\r\n/**\r\n * 获取路径的父目录\r\n * @param path 路径\r\n * @returns 父目录路径\r\n */\r\nexport function getParentPath(path: string): string {\r\n path = normalizePath(path);\r\n \r\n // 根路径没有父目录\r\n if (path === '/') {\r\n return '/';\r\n }\r\n \r\n const lastSlashIndex = path.lastIndexOf('/');\r\n if (lastSlashIndex <= 0) {\r\n return '/';\r\n }\r\n \r\n return path.slice(0, lastSlashIndex) || '/';\r\n}\r\n\r\n/**\r\n * 获取路径的基本名称(最后一个部分)\r\n * @param path 路径\r\n * @returns 基本名称\r\n */\r\nexport function getBasename(path: string): string {\r\n path = normalizePath(path);\r\n \r\n // 根路径的基本名称为空字符串\r\n if (path === '/') {\r\n return '';\r\n }\r\n \r\n const lastSlashIndex = path.lastIndexOf('/');\r\n return path.slice(lastSlashIndex + 1);\r\n}\r\n\r\n/**\r\n * 创建基本认证头\r\n * @param username 用户名\r\n * @param password 密码\r\n * @returns 基本认证头\r\n */\r\nexport function createBasicAuthHeader(username: string, password: string): string {\r\n // 在浏览器和 Node.js 环境中都可用的 btoa 实现\r\n const btoa = (str: string) => {\r\n if (typeof window !== 'undefined' && window.btoa) {\r\n return window.btoa(str);\r\n } else if (typeof Buffer !== 'undefined') {\r\n return Buffer.from(str).toString('base64');\r\n } else {\r\n throw new Error('Base64 encoding not available');\r\n }\r\n };\r\n \r\n return `Basic ${btoa(`${username}:${password}`)}`;\r\n}\r\n\r\n/**\r\n * 解析 WebDAV 响应中的属性\r\n * @param xml XML 文档\r\n * @returns 解析后的属性对象\r\n */\r\nexport function parseWebDAVProperties(xml: Document): Record<string, any> {\r\n const result: Record<string, any> = {};\r\n \r\n // 查找所有 prop 元素\r\n const propElements = xml.getElementsByTagNameNS(WebDAVNamespace.DAV, 'prop');\r\n \r\n for (let i = 0; i < propElements.length; i++) {\r\n const propElement = propElements[i];\r\n \r\n // 遍历 prop 元素的子元素\r\n for (let j = 0; j < propElement.childNodes.length; j++) {\r\n const childNode = propElement.childNodes[j];\r\n \r\n if (childNode.nodeType === Node.ELEMENT_NODE) {\r\n const element = childNode as Element;\r\n const localName = element.localName;\r\n const namespace = element.namespaceURI;\r\n \r\n // 根据属性类型进行特殊处理\r\n if (localName === WebDAVPropName.RESOURCE_TYPE) {\r\n // 资源类型\r\n if (element.getElementsByTagNameNS(WebDAVNamespace.DAV, 'collection').length > 0) {\r\n result.resourceType = WebDAVResourceType.DIRECTORY;\r\n } else {\r\n result.resourceType = WebDAVResourceType.FILE;\r\n }\r\n } else if (localName === WebDAVPropName.GET_CONTENT_LENGTH) {\r\n // 内容长度\r\n result.size = parseInt(element.textContent || '0', 10);\r\n } else if (localName === WebDAVPropName.GET_LAST_MODIFIED) {\r\n // 最后修改时间\r\n result.lastModified = new Date(element.textContent || '');\r\n } else if (localName === WebDAVPropName.CREATION_DATE) {\r\n // 创建时间\r\n result.createdAt = new Date(element.textContent || '');\r\n } else if (localName === WebDAVPropName.DISPLAY_NAME) {\r\n // 显示名称\r\n result.displayName = element.textContent || '';\r\n } else if (localName === WebDAVPropName.GET_CONTENT_TYPE) {\r\n // 内容类型\r\n result.mimeType = element.textContent || '';\r\n } else if (localName === WebDAVPropName.ETAG) {\r\n // ETag\r\n result.etag = element.textContent || '';\r\n } else {\r\n // 其他属性\r\n const key = `${namespace || ''}:${localName}`;\r\n result[key] = element.textContent || '';\r\n }\r\n }\r\n }\r\n }\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * 解析 WebDAV 多状态响应\r\n * @param xml XML 文档\r\n * @returns 解析后的响应数组\r\n */\r\nexport function parseMultiStatus(xml: Document): Array<{\r\n href: string;\r\n status?: string;\r\n statusCode?: number;\r\n properties: Record<string, any>;\r\n}> {\r\n const result: Array<{\r\n href: string;\r\n status?: string;\r\n statusCode?: number;\r\n properties: Record<string, any>;\r\n }> = [];\r\n \r\n // 查找所有 response 元素\r\n const responseElements = xml.getElementsByTagNameNS(WebDAVNamespace.DAV, 'response');\r\n \r\n for (let i = 0; i < responseElements.length; i++) {\r\n const responseElement = responseElements[i];\r\n \r\n // 获取 href\r\n const hrefElement = responseElement.getElementsByTagNameNS(WebDAVNamespace.DAV, 'href')[0];\r\n const href = hrefElement ? decodeURIComponent(hrefElement.textContent || '') : '';\r\n \r\n // 获取状态\r\n const statusElement = responseElement.getElementsByTagNameNS(WebDAVNamespace.DAV, 'status')[0];\r\n const status = statusElement ? statusElement.textContent || '' : undefined;\r\n \r\n // 解析状态码\r\n let statusCode: number | undefined;\r\n if (status) {\r\n const match = status.match(/HTTP\\/\\d+\\.\\d+\\s+(\\d+)/);\r\n if (match) {\r\n statusCode = parseInt(match[1], 10);\r\n }\r\n }\r\n \r\n // 解析属性\r\n const properties = parseWebDAVProperties(responseElement as unknown as Document);\r\n \r\n result.push({\r\n href,\r\n status,\r\n statusCode,\r\n properties,\r\n });\r\n }\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * 创建 PROPFIND 请求体\r\n * @param props 要查询的属性\r\n * @returns XML 字符串\r\n */\r\nexport function createPropfindXml(props: string[] = []): string {\r\n if (props.length === 0) {\r\n // 查询所有属性\r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propfind xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:allprop/>\r\n</d:propfind>`;\r\n } else {\r\n // 查询指定属性\r\n const propXml = props.map(prop => `<d:${prop}/>`).join('');\r\n \r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propfind xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:prop>\r\n ${propXml}\r\n </d:prop>\r\n</d:propfind>`;\r\n }\r\n}\r\n\r\n/**\r\n * 创建 PROPPATCH 请求体\r\n * @param props 要设置的属性\r\n * @returns XML 字符串\r\n */\r\nexport function createProppatchXml(props: Record<string, string>): string {\r\n const propXml = Object.entries(props)\r\n .map(([key, value]) => `<d:${key}>${value}</d:${key}>`)\r\n .join('');\r\n \r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propertyupdate xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:set>\r\n <d:prop>\r\n ${propXml}\r\n </d:prop>\r\n </d:set>\r\n</d:propertyupdate>`;\r\n}\r\n\r\n/**\r\n * 将 WebDAV 属性转换为 Stats 对象\r\n * @param href 资源路径\r\n * @param properties 属性\r\n * @returns Stats 对象\r\n */\r\nexport function propertiesToStats(href: string, properties: Record<string, any>): Stats {\r\n const name = getBasename(href);\r\n const isDirectory = properties.resourceType === WebDAVResourceType.DIRECTORY;\r\n \r\n return {\r\n isDirectory,\r\n isFile: !isDirectory,\r\n size: properties.size || 0,\r\n createdAt: properties.createdAt || new Date(),\r\n lastModified: properties.modifiedAt || new Date(),\r\n name,\r\n path: href,\r\n mimeType: properties.mimeType,\r\n etag: properties.etag,\r\n ...properties,\r\n };\r\n}\r\n\r\n/**\r\n * 解析 XML 字符串\r\n * @param xmlString XML 字符串\r\n * @returns XML 文档\r\n */\r\nexport function parseXml(xmlString: string): Document {\r\n if (typeof DOMParser !== 'undefined') {\r\n // 浏览器环境\r\n const parser = new DOMParser();\r\n return parser.parseFromString(xmlString, 'application/xml');\r\n } else if (typeof window === 'undefined') {\r\n // Node.js 环境\r\n try {\r\n // 尝试使用 xmldom\r\n const { DOMParser } = require('xmldom');\r\n return new DOMParser().parseFromString(xmlString, 'application/xml');\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error);\r\n throw new WebDAVError(`无法解析 XML: ${errorMessage}。在 Node.js 环境中,请安装 xmldom 包。`);\r\n }\r\n } else {\r\n throw new WebDAVError('无法解析 XML。未找到 XML 解析器。');\r\n }\r\n}\r\n\r\n/**\r\n * 检查响应是否成功\r\n * @param response 响应对象\r\n * @returns 是否成功\r\n */\r\nexport function isSuccessStatus(status: number): boolean {\r\n return status >= 200 && status < 300;\r\n}\r\n\r\n/**\r\n * 从 URL 中提取路径\r\n * @param url URL\r\n * @param baseUrl 基础 URL\r\n * @returns 路径\r\n */\r\nexport function extractPathFromUrl(url: string, baseUrl: string): string {\r\n // 移除基础 URL\r\n if (url.startsWith(baseUrl)) {\r\n url = url.slice(baseUrl.length);\r\n }\r\n \r\n // 解码 URL\r\n url = decodeURIComponent(url);\r\n \r\n // 规范化路径\r\n return normalizePath(url);\r\n}\r\n\r\n/**\r\n * 创建超时 Promise\r\n * @param ms 超时时间(毫秒)\r\n * @returns Promise\r\n */\r\nexport function createTimeoutPromise(ms: number): Promise<never> {\r\n return new Promise((_, reject) => {\r\n setTimeout(() => {\r\n reject(new WebDAVError(`请求超时(${ms}ms)`));\r\n }, ms);\r\n });\r\n}\r\n\r\n/**\r\n * 创建 AbortController\r\n * @returns AbortController 或 undefined\r\n */\r\nexport function createAbortController(): AbortController | undefined {\r\n if (typeof AbortController !== 'undefined') {\r\n return new AbortController();\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * 检查是否支持 Blob\r\n * @returns 是否支持\r\n */\r\nexport function isBlobSupported(): boolean {\r\n return typeof Blob !== 'undefined';\r\n}\r\n\r\n/**\r\n * 检查是否支持 ArrayBuffer\r\n * @returns 是否支持\r\n */\r\nexport function isArrayBufferSupported(): boolean {\r\n return typeof ArrayBuffer !== 'undefined';\r\n}\r\n\r\n/**\r\n * 将数据转换为 ArrayBuffer\r\n * @param data 数据\r\n * @returns ArrayBuffer\r\n */\r\nexport async function toArrayBuffer(data: string | ArrayBuffer | Blob): Promise<ArrayBuffer> {\r\n if (data instanceof ArrayBuffer) {\r\n return data;\r\n } else if (typeof Blob !== 'undefined' && data instanceof Blob) {\r\n return await data.arrayBuffer();\r\n } else if (typeof data === 'string') {\r\n if (typeof TextEncoder !== 'undefined') {\r\n return new TextEncoder().encode(data).buffer;\r\n } else if (typeof Buffer !== 'undefined') {\r\n return Buffer.from(data).buffer;\r\n } else {\r\n throw new WebDAVError('无法将字符串转换为 ArrayBuffer');\r\n }\r\n } else {\r\n throw new WebDAVError('不支持的数据类型');\r\n }\r\n}\r\n\r\n/**\r\n * 将 ArrayBuffer 转换为字符串\r\n * @param buffer ArrayBuffer\r\n * @param encoding 编码\r\n * @returns 字符串\r\n */\r\nexport function arrayBufferToString(buffer: ArrayBuffer, encoding = 'utf-8'): string {\r\n if (typeof TextDecoder !== 'undefined') {\r\n // 浏览器环境\r\n const decoder = new TextDecoder(encoding);\r\n return decoder.decode(buffer);\r\n } else if (typeof Buffer !== 'undefined') {\r\n // Node.js 环境\r\n return Buffer.from(buffer).toString(encoding as BufferEncoding);\r\n } else {\r\n throw new WebDAVError('无法将 ArrayBuffer 转换为字符串,当前环境不支持 TextDecoder 或 Buffer');\r\n }\r\n}\r\n\r\n/**\r\n * 根据文件名获取Content-Type\r\n * @param filename 文件名\r\n * @returns Content-Type字符串\r\n */\r\nexport function getContentType(filename: string): string {\r\n const ext = filename.split('.').pop()?.toLowerCase();\r\n switch (ext) {\r\n case 'txt': return 'text/plain';\r\n case 'html': case 'htm': return 'text/html';\r\n case 'json': return 'application/json';\r\n case 'xml': return 'application/xml';\r\n case 'jpg': case 'jpeg': return 'image/jpeg';\r\n case 'png': return 'image/png';\r\n case 'gif': return 'image/gif';\r\n case 'pdf': return 'application/pdf';\r\n case 'csv': return 'text/csv';\r\n case 'js': return 'application/javascript';\r\n case 'css': return 'text/css';\r\n case 'zip': return 'application/zip';\r\n default: return 'application/octet-stream';\r\n }\r\n}\r\n\r\n// 获取路径的父目录\r\nexport function getDirFromPath(path: string): string {\r\n if (!path || path === '/') return '/';\r\n const normalized = path.replace(/\\/+$/, '');\r\n const idx = normalized.lastIndexOf('/');\r\n if (idx <= 0) return '/';\r\n return normalized.slice(0, idx) || '/';\r\n}\r\n\r\n/**\r\n * 解析WebDAV PROPFIND XML响应,返回文件/目录信息数组\r\n * @param xml XML字符串\r\n * @param basePath 基础路径\r\n * @returns Stats[]\r\n */\r\n\r\nexport function parseWebDAVXml(xml: string, basePath: string): Stats[] {\r\n // 简单实现,实际可用 xml2js、fast-xml-parser 等库解析\r\n // 这里只做最基础的兼容,建议根据实际WebDAV响应完善\r\n const decode = require('he').decode;\r\n const result: Stats[] = [];\r\n if (!xml) return result;\r\n const parser = new XMLParser({\r\n ignoreAttributes: false,\r\n attributeNamePrefix: \"@_\",\r\n trimValues: true\r\n });\r\n const json = parser.parse(xml);\r\n // 兼容不同大小写的 multistatus/response 属性\r\n function getCaseInsensitive(obj: unknown, ...keys: string[]): unknown {\r\n if (!obj) return undefined;\r\n for (const key of keys) {\r\n if ((obj as Record<string, unknown>)[key] !== undefined) return (obj as Record<string, unknown>)[key];\r\n const found = Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase());\r\n if (found) return (obj as Record<string, unknown>)[found];\r\n }\r\n return undefined;\r\n }\r\n const multistatus = getCaseInsensitive(json, 'd:multistatus', 'multistatus');\r\n const responses = getCaseInsensitive(multistatus, 'd:response', 'response') || [];\r\n const arr = Array.isArray(responses) ? responses : [responses];\r\n const isBasePathFile = basePath && !basePath.endsWith('/');\r\n for (const item of arr) {\r\n const href = decode(getCaseInsensitive(item, 'd:href', 'href') || '');\r\n const propstat = getCaseInsensitive(item, 'd:propstat', 'propstat', 'd:prop', 'prop') || {};\r\n const prop = getCaseInsensitive(propstat, 'd:prop', 'prop') || propstat;\r\n const resourcetype = getCaseInsensitive(prop, 'd:resourcetype', 'resourcetype');\r\n const collection = resourcetype && getCaseInsensitive(resourcetype, 'd:collection', 'collection');\r\n const isDirectory = !!(resourcetype && collection !== undefined);\r\n\r\n // 如果basePath是文件,则直接取文件名,否则去除basePath前缀\r\n let name: string;\r\n if (isBasePathFile) {\r\n name = href.split('/').filter(Boolean).pop() || '';\r\n } else {\r\n name = href.replace(basePath, '').replace(/^\\//, '').replace(/\\/$/, '');\r\n }\r\n if (!name) continue;\r\n result.push({\r\n path: href,\r\n name,\r\n isDirectory,\r\n isFile: !isDirectory,\r\n size: parseInt(String(getCaseInsensitive(prop, 'd:getcontentlength', 'getcontentlength') ?? '0'), 10),\r\n lastModified: getCaseInsensitive(prop, 'd:getlastmodified', 'getlastmodified')\r\n ? new Date(String(getCaseInsensitive(prop, 'd:getlastmodified', 'getlastmodified')))\r\n : undefined,\r\n });\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * 拼接URL路径,自动处理斜杠\r\n * @param base 基础URL\r\n * @param paths 追加的路径\r\n * @returns 拼接后的URL\r\n */\r\nexport function joinUrl(base: string, ...paths: string[]): string {\r\n let url = base;\r\n let basePath = '';\r\n try {\r\n const u = new URL(base);\r\n basePath = u.pathname.replace(/\\/+$/, '');\r\n } catch {\r\n basePath = base.startsWith('/') ? base.replace(/\\/+$/, '') : '';\r\n }\r\n // 只处理第一个 path,只去除与 basePath 第一部分相同的部分\r\n if (paths.length > 0 && basePath) {\r\n let p = paths[0];\r\n if (p) {\r\n // 取 basePath 的第一部分\r\n const baseFirst = basePath.split('/').filter(Boolean)[0];\r\n const pParts = p.split('/').filter(Boolean);\r\n if (baseFirst && pParts[0] === baseFirst) {\r\n pParts.shift();\r\n p = pParts.join('/');\r\n if (p && !p.startsWith('/')) p = '/' + p;\r\n paths[0] = p;\r\n }\r\n }\r\n }\r\n for (const p of paths) {\r\n if (!p) continue;\r\n if (!url.endsWith('/')) url += '/';\r\n url += p.startsWith('/') ? p.slice(1) : p;\r\n }\r\n url = url.replace(/([^:]\\/)\\/+/g, '$1');\r\n return url;\r\n}","/**\r\n * WebDAV文件系统实现\r\n */\r\n\r\nimport {\r\n WebDAVOptions,\r\n Stats,\r\n ReadFileOptions,\r\n WriteFileOptions,\r\n ReaddirOptions,\r\n MkdirOptions,\r\n WebDAVResult,\r\n} from './types';\r\n\r\nimport { WebDAVFileSystem } from './WebDAVFileSystem';\r\n\r\nimport {\r\n WebDAVError,\r\n NotFoundError,\r\n AuthenticationError,\r\n AuthorizationError,\r\n FileExistsError,\r\n NetworkError,\r\n TimeoutError,\r\n ArgumentError,\r\n ServerError,\r\n} from './errors';\r\n\r\nimport {\r\n normalizePath,\r\n joinUrl,\r\n getDirFromPath,\r\n parseWebDAVXml,\r\n createBasicAuthHeader,\r\n getContentType,\r\n} from './utils';\r\n\r\n/**\r\n * WebDAV文件系统实现类\r\n */\r\nexport class WebDAVFS implements WebDAVFileSystem {\r\n private baseUrl: string;\r\n private auth?: { username: string; password: string };\r\n private timeout: number;\r\n private headers: Record<string, string>;\r\n\r\n /**\r\n * 创建WebDAV文件系统实例\r\n * @param options WebDAV选项\r\n */\r\n constructor(options: WebDAVOptions) {\r\n if (!options.baseUrl) {\r\n throw new ArgumentError('必须提供baseUrl');\r\n }\r\n\r\n this.baseUrl = options.baseUrl.endsWith('/') \r\n ? options.baseUrl.slice(0, -1) \r\n : options.baseUrl;\r\n if (options.username && options.password) {\r\n this.auth = { username: options.username, password: options.password };\r\n } else {\r\n this.auth = undefined;\r\n }\r\n this.timeout = options.timeout || 30000;\r\n this.headers = options.headers || {};\r\n }\r\n\r\n /**\r\n * 创建请求头\r\n * @param customHeaders 自定义请求头\r\n * @returns 合并后的请求头\r\n */\r\n private createHeaders(customHeaders?: Record<string, string>): Record<string, string> {\r\n const headers: Record<string, string> = {\r\n ...this.headers,\r\n ...customHeaders,\r\n };\r\n\r\n // 添加认证头\r\n if (this.auth) {\r\n headers['Authorization'] = createBasicAuthHeader(this.auth.username, this.auth.password);\r\n }\r\n\r\n return headers;\r\n }\r\n\r\n /**\r\n * 执行HTTP请求\r\n * @param method HTTP方法\r\n * @param path 请求路径\r\n * @param options 请求选项\r\n * @returns 响应对象\r\n */\r\n private async request(\r\n method: string,\r\n path: string,\r\n options: {\r\n headers?: Record<string, string>;\r\n body?: string | ArrayBuffer | Blob | Buffer;\r\n responseType?: 'text' | 'arraybuffer' | 'blob' | 'json';\r\n } = {}\r\n ): Promise<{ data: any; status: number; headers: Record<string, string> }> {\r\n const normalizedPath = normalizePath(path);\r\n const url = joinUrl(this.baseUrl, normalizedPath);\r\n const headers = this.createHeaders(options.headers);\r\n \r\n // 创建AbortController用于超时处理\r\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;\r\n const timeoutId = controller ? setTimeout(() => controller.abort(), this.timeout) : undefined;\r\n \r\n try {\r\n // 使用fetch API(浏览器和现代Node.js都支持)\r\n const response = await fetch(url, {\r\n method,\r\n headers,\r\n body: options.body,\r\n signal: controller?.signal,\r\n credentials: 'include',\r\n });\r\n \r\n // 清除超时\r\n if (timeoutId) clearTimeout(timeoutId);\r\n \r\n // 提取响应头\r\n const responseHeaders: Record<string, string> = {};\r\n response.headers.forEach((value, key) => {\r\n responseHeaders[key.toLowerCase()] = value;\r\n });\r\n \r\n // 根据请求的responseType处理响应数据\r\n let data;\r\n if (options.responseType === 'arraybuffer') {\r\n data = await response.arrayBuffer();\r\n } else if (options.responseType === 'blob') {\r\n data = await response.blob();\r\n } else if (options.responseType === 'json') {\r\n data = await response.json();\r\n } else {\r\n data = await response.text();\r\n }\r\n \r\n return {\r\n data,\r\n status: response.status,\r\n headers: responseHeaders,\r\n };\r\n } catch (error: unknown) {\r\n // 清除超时\r\n if (timeoutId) clearTimeout(timeoutId);\r\n \r\n // 处理错误\r\n if (error instanceof Error && error.name === 'AbortError') {\r\n throw new TimeoutError(`请求超时: ${url}`);\r\n } else {\r\n const errorMessage = error instanceof Error ? error.message : String(error);\r\n throw new NetworkError(error instanceof Error ? error : new Error(String(error)), `网络错误: ${errorMessage}`);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 处理响应错误\r\n * @param status HTTP状态码\r\n * @param path 请求路径\r\n * @param error 原始错误\r\n */\r\n private handleResponseError(status: number, path: string, error?: Error): never {\r\n switch (status) {\r\n case 401:\r\n throw new AuthenticationError(`认证失败: ${path}`);\r\n case 403:\r\n throw new AuthorizationError(`无权限访问: ${path}`);\r\n case 404:\r\n throw new NotFoundError(`资源不存在: ${path}`);\r\n case 409:\r\n throw new FileExistsError(`资源已存在: ${path}`);\r\n default:\r\n throw new ServerError(`服务器错误 (${status}): ${path}`, status);\r\n }\r\n }\r\n\r\n /**\r\n * 读取文件内容\r\n * @param path 文件路径\r\n * @param options 读取选项\r\n * @returns 文件内容\r\n */\r\n async readFile(path: string, options: ReadFileOptions = {}): Promise<Buffer | string> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n const response = await this.request('GET', normalizedPath, {\r\n headers: options.headers,\r\n responseType: 'arraybuffer',\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 创建Buffer\r\n const arrayBuffer = response.data as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n const buffer = Buffer.from(uint8Array);\r\n\r\n // 根据编码返回字符串或Buffer\r\n if (options.encoding) {\r\n return buffer.toString(options.encoding as BufferEncoding);\r\n } else {\r\n return buffer;\r\n }\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`读取文件失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 写入文件内容\r\n * @param path 文件路径\r\n * @param data 文件内容\r\n * @param options 写入选项\r\n * @returns 操作结果\r\n */\r\n async writeFile(\r\n path: string,\r\n data: Buffer | string,\r\n options: WriteFileOptions = {}\r\n ): Promise<WebDAVResult> {\r\n const normalizedPath = normalizePath(path);\r\n const getFilenameFromPath = (p: string) => {\r\n const parts = p.split('/');\r\n return parts[parts.length - 1] || '';\r\n };\r\n const contentType = options.contentType || getContentType(getFilenameFromPath(normalizedPath));\r\n // 检查文件是否存在(如果不允许覆盖)\r\n if (options.overwrite === false) {\r\n const exists = await this.exists(normalizedPath);\r\n if (exists) {\r\n throw new FileExistsError(normalizedPath);\r\n }\r\n }\r\n \r\n // 准备请求头\r\n const headers = {\r\n 'Content-Type': contentType,\r\n };\r\n \r\n try {\r\n const response = await this.request('PUT', normalizedPath, {\r\n headers,\r\n body: data,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`写入文件失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件\r\n * @param path 文件路径\r\n * @returns 操作结果\r\n */\r\n async deleteFile(path: string): Promise<WebDAVResult> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 确保是文件而不是目录\r\n const stat = await this.stat(normalizedPath);\r\n if (stat.isDirectory) {\r\n throw new ArgumentError(`路径指向一个目录,请使用rmdir方法: ${normalizedPath}`);\r\n }\r\n \r\n const response = await this.request('DELETE', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`删除文件失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 读取目录内容\r\n * @param path 目录路径\r\n * @param options 读取选项\r\n * @returns 文件统计信息数组\r\n */\r\n async readDir(path: string, options: ReaddirOptions = {}): Promise<Stats[]> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 准备PROPFIND请求\r\n const headers = {\r\n 'Depth': options.recursive ? 'infinity' : '1',\r\n 'Content-Type': 'application/xml',\r\n };\r\n \r\n const response = await this.request('PROPFIND', normalizedPath, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 解析XML响应\r\n const files = parseWebDAVXml(response.data, normalizedPath);\r\n \r\n // 过滤结果\r\n const result = files.filter(file => {\r\n // 排除当前目录\r\n if (file.path === normalizedPath) {\r\n return false;\r\n }\r\n \r\n // 处理隐藏文件\r\n if (!options.includeHidden && file.name.startsWith('.')) {\r\n return false;\r\n }\r\n \r\n return true;\r\n });\r\n \r\n return result;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`读取目录失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 创建目录\r\n * @param path 目录路径\r\n * @param options 创建选项\r\n * @returns 操作结果\r\n */\r\n async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 检查目录是否已存在\r\n try {\r\n const stat = await this.stat(normalizedPath);\r\n if (stat.isDirectory) {\r\n return; // 目录已存在,视为成功\r\n }\r\n throw new FileExistsError(normalizedPath); // 路径存在但不是目录\r\n } catch (error) {\r\n if (!(error instanceof NotFoundError)) {\r\n throw error;\r\n }\r\n // 目录不存在,继续创建\r\n }\r\n \r\n // 如果需要递归创建父目录\r\n if (options.recursive !== false) {\r\n const parentDir = getDirFromPath(normalizedPath);\r\n if (parentDir !== '/' && parentDir !== normalizedPath) {\r\n try {\r\n await this.stat(parentDir);\r\n } catch (error) {\r\n if (error instanceof NotFoundError) {\r\n // 递归创建父目录\r\n await this.mkdir(parentDir, options);\r\n } else {\r\n throw error;\r\n }\r\n }\r\n }\r\n }\r\n \r\n const response = await this.request('MKCOL', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`创建目录失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件或目录,行为类似于 Node.js 的 fs.rm\r\n * @param path 路径\r\n * @param options { recursive?: boolean, force?: boolean }\r\n */\r\n async rm(path: string, options?: { recursive?: boolean, force?: boolean }) {\r\n const { recursive = false, force = false } = options || {};\r\n try {\r\n const stat = await this.stat(path);\r\n if (stat.isDirectory) {\r\n if (recursive) {\r\n // 递归删除目录内容\r\n const files = await this.readDir(path);\r\n for (const file of files) {\r\n const childPath = path.replace(/\\/$/, '') + '/' + file.name;\r\n await this.rm(childPath, { recursive: true, force });\r\n }\r\n } else {\r\n // 非递归时,目录必须为空\r\n const files = await this.readDir(path);\r\n if (files.length > 0) {\r\n throw new WebDAVError(`Directory not empty: ${path}`);\r\n }\r\n }\r\n }\r\n // 删除文件或空目录\r\n await this._delete(path);\r\n } catch (err: unknown) {\r\n if (force && (\r\n (err instanceof Error && 'code' in err && err.code === 'ENOENT') || \r\n (err instanceof Error && 'status' in err && err.status === 404)\r\n )) {\r\n // force=true 时忽略不存在\r\n return;\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * 兼容旧的 rmdir 方法,内部重定向到 rm\r\n * @deprecated 请使用 rm\r\n */\r\n async rmdir(path: string, options?: boolean | { recursive?: boolean, force?: boolean }) {\r\n // 兼容 boolean 递归参数\r\n let opts: { recursive?: boolean, force?: boolean } = {};\r\n if (typeof options === 'boolean') {\r\n opts.recursive = options;\r\n } else if (typeof options === 'object' && options !== null) {\r\n opts = options;\r\n }\r\n return this.rm(path, opts);\r\n }\r\n\r\n /**\r\n * 删除文件或目录\r\n * @param path 文件或目录路径\r\n * @returns 操作结果\r\n */\r\n private async _delete(path: string) {\r\n // 实现 WebDAV DELETE 请求\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n const response = await this.request('DELETE', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`删除失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 获取文件或目录的统计信息\r\n * @param path 文件或目录路径\r\n * @returns 文件统计信息\r\n */\r\n async stat(path: string): Promise<Stats> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 准备PROPFIND请求\r\n const headers = {\r\n 'Depth': '0',\r\n 'Content-Type': 'application/xml',\r\n };\r\n \r\n const response = await this.request('PROPFIND', normalizedPath, {\r\n headers,\r\n });\r\n \r\n if (response.status === 404) {\r\n throw new NotFoundError(normalizedPath);\r\n } else if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 解析XML响应\r\n const files = parseWebDAVXml(response.data, normalizedPath);\r\n \r\n if (files.length === 0) {\r\n throw new NotFoundError(normalizedPath);\r\n }\r\n \r\n const stat = files[0];\r\n \r\n return stat;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`获取文件信息失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 检查文件或目录是否存在\r\n * @param path 文件或目录路径\r\n * @returns 是否存在\r\n */\r\n async exists(path: string): Promise<boolean> {\r\n try {\r\n await this.stat(path);\r\n return true;\r\n } catch (error) {\r\n if (error instanceof NotFoundError) {\r\n return false;\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 复制文件或目录\r\n * @param source 源路径\r\n * @param destination 目标路径\r\n * @param overwrite 是否覆盖已存在的文件\r\n * @returns 操作结果\r\n */\r\n async copy(source: string, destination: string, overwrite = true): Promise<WebDAVResult> {\r\n const normalizedSource = normalizePath(source);\r\n const normalizedDestination = normalizePath(destination);\r\n \r\n try {\r\n // 检查源文件是否存在\r\n await this.stat(normalizedSource);\r\n \r\n // 检查目标文件是否存在(如果不允许覆盖)\r\n if (!overwrite) {\r\n const exists = await this.exists(normalizedDestination);\r\n if (exists) {\r\n throw new FileExistsError(normalizedDestination);\r\n }\r\n }\r\n \r\n // 准备COPY请求\r\n const headers = {\r\n 'Destination': joinUrl(this.baseUrl, normalizedDestination),\r\n 'Overwrite': overwrite ? 'T' : 'F',\r\n };\r\n \r\n const response = await this.request('COPY', normalizedSource, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedSource);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`复制失败: ${normalizedSource} -> ${normalizedDestination}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 移动文件或目录\r\n * @param source 源路径\r\n * @param destination 目标路径\r\n * @param overwrite 是否覆盖已存在的文件\r\n * @returns 操作结果\r\n */\r\n async move(source: string, destination: string, overwrite = true): Promise<WebDAVResult> {\r\n const normalizedSource = normalizePath(source);\r\n const normalizedDestination = normalizePath(destination);\r\n \r\n try {\r\n // 检查源文件是否存在\r\n await this.stat(normalizedSource);\r\n \r\n // 检查目标文件是否存在(如果不允许覆盖)\r\n if (!overwrite) {\r\n const exists = await this.exists(normalizedDestination);\r\n if (exists) {\r\n throw new FileExistsError(normalizedDestination);\r\n }\r\n }\r\n \r\n // 准备MOVE请求\r\n const headers = {\r\n 'Destination': joinUrl(this.baseUrl, normalizedDestination),\r\n 'Overwrite': overwrite ? 'T' : 'F',\r\n };\r\n \r\n const response = await this.request('MOVE', normalizedSource, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedSource);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`移动失败: ${normalizedSource} -> ${normalizedDestination}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件(fs/unlink 兼容方法)\r\n * @param path 文件路径\r\n */\r\n async unlink(path: string): Promise<void> {\r\n await this.deleteFile(path);\r\n }\r\n}","import { WebDAVOptions } from './types';\r\nimport { WebDAVFS } from './webdav-fs';\r\nimport { WebDAVFileSystem } from './WebDAVFileSystem';\r\n\r\n/**\r\n * 创建 WebDAV 文件系统实例(工厂函数)\r\n * @param options WebDAV 配置选项\r\n * @returns WebDAVFileSystem 实例\r\n */\r\nexport function createWebDAVFileSystem(options: WebDAVOptions): WebDAVFileSystem {\r\n return new WebDAVFS(options);\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,cAAN,MAAM,qBAAoB,MAAM;AAAA,EAUrC,YAAY,SAAiB,QAAiB,OAAiB;AAC7D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,QAAQ;AAEb,WAAO,eAAe,MAAM,aAAY,SAAS;AAAA,EACnD;AAAA,EAEA,WAAmB;AACjB,QAAI,OAAO,KAAK,WAAW,UAAU;AACnC,aAAO,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO,aAAa,KAAK,MAAM;AAAA,IAC9D;AACA,WAAO,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO;AAAA,EACtC;AAAA,EAEA,OAAO,aAAa,UAAkD,SAA+B;AACnG,UAAM,MAAM,UACR,GAAG,OAAO,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,KACrD,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU;AAC7C,WAAO,IAAI,aAAY,KAAK,SAAS,MAAM;AAAA,EAC7C;AAAA,EAEA,OAAO,UAAU,OAA6B;AAC5C,QAAI,iBAAiB,cAAa;AAChC,aAAO;AAAA,IACT;AACA,QAAI,iBAAiB,OAAO;AAC1B,YAAM,MAAM,MAAM,WAAW;AAC7B,aAAO,IAAI,aAAY,KAAK,QAAW,KAAK;AAAA,IAC9C;AACA,WAAO,IAAI,aAAY,sBAAsB;AAAA,EAC/C;AACF;AAKO,IAAM,gBAAN,MAAM,uBAAsB,YAAY;AAAA,EAC7C,YAAY,MAAc;AACxB,UAAM,mCAAU,IAAI,IAAI,GAAG;AAC3B,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,eAAc,SAAS;AAAA,EACrD;AACF;AAKO,IAAM,sBAAN,MAAM,6BAA4B,YAAY;AAAA,EACnD,YAAY,UAAU,4BAAQ;AAC5B,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,qBAAoB,SAAS;AAAA,EAC3D;AACF;AAKO,IAAM,qBAAN,MAAM,4BAA2B,YAAY;AAAA,EAClD,YAAY,UAAU,kCAAS;AAC7B,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,oBAAmB,SAAS;AAAA,EAC1D;AACF;AAKO,IAAM,cAAN,MAAM,qBAAoB,YAAY;AAAA,EAC3C,YAAY,UAAU,kCAAS,aAAa,KAAK;AAC/C,UAAM,SAAS,UAAU;AACzB,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,aAAY,SAAS;AAAA,EACnD;AACF;AAKO,IAAM,kBAAN,MAAM,yBAAwB,YAAY;AAAA,EAC/C,YAAY,MAAc;AACxB,UAAM,mCAAU,IAAI,IAAI,GAAG;AAC3B,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,iBAAgB,SAAS;AAAA,EACvD;AACF;AAgBO,IAAM,eAAN,MAAM,sBAAqB,YAAY;AAAA,EAC5C,YAAY,UAAU,4BAAQ;AAC5B,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,cAAa,SAAS;AAAA,EACpD;AACF;AAKO,IAAM,eAAN,MAAM,sBAAqB,YAAY;AAAA,EAC5C,YAAY,eAAuB,UAAU,4BAAQ;AACnD,UAAM,gBAAgB,GAAG,OAAO,KAAK,cAAc,OAAO,KAAK,SAAS,GAAG,aAAa;AACxF,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,cAAa,SAAS;AAAA,EACpD;AACF;AAKO,IAAM,gBAAN,MAAM,uBAAsB,YAAY;AAAA,EAC7C,YAAY,SAAiB;AAC3B,UAAM,6BAAS,OAAO,EAAE;AACxB,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,eAAc,SAAS;AAAA,EACrD;AACF;;;AC7HO,IAAM,kBAAkB;AAAA,EAC7B,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AACb;AA4DO,IAAM,uBAAuB;AAAA,EAClC,KAAK,gBAAgB;AAAA,EACrB,MAAM,gBAAgB;AAAA,EACtB,MAAM,gBAAgB;AACxB;;;AC3FA,6BAA0B;AAOnB,SAAS,cAAc,MAAsB;AAElD,MAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,WAAO,MAAM;AAAA,EACf;AAEA,SAAO,KAAK,QAAQ,QAAQ,GAAG;AAE/B,MAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,GAAG;AACzC,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AACA,SAAO;AACT;AAoEO,SAAS,sBAAsB,UAAkB,UAA0B;AAEhF,QAAM,OAAO,CAAC,QAAgB;AAC5B,QAAI,OAAO,WAAW,eAAe,OAAO,MAAM;AAChD,aAAO,OAAO,KAAK,GAAG;AAAA,IACxB,WAAW,OAAO,WAAW,aAAa;AACxC,aAAO,OAAO,KAAK,GAAG,EAAE,SAAS,QAAQ;AAAA,IAC3C,OAAO;AACL,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,GAAG,QAAQ,IAAI,QAAQ,EAAE,CAAC;AACjD;AAuUO,SAAS,eAAe,UAA0B;AACvD,QAAM,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY;AACnD,UAAQ,KAAK;AAAA,IACX,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAA,IAAQ,KAAK;AAAO,aAAO;AAAA,IAChC,KAAK;AAAQ,aAAO;AAAA,IACpB,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAA,IAAO,KAAK;AAAQ,aAAO;AAAA,IAChC,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAM,aAAO;AAAA,IAClB,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAO,aAAO;AAAA,IACnB;AAAS,aAAO;AAAA,EAClB;AACF;AAGO,SAAS,eAAe,MAAsB;AACnD,MAAI,CAAC,QAAQ,SAAS,IAAK,QAAO;AAClC,QAAM,aAAa,KAAK,QAAQ,QAAQ,EAAE;AAC1C,QAAM,MAAM,WAAW,YAAY,GAAG;AACtC,MAAI,OAAO,EAAG,QAAO;AACrB,SAAO,WAAW,MAAM,GAAG,GAAG,KAAK;AACrC;AASO,SAAS,eAAe,KAAa,UAA2B;AAGrE,QAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,QAAM,SAAkB,CAAC;AACzB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,IAAI,iCAAU;AAAA,IAC3B,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,YAAY;AAAA,EACd,CAAC;AACD,QAAM,OAAO,OAAO,MAAM,GAAG;AAE7B,WAAS,mBAAmB,QAAiB,MAAyB;AACpE,QAAI,CAAC,IAAK,QAAO;AACjB,eAAW,OAAO,MAAM;AACtB,UAAK,IAAgC,GAAG,MAAM,OAAW,QAAQ,IAAgC,GAAG;AACpG,YAAM,QAAQ,OAAO,KAAK,GAAG,EAAE,KAAK,OAAK,EAAE,YAAY,MAAM,IAAI,YAAY,CAAC;AAC9E,UAAI,MAAO,QAAQ,IAAgC,KAAK;AAAA,IAC1D;AACA,WAAO;AAAA,EACT;AACA,QAAM,cAAc,mBAAmB,MAAM,iBAAiB,aAAa;AAC3E,QAAM,YAAY,mBAAmB,aAAa,cAAc,UAAU,KAAK,CAAC;AAChF,QAAM,MAAM,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AAC7D,QAAM,iBAAiB,YAAY,CAAC,SAAS,SAAS,GAAG;AACzD,aAAW,QAAQ,KAAK;AACtB,UAAM,OAAO,OAAO,mBAAmB,MAAM,UAAU,MAAM,KAAK,EAAE;AACpE,UAAM,WAAW,mBAAmB,MAAM,cAAc,YAAY,UAAU,MAAM,KAAK,CAAC;AAC1F,UAAM,OAAO,mBAAmB,UAAU,UAAU,MAAM,KAAK;AAC/D,UAAM,eAAe,mBAAmB,MAAM,kBAAkB,cAAc;AAC9E,UAAM,aAAa,gBAAgB,mBAAmB,cAAc,gBAAgB,YAAY;AAChG,UAAM,cAAc,CAAC,EAAE,gBAAgB,eAAe;AAGtD,QAAI;AACJ,QAAI,gBAAgB;AAClB,aAAO,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK;AAAA,IAClD,OAAO;AACL,aAAO,KAAK,QAAQ,UAAU,EAAE,EAAE,QAAQ,OAAO,EAAE,EAAE,QAAQ,OAAO,EAAE;AAAA,IACxE;AACA,QAAI,CAAC,KAAM;AACX,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,QAAQ,CAAC;AAAA,MACT,MAAM,SAAS,OAAO,mBAAmB,MAAM,sBAAsB,kBAAkB,KAAK,GAAG,GAAG,EAAE;AAAA,MACpG,cAAc,mBAAmB,MAAM,qBAAqB,iBAAiB,IACzE,IAAI,KAAK,OAAO,mBAAmB,MAAM,qBAAqB,iBAAiB,CAAC,CAAC,IACjF;AAAA,IACN,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAQO,SAAS,QAAQ,SAAiB,OAAyB;AAChE,MAAI,MAAM;AACV,MAAI,WAAW;AACf,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,IAAI;AACtB,eAAW,EAAE,SAAS,QAAQ,QAAQ,EAAE;AAAA,EAC1C,QAAQ;AACN,eAAW,KAAK,WAAW,GAAG,IAAI,KAAK,QAAQ,QAAQ,EAAE,IAAI;AAAA,EAC/D;AAEA,MAAI,MAAM,SAAS,KAAK,UAAU;AAChC,QAAI,IAAI,MAAM,CAAC;AACf,QAAI,GAAG;AAEL,YAAM,YAAY,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,CAAC;AACvD,YAAM,SAAS,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC1C,UAAI,aAAa,OAAO,CAAC,MAAM,WAAW;AACxC,eAAO,MAAM;AACb,YAAI,OAAO,KAAK,GAAG;AACnB,YAAI,KAAK,CAAC,EAAE,WAAW,GAAG,EAAG,KAAI,MAAM;AACvC,cAAM,CAAC,IAAI;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,EAAG;AACR,QAAI,CAAC,IAAI,SAAS,GAAG,EAAG,QAAO;AAC/B,WAAO,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,IAAI;AAAA,EAC1C;AACA,QAAM,IAAI,QAAQ,gBAAgB,IAAI;AACtC,SAAO;AACT;;;ACtgBO,IAAM,WAAN,MAA2C;AAAA;AAAA;AAAA;AAAA;AAAA,EAUhD,YAAY,SAAwB;AAClC,QAAI,CAAC,QAAQ,SAAS;AACpB,YAAM,IAAI,cAAc,iCAAa;AAAA,IACvC;AAEA,SAAK,UAAU,QAAQ,QAAQ,SAAS,GAAG,IACvC,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAC3B,QAAQ;AACZ,QAAI,QAAQ,YAAY,QAAQ,UAAU;AACxC,WAAK,OAAO,EAAE,UAAU,QAAQ,UAAU,UAAU,QAAQ,SAAS;AAAA,IACvE,OAAO;AACL,WAAK,OAAO;AAAA,IACd;AACA,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,WAAW,CAAC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,eAAgE;AACpF,UAAM,UAAkC;AAAA,MACtC,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AAGA,QAAI,KAAK,MAAM;AACb,cAAQ,eAAe,IAAI,sBAAsB,KAAK,KAAK,UAAU,KAAK,KAAK,QAAQ;AAAA,IACzF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,QACZ,QACA,MACA,UAII,CAAC,GACoE;AACzE,UAAM,iBAAiB,cAAc,IAAI;AACzC,UAAM,MAAM,QAAQ,KAAK,SAAS,cAAc;AAChD,UAAM,UAAU,KAAK,cAAc,QAAQ,OAAO;AAGlD,UAAM,aAAa,OAAO,oBAAoB,cAAc,IAAI,gBAAgB,IAAI;AACpF,UAAM,YAAY,aAAa,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,IAAI;AAEpF,QAAI;AAEF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,QACA,MAAM,QAAQ;AAAA,QACd,QAAQ,YAAY;AAAA,QACpB,aAAa;AAAA,MACf,CAAC;AAGD,UAAI,UAAW,cAAa,SAAS;AAGrC,YAAM,kBAA0C,CAAC;AACjD,eAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,wBAAgB,IAAI,YAAY,CAAC,IAAI;AAAA,MACvC,CAAC;AAGD,UAAI;AACJ,UAAI,QAAQ,iBAAiB,eAAe;AAC1C,eAAO,MAAM,SAAS,YAAY;AAAA,MACpC,WAAW,QAAQ,iBAAiB,QAAQ;AAC1C,eAAO,MAAM,SAAS,KAAK;AAAA,MAC7B,WAAW,QAAQ,iBAAiB,QAAQ;AAC1C,eAAO,MAAM,SAAS,KAAK;AAAA,MAC7B,OAAO;AACL,eAAO,MAAM,SAAS,KAAK;AAAA,MAC7B;AAEA,aAAO;AAAA,QACL;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB,SAAS;AAAA,MACX;AAAA,IACF,SAAS,OAAgB;AAEvB,UAAI,UAAW,cAAa,SAAS;AAGrC,UAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;AACzD,cAAM,IAAI,aAAa,6BAAS,GAAG,EAAE;AAAA,MACvC,OAAO;AACL,cAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,cAAM,IAAI,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,GAAG,6BAAS,YAAY,EAAE;AAAA,MAC3G;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBAAoB,QAAgB,MAAc,OAAsB;AAC9E,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,cAAM,IAAI,oBAAoB,6BAAS,IAAI,EAAE;AAAA,MAC/C,KAAK;AACH,cAAM,IAAI,mBAAmB,mCAAU,IAAI,EAAE;AAAA,MAC/C,KAAK;AACH,cAAM,IAAI,cAAc,mCAAU,IAAI,EAAE;AAAA,MAC1C,KAAK;AACH,cAAM,IAAI,gBAAgB,mCAAU,IAAI,EAAE;AAAA,MAC5C;AACE,cAAM,IAAI,YAAY,mCAAU,MAAM,MAAM,IAAI,IAAI,MAAM;AAAA,IAC9D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAAS,MAAc,UAA2B,CAAC,GAA6B;AACpF,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,gBAAgB;AAAA,QACzD,SAAS,QAAQ;AAAA,QACjB,cAAc;AAAA,MAChB,CAAC;AAED,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAGA,YAAM,cAAc,SAAS;AAC7B,YAAM,aAAa,IAAI,WAAW,WAAW;AAC7C,YAAM,SAAS,OAAO,KAAK,UAAU;AAGrC,UAAI,QAAQ,UAAU;AACpB,eAAO,OAAO,SAAS,QAAQ,QAA0B;AAAA,MAC3D,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,yCAAW,cAAc,IAAI,QAAW,KAAK;AAAA,IACrE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,UACJ,MACA,MACA,UAA4B,CAAC,GACN;AACvB,UAAM,iBAAiB,cAAc,IAAI;AACzC,UAAM,sBAAsB,CAAC,MAAc;AACzC,YAAM,QAAQ,EAAE,MAAM,GAAG;AACzB,aAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,IACpC;AACA,UAAM,cAAc,QAAQ,eAAe,eAAe,oBAAoB,cAAc,CAAC;AAE7F,QAAI,QAAQ,cAAc,OAAO;AAC/B,YAAM,SAAS,MAAM,KAAK,OAAO,cAAc;AAC/C,UAAI,QAAQ;AACV,cAAM,IAAI,gBAAgB,cAAc;AAAA,MAC1C;AAAA,IACF;AAGA,UAAM,UAAU;AAAA,MACd,gBAAgB;AAAA,IAClB;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,gBAAgB;AAAA,QACzD;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AAED,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAEA,aAAO;AAAA,QACL,SAAS,SAAS,UAAU,OAAO,SAAS,SAAS;AAAA,QACrD,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,yCAAW,cAAc,EAAE;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAW,MAAqC;AACpD,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AAEF,YAAM,OAAO,MAAM,KAAK,KAAK,cAAc;AAC3C,UAAI,KAAK,aAAa;AACpB,cAAM,IAAI,cAAc,8FAAwB,cAAc,EAAE;AAAA,MAClE;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,UAAU,cAAc;AAE5D,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAEA,aAAO;AAAA,QACL,SAAS,SAAS,UAAU,OAAO,SAAS,SAAS;AAAA,QACrD,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,yCAAW,cAAc,EAAE;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,MAAc,UAA0B,CAAC,GAAqB;AAC1E,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AAEF,YAAM,UAAU;AAAA,QACd,SAAS,QAAQ,YAAY,aAAa;AAAA,QAC1C,gBAAgB;AAAA,MAClB;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,YAAY,gBAAgB;AAAA,QAC9D;AAAA,MACF,CAAC;AAED,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAGA,YAAM,QAAQ,eAAe,SAAS,MAAM,cAAc;AAG1D,YAAM,SAAS,MAAM,OAAO,UAAQ;AAElC,YAAI,KAAK,SAAS,gBAAgB;AAChC,iBAAO;AAAA,QACT;AAGA,YAAI,CAAC,QAAQ,iBAAiB,KAAK,KAAK,WAAW,GAAG,GAAG;AACvD,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,yCAAW,cAAc,EAAE;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,MAAM,MAAc,UAAwB,CAAC,GAAkB;AACnE,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AAEF,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,KAAK,cAAc;AAC3C,YAAI,KAAK,aAAa;AACpB;AAAA,QACF;AACA,cAAM,IAAI,gBAAgB,cAAc;AAAA,MAC1C,SAAS,OAAO;AACd,YAAI,EAAE,iBAAiB,gBAAgB;AACrC,gBAAM;AAAA,QACR;AAAA,MAEF;AAGA,UAAI,QAAQ,cAAc,OAAO;AAC/B,cAAM,YAAY,eAAe,cAAc;AAC/C,YAAI,cAAc,OAAO,cAAc,gBAAgB;AACrD,cAAI;AACF,kBAAM,KAAK,KAAK,SAAS;AAAA,UAC3B,SAAS,OAAO;AACd,gBAAI,iBAAiB,eAAe;AAElC,oBAAM,KAAK,MAAM,WAAW,OAAO;AAAA,YACrC,OAAO;AACL,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,SAAS,cAAc;AAE3D,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAEA;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,yCAAW,cAAc,EAAE;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,GAAG,MAAc,SAAoD;AACzE,UAAM,EAAE,YAAY,OAAO,QAAQ,MAAM,IAAI,WAAW,CAAC;AACzD,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAK,IAAI;AACjC,UAAI,KAAK,aAAa;AACpB,YAAI,WAAW;AAEb,gBAAM,QAAQ,MAAM,KAAK,QAAQ,IAAI;AACrC,qBAAW,QAAQ,OAAO;AACxB,kBAAM,YAAY,KAAK,QAAQ,OAAO,EAAE,IAAI,MAAM,KAAK;AACvD,kBAAM,KAAK,GAAG,WAAW,EAAE,WAAW,MAAM,MAAM,CAAC;AAAA,UACrD;AAAA,QACF,OAAO;AAEL,gBAAM,QAAQ,MAAM,KAAK,QAAQ,IAAI;AACrC,cAAI,MAAM,SAAS,GAAG;AACpB,kBAAM,IAAI,YAAY,wBAAwB,IAAI,EAAE;AAAA,UACtD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,KAAK,QAAQ,IAAI;AAAA,IACzB,SAAS,KAAc;AACrB,UAAI,UACD,eAAe,SAAS,UAAU,OAAO,IAAI,SAAS,YACtD,eAAe,SAAS,YAAY,OAAO,IAAI,WAAW,MAC1D;AAED;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,MAAc,SAA8D;AAEtF,QAAI,OAAiD,CAAC;AACtD,QAAI,OAAO,YAAY,WAAW;AAChC,WAAK,YAAY;AAAA,IACnB,WAAW,OAAO,YAAY,YAAY,YAAY,MAAM;AAC1D,aAAO;AAAA,IACT;AACA,WAAO,KAAK,GAAG,MAAM,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,QAAQ,MAAc;AAElC,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,UAAU,cAAc;AAE5D,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAEA,aAAO;AAAA,QACL,SAAS,SAAS,UAAU,OAAO,SAAS,SAAS;AAAA,QACrD,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,6BAAS,cAAc,IAAI,QAAW,KAAK;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,MAA8B;AACvC,UAAM,iBAAiB,cAAc,IAAI;AAEzC,QAAI;AAEF,YAAM,UAAU;AAAA,QACd,SAAS;AAAA,QACT,gBAAgB;AAAA,MAClB;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,YAAY,gBAAgB;AAAA,QAC9D;AAAA,MACF,CAAC;AAED,UAAI,SAAS,WAAW,KAAK;AAC3B,cAAM,IAAI,cAAc,cAAc;AAAA,MACxC,WAAW,SAAS,UAAU,KAAK;AACjC,aAAK,oBAAoB,SAAS,QAAQ,cAAc;AAAA,MAC1D;AAGA,YAAM,QAAQ,eAAe,SAAS,MAAM,cAAc;AAE1D,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,cAAc,cAAc;AAAA,MACxC;AAEA,YAAM,OAAO,MAAM,CAAC;AAEpB,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,qDAAa,cAAc,IAAI,QAAW,KAAK;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,MAAgC;AAC3C,QAAI;AACF,YAAM,KAAK,KAAK,IAAI;AACpB,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,eAAe;AAClC,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAK,QAAgB,aAAqB,YAAY,MAA6B;AACvF,UAAM,mBAAmB,cAAc,MAAM;AAC7C,UAAM,wBAAwB,cAAc,WAAW;AAEvD,QAAI;AAEF,YAAM,KAAK,KAAK,gBAAgB;AAGhC,UAAI,CAAC,WAAW;AACd,cAAM,SAAS,MAAM,KAAK,OAAO,qBAAqB;AACtD,YAAI,QAAQ;AACV,gBAAM,IAAI,gBAAgB,qBAAqB;AAAA,QACjD;AAAA,MACF;AAGA,YAAM,UAAU;AAAA,QACd,eAAe,QAAQ,KAAK,SAAS,qBAAqB;AAAA,QAC1D,aAAa,YAAY,MAAM;AAAA,MACjC;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,QAC5D;AAAA,MACF,CAAC;AAED,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,gBAAgB;AAAA,MAC5D;AAEA,aAAO;AAAA,QACL,SAAS,SAAS,UAAU,OAAO,SAAS,SAAS;AAAA,QACrD,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,6BAAS,gBAAgB,OAAO,qBAAqB,IAAI,QAAW,KAAK;AAAA,IACjG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAK,QAAgB,aAAqB,YAAY,MAA6B;AACvF,UAAM,mBAAmB,cAAc,MAAM;AAC7C,UAAM,wBAAwB,cAAc,WAAW;AAEvD,QAAI;AAEF,YAAM,KAAK,KAAK,gBAAgB;AAGhC,UAAI,CAAC,WAAW;AACd,cAAM,SAAS,MAAM,KAAK,OAAO,qBAAqB;AACtD,YAAI,QAAQ;AACV,gBAAM,IAAI,gBAAgB,qBAAqB;AAAA,QACjD;AAAA,MACF;AAGA,YAAM,UAAU;AAAA,QACd,eAAe,QAAQ,KAAK,SAAS,qBAAqB;AAAA,QAC1D,aAAa,YAAY,MAAM;AAAA,MACjC;AAEA,YAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,QAC5D;AAAA,MACF,CAAC;AAED,UAAI,SAAS,UAAU,KAAK;AAC1B,aAAK,oBAAoB,SAAS,QAAQ,gBAAgB;AAAA,MAC5D;AAEA,aAAO;AAAA,QACL,SAAS,SAAS,UAAU,OAAO,SAAS,SAAS;AAAA,QACrD,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,YAAM,IAAI,YAAY,6BAAS,gBAAgB,OAAO,qBAAqB,IAAI,QAAW,KAAK;AAAA,IACjG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,MAA6B;AACxC,UAAM,KAAK,WAAW,IAAI;AAAA,EAC5B;AACF;;;AC1oBO,SAAS,uBAAuB,SAA0C;AAC/E,SAAO,IAAI,SAAS,OAAO;AAC7B;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/constants.ts","../src/utils.ts","../src/webdav-fs.ts","../src/webdav.ts"],"sourcesContent":["/**\r\n * zen-fs-webdav\r\n * 一个简单、现代的 WebDAV 客户端库,提供类似文件系统的 API\r\n */\r\n\r\n// 导出主类\r\nexport { WebDAVFileSystem } from './WebDAVFileSystem';\r\nexport { createWebDAVFileSystem } from './webdav';\r\n\r\n// 导出错误类\r\nexport { WebDAVError } from './errors';\r\n\r\n// 导出类型定义\r\nexport {\r\n WebDAVOptions,\r\n WebDAVRequestOptions,\r\n FileEntry,\r\n Stats,\r\n ReadFileOptions,\r\n WriteFileOptions,\r\n MkdirOptions,\r\n RmdirOptions,\r\n ReaddirOptions,\r\n CopyOptions,\r\n MoveOptions,\r\n} from './types';\r\n\r\n// 导出工具函数(例如 parseWebDAVXml)以便其它包可以直接使用\r\nexport { parseWebDAVXml } from './utils';","/**\r\n * WebDAV文件系统库的错误类型定义\r\n */\r\n\r\n/**\r\n * WebDAV错误基类\r\n */\r\nexport class WebDAVError extends Error {\r\n /**\r\n * HTTP状态码\r\n */\r\n status?: number;\r\n /**\r\n * 原始错误对象(如果有)\r\n */\r\n cause?: unknown;\r\n\r\n constructor(message: string, status?: number, cause?: unknown) {\r\n super(message);\r\n this.name = 'WebDAVError';\r\n this.status = status;\r\n this.cause = cause;\r\n // 兼容 TS 的 Error 子类\r\n Object.setPrototypeOf(this, WebDAVError.prototype);\r\n }\r\n\r\n toString(): string {\r\n if (typeof this.status === 'number') {\r\n return `${this.name}: ${this.message} (Status: ${this.status})`;\r\n }\r\n return `${this.name}: ${this.message}`;\r\n }\r\n\r\n static fromResponse(response: { status: number; statusText: string }, message?: string): WebDAVError {\r\n const msg = message\r\n ? `${message}: ${response.status} ${response.statusText}`\r\n : `${response.status} ${response.statusText}`;\r\n return new WebDAVError(msg, response.status);\r\n }\r\n\r\n static fromError(error: unknown): WebDAVError {\r\n if (error instanceof WebDAVError) {\r\n return error;\r\n }\r\n if (error instanceof Error) {\r\n const msg = error.message || 'Unknown WebDAV error';\r\n return new WebDAVError(msg, undefined, error);\r\n }\r\n return new WebDAVError('Unknown WebDAV error');\r\n }\r\n}\r\n\r\n/**\r\n * 文件或目录不存在错误\r\n */\r\nexport class NotFoundError extends WebDAVError {\r\n constructor(path: string) {\r\n super(`文件未找到: ${path}`, 404);\r\n this.name = 'NotFoundError';\r\n Object.setPrototypeOf(this, NotFoundError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 认证错误\r\n */\r\nexport class AuthenticationError extends WebDAVError {\r\n constructor(message = '认证失败') {\r\n super(message, 401);\r\n this.name = 'AuthenticationError';\r\n Object.setPrototypeOf(this, AuthenticationError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 授权错误\r\n */\r\nexport class AuthorizationError extends WebDAVError {\r\n constructor(message = '权限被拒绝') {\r\n super(message, 403);\r\n this.name = 'AuthorizationError';\r\n Object.setPrototypeOf(this, AuthorizationError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 服务器错误\r\n */\r\nexport class ServerError extends WebDAVError {\r\n constructor(message = '服务器错误', statusCode = 500) {\r\n super(message, statusCode);\r\n this.name = 'ServerError';\r\n Object.setPrototypeOf(this, ServerError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 文件已存在错误\r\n */\r\nexport class FileExistsError extends WebDAVError {\r\n constructor(path: string) {\r\n super(`文件已存在: ${path}`, 412);\r\n this.name = 'FileExistsError';\r\n Object.setPrototypeOf(this, FileExistsError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 锁定错误\r\n */\r\nexport class LockError extends WebDAVError {\r\n constructor(path: string, message = '资源被锁定') {\r\n super(`${message}: ${path}`, 423);\r\n this.name = 'LockError';\r\n Object.setPrototypeOf(this, LockError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 超时错误\r\n */\r\nexport class TimeoutError extends WebDAVError {\r\n constructor(message = '连接超时') {\r\n super(message, 408);\r\n this.name = 'TimeoutError';\r\n Object.setPrototypeOf(this, TimeoutError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 网络错误\r\n */\r\nexport class NetworkError extends WebDAVError {\r\n constructor(originalError?: Error, message = '网络错误') {\r\n super(originalError ? `${message}: ${originalError.message}` : message, 0, originalError);\r\n this.name = 'NetworkError';\r\n Object.setPrototypeOf(this, NetworkError.prototype);\r\n }\r\n}\r\n\r\n/**\r\n * 参数错误\r\n */\r\nexport class ArgumentError extends WebDAVError {\r\n constructor(message: string) {\r\n super(`无效参数: ${message}`);\r\n this.name = 'ArgumentError';\r\n Object.setPrototypeOf(this, ArgumentError.prototype);\r\n }\r\n}","/**\n * WebDAV 相关常量\n */\n\n/**\n * WebDAV 方法\n */\nexport enum WebDAVMethod {\n GET = 'GET',\n PUT = 'PUT',\n POST = 'POST',\n DELETE = 'DELETE',\n PROPFIND = 'PROPFIND',\n PROPPATCH = 'PROPPATCH',\n MKCOL = 'MKCOL',\n COPY = 'COPY',\n MOVE = 'MOVE',\n LOCK = 'LOCK',\n UNLOCK = 'UNLOCK',\n}\n\n/**\n * WebDAV 命名空间\n */\nexport const WebDAVNamespace = {\n DAV: 'DAV:',\n CALDAV: 'urn:ietf:params:xml:ns:caldav',\n CARDDAV: 'urn:ietf:params:xml:ns:carddav',\n OWNCLOUD: 'http://owncloud.org/ns',\n NEXTCLOUD: 'http://nextcloud.org/ns',\n};\n\n/**\n * WebDAV 属性名称\n */\nexport const WebDAVPropName = {\n RESOURCE_TYPE: 'resourcetype',\n DISPLAY_NAME: 'displayname',\n GET_CONTENT_LENGTH: 'getcontentlength',\n GET_CONTENT_TYPE: 'getcontenttype',\n GET_LAST_MODIFIED: 'getlastmodified',\n CREATION_DATE: 'creationdate',\n ETAG: 'getetag',\n};\n\n/**\n * WebDAV 资源类型\n */\nexport enum WebDAVResourceType {\n FILE = 'file',\n DIRECTORY = 'directory',\n COLLECTION = 'collection',\n}\n\n/**\n * WebDAV 深度\n */\nexport enum WebDAVDepth {\n /**\n * 仅当前资源\n */\n ZERO = '0',\n \n /**\n * 当前资源及其直接子资源\n */\n ONE = '1',\n \n /**\n * 当前资源及其所有后代资源\n */\n INFINITY = 'infinity',\n}\n\n/**\n * 默认超时时间(毫秒)\n */\nexport const DEFAULT_TIMEOUT = 30000;\n\n/**\n * 默认请求头\n */\nexport const DEFAULT_HEADERS = {\n 'Content-Type': 'application/xml; charset=utf-8',\n 'Accept': 'application/xml, */*',\n};\n\n/**\n * XML 命名空间前缀\n */\nexport const XML_NAMESPACE_PREFIX = {\n 'd': WebDAVNamespace.DAV,\n 'oc': WebDAVNamespace.OWNCLOUD,\n 'nc': WebDAVNamespace.NEXTCLOUD,\n};\n\n/**\n * 错误消息\n */\nexport const ErrorMessage = {\n INVALID_URL: '无效的 URL',\n TIMEOUT: '请求超时',\n NETWORK_ERROR: '网络错误',\n UNAUTHORIZED: '未授权',\n FORBIDDEN: '禁止访问',\n NOT_FOUND: '资源不存在',\n METHOD_NOT_ALLOWED: '方法不允许',\n CONFLICT: '资源冲突',\n PRECONDITION_FAILED: '前提条件失败',\n INTERNAL_SERVER_ERROR: '服务器内部错误',\n NOT_IMPLEMENTED: '未实现',\n BAD_GATEWAY: '错误的网关',\n SERVICE_UNAVAILABLE: '服务不可用',\n GATEWAY_TIMEOUT: '网关超时',\n};\n\n/**\n * HTTP 状态码\n */\nexport enum HttpStatus {\n OK = 200,\n CREATED = 201,\n NO_CONTENT = 204,\n MULTI_STATUS = 207,\n BAD_REQUEST = 400,\n UNAUTHORIZED = 401,\n FORBIDDEN = 403,\n NOT_FOUND = 404,\n METHOD_NOT_ALLOWED = 405,\n CONFLICT = 409,\n PRECONDITION_FAILED = 412,\n INTERNAL_SERVER_ERROR = 500,\n NOT_IMPLEMENTED = 501,\n BAD_GATEWAY = 502,\n SERVICE_UNAVAILABLE = 503,\n GATEWAY_TIMEOUT = 504,\n}","import { WebDAVNamespace, WebDAVPropName, WebDAVResourceType } from './constants';\r\nimport { Stats } from './types';\r\nimport { WebDAVError } from './errors';\r\nimport { XMLParser } from 'fast-xml-parser'\r\n\r\n/**\r\n * 规范化路径,确保以 / 开头,不以 / 结尾(除非是根路径)\r\n * @param path 路径\r\n * @returns 规范化后的路径\r\n */\r\nexport function normalizePath(path: string): string {\r\n // 确保路径以 / 开头\r\n if (!path.startsWith('/')) {\r\n path = '/' + path;\r\n }\r\n // 合并多个连续的斜杠为一个\r\n path = path.replace(/\\/+/g, '/');\r\n // 如果不是根路径,则确保不以 / 结尾\r\n if (path.length > 1 && path.endsWith('/')) {\r\n path = path.slice(0, -1);\r\n }\r\n return path;\r\n}\r\n\r\n// /**\r\n// * 连接路径\r\n// * @param base 基础路径\r\n// * @param paths 要连接的路径\r\n// * @returns 连接后的路径\r\n// */\r\n// export function joinPaths(base: string, ...paths: string[]): string {\r\n// let result = base;\r\n \r\n// for (const path of paths) {\r\n// if (!path) continue;\r\n \r\n// if (result.endsWith('/')) {\r\n// result = result + (path.startsWith('/') ? path.slice(1) : path);\r\n// } else {\r\n// result = result + (path.startsWith('/') ? path : '/' + path);\r\n// }\r\n// }\r\n \r\n// return normalizePath(result);\r\n// }\r\n\r\n/**\r\n * 获取路径的父目录\r\n * @param path 路径\r\n * @returns 父目录路径\r\n */\r\nexport function getParentPath(path: string): string {\r\n path = normalizePath(path);\r\n \r\n // 根路径没有父目录\r\n if (path === '/') {\r\n return '/';\r\n }\r\n \r\n const lastSlashIndex = path.lastIndexOf('/');\r\n if (lastSlashIndex <= 0) {\r\n return '/';\r\n }\r\n \r\n return path.slice(0, lastSlashIndex) || '/';\r\n}\r\n\r\n/**\r\n * 获取路径的基本名称(最后一个部分)\r\n * @param path 路径\r\n * @returns 基本名称\r\n */\r\nexport function getBasename(path: string): string {\r\n path = normalizePath(path);\r\n \r\n // 根路径的基本名称为空字符串\r\n if (path === '/') {\r\n return '';\r\n }\r\n \r\n const lastSlashIndex = path.lastIndexOf('/');\r\n return path.slice(lastSlashIndex + 1);\r\n}\r\n\r\n/**\r\n * 创建基本认证头\r\n * @param username 用户名\r\n * @param password 密码\r\n * @returns 基本认证头\r\n */\r\nexport function createBasicAuthHeader(username: string, password: string): string {\r\n // 在浏览器和 Node.js 环境中都可用的 btoa 实现\r\n const btoa = (str: string) => {\r\n if (typeof window !== 'undefined' && window.btoa) {\r\n return window.btoa(str);\r\n } else if (typeof Buffer !== 'undefined') {\r\n return Buffer.from(str).toString('base64');\r\n } else {\r\n throw new Error('Base64 encoding not available');\r\n }\r\n };\r\n \r\n return `Basic ${btoa(`${username}:${password}`)}`;\r\n}\r\n\r\n/**\r\n * 解析 WebDAV 响应中的属性\r\n * @param xml XML 文档\r\n * @returns 解析后的属性对象\r\n */\r\nexport function parseWebDAVProperties(xml: Document): Record<string, any> {\r\n const result: Record<string, any> = {};\r\n \r\n // 查找所有 prop 元素\r\n const propElements = xml.getElementsByTagNameNS(WebDAVNamespace.DAV, 'prop');\r\n \r\n for (let i = 0; i < propElements.length; i++) {\r\n const propElement = propElements[i];\r\n \r\n // 遍历 prop 元素的子元素\r\n for (let j = 0; j < propElement.childNodes.length; j++) {\r\n const childNode = propElement.childNodes[j];\r\n \r\n if (childNode.nodeType === Node.ELEMENT_NODE) {\r\n const element = childNode as Element;\r\n const localName = element.localName;\r\n const namespace = element.namespaceURI;\r\n \r\n // 根据属性类型进行特殊处理\r\n if (localName === WebDAVPropName.RESOURCE_TYPE) {\r\n // 资源类型\r\n if (element.getElementsByTagNameNS(WebDAVNamespace.DAV, 'collection').length > 0) {\r\n result.resourceType = WebDAVResourceType.DIRECTORY;\r\n } else {\r\n result.resourceType = WebDAVResourceType.FILE;\r\n }\r\n } else if (localName === WebDAVPropName.GET_CONTENT_LENGTH) {\r\n // 内容长度\r\n result.size = parseInt(element.textContent || '0', 10);\r\n } else if (localName === WebDAVPropName.GET_LAST_MODIFIED) {\r\n // 最后修改时间\r\n result.lastModified = new Date(element.textContent || '');\r\n } else if (localName === WebDAVPropName.CREATION_DATE) {\r\n // 创建时间\r\n result.createdAt = new Date(element.textContent || '');\r\n } else if (localName === WebDAVPropName.DISPLAY_NAME) {\r\n // 显示名称\r\n result.displayName = element.textContent || '';\r\n } else if (localName === WebDAVPropName.GET_CONTENT_TYPE) {\r\n // 内容类型\r\n result.mimeType = element.textContent || '';\r\n } else if (localName === WebDAVPropName.ETAG) {\r\n // ETag\r\n result.etag = element.textContent || '';\r\n } else {\r\n // 其他属性\r\n const key = `${namespace || ''}:${localName}`;\r\n result[key] = element.textContent || '';\r\n }\r\n }\r\n }\r\n }\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * 解析 WebDAV 多状态响应\r\n * @param xml XML 文档\r\n * @returns 解析后的响应数组\r\n */\r\nexport function parseMultiStatus(xml: Document): Array<{\r\n href: string;\r\n status?: string;\r\n statusCode?: number;\r\n properties: Record<string, any>;\r\n}> {\r\n const result: Array<{\r\n href: string;\r\n status?: string;\r\n statusCode?: number;\r\n properties: Record<string, any>;\r\n }> = [];\r\n \r\n // 查找所有 response 元素\r\n const responseElements = xml.getElementsByTagNameNS(WebDAVNamespace.DAV, 'response');\r\n \r\n for (let i = 0; i < responseElements.length; i++) {\r\n const responseElement = responseElements[i];\r\n \r\n // 获取 href\r\n const hrefElement = responseElement.getElementsByTagNameNS(WebDAVNamespace.DAV, 'href')[0];\r\n const href = hrefElement ? decodeURIComponent(hrefElement.textContent || '') : '';\r\n \r\n // 获取状态\r\n const statusElement = responseElement.getElementsByTagNameNS(WebDAVNamespace.DAV, 'status')[0];\r\n const status = statusElement ? statusElement.textContent || '' : undefined;\r\n \r\n // 解析状态码\r\n let statusCode: number | undefined;\r\n if (status) {\r\n const match = status.match(/HTTP\\/\\d+\\.\\d+\\s+(\\d+)/);\r\n if (match) {\r\n statusCode = parseInt(match[1], 10);\r\n }\r\n }\r\n \r\n // 解析属性\r\n const properties = parseWebDAVProperties(responseElement as unknown as Document);\r\n \r\n result.push({\r\n href,\r\n status,\r\n statusCode,\r\n properties,\r\n });\r\n }\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * 创建 PROPFIND 请求体\r\n * @param props 要查询的属性\r\n * @returns XML 字符串\r\n */\r\nexport function createPropfindXml(props: string[] = []): string {\r\n if (props.length === 0) {\r\n // 查询所有属性\r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propfind xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:allprop/>\r\n</d:propfind>`;\r\n } else {\r\n // 查询指定属性\r\n const propXml = props.map(prop => `<d:${prop}/>`).join('');\r\n \r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propfind xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:prop>\r\n ${propXml}\r\n </d:prop>\r\n</d:propfind>`;\r\n }\r\n}\r\n\r\n/**\r\n * 创建 PROPPATCH 请求体\r\n * @param props 要设置的属性\r\n * @returns XML 字符串\r\n */\r\nexport function createProppatchXml(props: Record<string, string>): string {\r\n const propXml = Object.entries(props)\r\n .map(([key, value]) => `<d:${key}>${value}</d:${key}>`)\r\n .join('');\r\n \r\n return `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<d:propertyupdate xmlns:d=\"${WebDAVNamespace.DAV}\">\r\n <d:set>\r\n <d:prop>\r\n ${propXml}\r\n </d:prop>\r\n </d:set>\r\n</d:propertyupdate>`;\r\n}\r\n\r\n/**\r\n * 将 WebDAV 属性转换为 Stats 对象\r\n * @param href 资源路径\r\n * @param properties 属性\r\n * @returns Stats 对象\r\n */\r\nexport function propertiesToStats(href: string, properties: Record<string, any>): Stats {\r\n const name = getBasename(href);\r\n const isDirectory = properties.resourceType === WebDAVResourceType.DIRECTORY;\r\n \r\n return {\r\n isDirectory,\r\n isFile: !isDirectory,\r\n size: properties.size || 0,\r\n createdAt: properties.createdAt || new Date(),\r\n lastModified: properties.modifiedAt || new Date(),\r\n name,\r\n path: href,\r\n mimeType: properties.mimeType,\r\n etag: properties.etag,\r\n ...properties,\r\n };\r\n}\r\n\r\n/**\r\n * 解析 XML 字符串\r\n * @param xmlString XML 字符串\r\n * @returns XML 文档\r\n */\r\nexport function parseXml(xmlString: string): Document {\r\n if (typeof DOMParser !== 'undefined') {\r\n // 浏览器环境\r\n const parser = new DOMParser();\r\n return parser.parseFromString(xmlString, 'application/xml');\r\n } else if (typeof window === 'undefined') {\r\n // Node.js 环境\r\n try {\r\n // 尝试使用 xmldom\r\n const { DOMParser } = require('xmldom');\r\n return new DOMParser().parseFromString(xmlString, 'application/xml');\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error);\r\n throw new WebDAVError(`无法解析 XML: ${errorMessage}。在 Node.js 环境中,请安装 xmldom 包。`);\r\n }\r\n } else {\r\n throw new WebDAVError('无法解析 XML。未找到 XML 解析器。');\r\n }\r\n}\r\n\r\n/**\r\n * 检查响应是否成功\r\n * @param response 响应对象\r\n * @returns 是否成功\r\n */\r\nexport function isSuccessStatus(status: number): boolean {\r\n return status >= 200 && status < 300;\r\n}\r\n\r\n/**\r\n * 从 URL 中提取路径\r\n * @param url URL\r\n * @param baseUrl 基础 URL\r\n * @returns 路径\r\n */\r\nexport function extractPathFromUrl(url: string, baseUrl: string): string {\r\n // 移除基础 URL\r\n if (url.startsWith(baseUrl)) {\r\n url = url.slice(baseUrl.length);\r\n }\r\n \r\n // 解码 URL\r\n url = decodeURIComponent(url);\r\n \r\n // 规范化路径\r\n return normalizePath(url);\r\n}\r\n\r\n/**\r\n * 创建超时 Promise\r\n * @param ms 超时时间(毫秒)\r\n * @returns Promise\r\n */\r\nexport function createTimeoutPromise(ms: number): Promise<never> {\r\n return new Promise((_, reject) => {\r\n setTimeout(() => {\r\n reject(new WebDAVError(`请求超时(${ms}ms)`));\r\n }, ms);\r\n });\r\n}\r\n\r\n/**\r\n * 创建 AbortController\r\n * @returns AbortController 或 undefined\r\n */\r\nexport function createAbortController(): AbortController | undefined {\r\n if (typeof AbortController !== 'undefined') {\r\n return new AbortController();\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * 检查是否支持 Blob\r\n * @returns 是否支持\r\n */\r\nexport function isBlobSupported(): boolean {\r\n return typeof Blob !== 'undefined';\r\n}\r\n\r\n/**\r\n * 检查是否支持 ArrayBuffer\r\n * @returns 是否支持\r\n */\r\nexport function isArrayBufferSupported(): boolean {\r\n return typeof ArrayBuffer !== 'undefined';\r\n}\r\n\r\n/**\r\n * 将数据转换为 ArrayBuffer\r\n * @param data 数据\r\n * @returns ArrayBuffer\r\n */\r\nexport async function toArrayBuffer(data: string | ArrayBuffer | Blob): Promise<ArrayBuffer> {\r\n if (data instanceof ArrayBuffer) {\r\n return data;\r\n } else if (typeof Blob !== 'undefined' && data instanceof Blob) {\r\n return await data.arrayBuffer();\r\n } else if (typeof data === 'string') {\r\n if (typeof TextEncoder !== 'undefined') {\r\n return new TextEncoder().encode(data).buffer;\r\n } else if (typeof Buffer !== 'undefined') {\r\n // Buffer.buffer may be a larger ArrayBuffer; create a new ArrayBuffer\r\n // that exactly matches the Buffer contents to satisfy strict typing\r\n const buf = Buffer.from(data);\r\n // slice the underlying ArrayBuffer to the buffer's byte range\r\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer;\r\n } else {\r\n throw new WebDAVError('无法将字符串转换为 ArrayBuffer');\r\n }\r\n } else {\r\n throw new WebDAVError('不支持的数据类型');\r\n }\r\n}\r\n\r\n/**\r\n * 将 ArrayBuffer 转换为字符串\r\n * @param buffer ArrayBuffer\r\n * @param encoding 编码\r\n * @returns 字符串\r\n */\r\nexport function arrayBufferToString(buffer: ArrayBuffer, encoding = 'utf-8'): string {\r\n if (typeof TextDecoder !== 'undefined') {\r\n // 浏览器环境\r\n const decoder = new TextDecoder(encoding);\r\n return decoder.decode(buffer);\r\n } else if (typeof Buffer !== 'undefined') {\r\n // Node.js 环境\r\n return Buffer.from(buffer).toString(encoding as BufferEncoding);\r\n } else {\r\n throw new WebDAVError('无法将 ArrayBuffer 转换为字符串,当前环境不支持 TextDecoder 或 Buffer');\r\n }\r\n}\r\n\r\n/**\r\n * 根据文件名获取Content-Type\r\n * @param filename 文件名\r\n * @returns Content-Type字符串\r\n */\r\nexport function getContentType(filename: string): string {\r\n const ext = filename.split('.').pop()?.toLowerCase();\r\n switch (ext) {\r\n case 'txt': return 'text/plain';\r\n case 'html': case 'htm': return 'text/html';\r\n case 'json': return 'application/json';\r\n case 'xml': return 'application/xml';\r\n case 'jpg': case 'jpeg': return 'image/jpeg';\r\n case 'png': return 'image/png';\r\n case 'gif': return 'image/gif';\r\n case 'pdf': return 'application/pdf';\r\n case 'csv': return 'text/csv';\r\n case 'js': return 'application/javascript';\r\n case 'css': return 'text/css';\r\n case 'zip': return 'application/zip';\r\n default: return 'application/octet-stream';\r\n }\r\n}\r\n\r\n// 获取路径的父目录\r\nexport function getDirFromPath(path: string): string {\r\n if (!path || path === '/') return '/';\r\n const normalized = path.replace(/\\/+$/, '');\r\n const idx = normalized.lastIndexOf('/');\r\n if (idx <= 0) return '/';\r\n return normalized.slice(0, idx) || '/';\r\n}\r\n\r\n/**\r\n * 解析WebDAV PROPFIND XML响应,返回文件/目录信息数组\r\n * @param xml XML字符串\r\n * @param basePath 基础路径\r\n * @returns Stats[]\r\n */\r\n\r\n// 导入he库用于HTML实体解码\r\nimport * as he from 'he';\r\n\r\nexport function parseWebDAVXml(xml: string, basePath: string): Stats[] {\r\n // 简单实现,实际可用 xml2js、fast-xml-parser 等库解析\r\n // 这里只做最基础的兼容,建议根据实际WebDAV响应完善\r\n const decode = he.decode;\r\n const result: Stats[] = [];\r\n if (!xml) return result;\r\n const parser = new XMLParser({\r\n ignoreAttributes: false,\r\n attributeNamePrefix: \"@_\",\r\n trimValues: true\r\n });\r\n const json = parser.parse(xml);\r\n // 兼容不同大小写的 multistatus/response 属性\r\n function getCaseInsensitive(obj: unknown, ...keys: string[]): unknown {\r\n if (!obj) return undefined;\r\n for (const key of keys) {\r\n // 直接匹配\r\n if ((obj as Record<string, unknown>)[key] !== undefined) return (obj as Record<string, unknown>)[key];\r\n \r\n // 不区分大小写匹配\r\n const found = Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase());\r\n if (found) return (obj as Record<string, unknown>)[found];\r\n \r\n // 匹配任意命名空间前缀的标签名(例如 resourcetype 匹配 lp1:resourcetype, d:resourcetype 等)\r\n const keyWithoutPrefix = key.includes(':') ? key.split(':').pop()! : key;\r\n const foundWithNamespace = Object.keys(obj).find(k => {\r\n const kWithoutPrefix = k.includes(':') ? k.split(':').pop()! : k;\r\n return kWithoutPrefix.toLowerCase() === keyWithoutPrefix.toLowerCase();\r\n });\r\n if (foundWithNamespace) return (obj as Record<string, unknown>)[foundWithNamespace];\r\n }\r\n return undefined;\r\n }\r\n const multistatus = getCaseInsensitive(json, 'd:multistatus', 'multistatus');\r\n const responses = getCaseInsensitive(multistatus, 'd:response', 'response') || [];\r\n const arr = Array.isArray(responses) ? responses : [responses];\r\n const isBasePathFile = basePath && !basePath.endsWith('/');\r\n for (const item of arr) {\r\n const hrefRaw = getCaseInsensitive(item, 'd:href', 'href');\r\n const href = decode(typeof hrefRaw === 'string' ? hrefRaw : hrefRaw ? String(hrefRaw) : '');\r\n const propstat = getCaseInsensitive(item, 'd:propstat', 'propstat', 'd:prop', 'prop') || {};\r\n const prop = getCaseInsensitive(propstat, 'd:prop', 'prop') || propstat;\r\n const resourcetype = getCaseInsensitive(prop, 'd:resourcetype', 'resourcetype');\r\n const collection = resourcetype && getCaseInsensitive(resourcetype, 'd:collection', 'collection');\r\n const getcontentlength = getCaseInsensitive(prop, 'd:getcontentlength', 'getcontentlength');\r\n \r\n // 判断是否为目录:\r\n // 1. resourcetype 中有 collection 元素 -> 目录\r\n // 2. 如果有 getcontentlength -> 文件(即使 href 以 / 结尾)\r\n // 3. 否则根据 href 是否以斜杠结尾判断(WebDAV 规范中目录通常以 / 结尾)\r\n // 注意:空的 resourcetype(如 <lp1:resourcetype/>)会被解析为空字符串或空对象\r\n const hasCollection = !!(resourcetype && typeof resourcetype === 'object' && collection !== undefined);\r\n const isDirectory = hasCollection || (!getcontentlength && href.endsWith('/'));\r\n\r\n // 如果basePath是文件,则直接取文件名,否则去除basePath前缀\r\n let name: string;\r\n if (isBasePathFile) {\r\n name = href.split('/').filter(Boolean).pop() || '';\r\n } else {\r\n name = href.replace(basePath, '').replace(/^\\//, '').replace(/\\/$/, '');\r\n }\r\n if (!name) continue;\r\n result.push({\r\n path: href,\r\n name,\r\n isDirectory,\r\n isFile: !isDirectory,\r\n size: parseInt(String(getCaseInsensitive(prop, 'd:getcontentlength', 'getcontentlength') ?? '0'), 10),\r\n lastModified: getCaseInsensitive(prop, 'd:getlastmodified', 'getlastmodified')\r\n ? new Date(String(getCaseInsensitive(prop, 'd:getlastmodified', 'getlastmodified')))\r\n : undefined,\r\n });\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * 拼接URL路径,自动处理斜杠\r\n * @param base 基础URL\r\n * @param paths 追加的路径\r\n * @returns 拼接后的URL\r\n */\r\nexport function joinUrl(base: string, ...paths: string[]): string {\r\n let url = base;\r\n let basePath = '';\r\n try {\r\n const u = new URL(base);\r\n basePath = u.pathname.replace(/\\/+$/, '');\r\n } catch {\r\n basePath = base.startsWith('/') ? base.replace(/\\/+$/, '') : '';\r\n }\r\n // 只处理第一个 path,只去除与 basePath 第一部分相同的部分\r\n if (paths.length > 0 && basePath) {\r\n let p = paths[0];\r\n if (p) {\r\n // 取 basePath 的第一部分\r\n const baseFirst = basePath.split('/').filter(Boolean)[0];\r\n const pParts = p.split('/').filter(Boolean);\r\n if (baseFirst && pParts[0] === baseFirst) {\r\n pParts.shift();\r\n p = pParts.join('/');\r\n if (p && !p.startsWith('/')) p = '/' + p;\r\n paths[0] = p;\r\n }\r\n }\r\n }\r\n for (const p of paths) {\r\n if (!p) continue;\r\n if (!url.endsWith('/')) url += '/';\r\n url += p.startsWith('/') ? p.slice(1) : p;\r\n }\r\n url = url.replace(/([^:]\\/)\\/+/g, '$1');\r\n return url;\r\n}","/**\r\n * WebDAV文件系统实现\r\n */\r\n\r\nimport {\r\n WebDAVOptions,\r\n Stats,\r\n ReadFileOptions,\r\n WriteFileOptions,\r\n ReaddirOptions,\r\n MkdirOptions,\r\n WebDAVResult,\r\n} from './types';\r\n\r\nimport { WebDAVFileSystem } from './WebDAVFileSystem';\r\n\r\nimport {\r\n WebDAVError,\r\n NotFoundError,\r\n AuthenticationError,\r\n AuthorizationError,\r\n FileExistsError,\r\n NetworkError,\r\n TimeoutError,\r\n ArgumentError,\r\n ServerError,\r\n} from './errors';\r\n\r\nimport {\r\n normalizePath,\r\n joinUrl,\r\n getDirFromPath,\r\n parseWebDAVXml,\r\n createBasicAuthHeader,\r\n getContentType,\r\n} from './utils';\r\n\r\n// 兼容 TextDecoder(浏览器和 Node.js)\r\nclass TextDecoderFallback {\r\n constructor(private encoding: string) {}\r\n decode(buffer: ArrayBuffer) {\r\n const uint8Array = new Uint8Array(buffer);\r\n return Array.from(uint8Array).map(byte => String.fromCharCode(byte)).join('');\r\n }\r\n}\r\n\r\nlet TextDecoderImpl: any;\r\n\r\n// 尝试获取 TextDecoder\r\nif (typeof TextDecoder !== 'undefined') {\r\n TextDecoderImpl = TextDecoder;\r\n} else {\r\n try {\r\n // 在 Node.js 中,尝试从全局对象获取\r\n if (typeof global !== 'undefined' && (global as any).TextDecoder) {\r\n TextDecoderImpl = (global as any).TextDecoder;\r\n } else {\r\n // 如果都失败了,使用后备实现\r\n TextDecoderImpl = TextDecoderFallback;\r\n }\r\n } catch {\r\n // 作为最终后备,使用简单的 ASCII 解码\r\n TextDecoderImpl = TextDecoderFallback;\r\n }\r\n}\r\n\r\n/**\r\n * WebDAV文件系统实现类\r\n */\r\nexport class WebDAVFS implements WebDAVFileSystem {\r\n private baseUrl: string;\r\n private auth?: { username: string; password: string };\r\n private timeout: number;\r\n private headers: Record<string, string>;\r\n\r\n /**\r\n * 创建WebDAV文件系统实例\r\n * @param options WebDAV选项\r\n */\r\n constructor(options: WebDAVOptions) {\r\n if (!options.baseUrl) {\r\n throw new ArgumentError('必须提供baseUrl');\r\n }\r\n\r\n this.baseUrl = options.baseUrl.endsWith('/') \r\n ? options.baseUrl.slice(0, -1) \r\n : options.baseUrl;\r\n if (options.username && options.password) {\r\n this.auth = { username: options.username, password: options.password };\r\n } else {\r\n this.auth = undefined;\r\n }\r\n this.timeout = options.timeout || 30000;\r\n this.headers = options.headers || {};\r\n }\r\n\r\n /**\r\n * 创建请求头\r\n * @param customHeaders 自定义请求头\r\n * @returns 合并后的请求头\r\n */\r\n private createHeaders(customHeaders?: Record<string, string>): Record<string, string> {\r\n const headers: Record<string, string> = {\r\n ...this.headers,\r\n ...customHeaders,\r\n };\r\n\r\n // 添加认证头\r\n if (this.auth) {\r\n headers['Authorization'] = createBasicAuthHeader(this.auth.username, this.auth.password);\r\n }\r\n\r\n return headers;\r\n }\r\n\r\n /**\r\n * 执行HTTP请求\r\n * @param method HTTP方法\r\n * @param path 请求路径\r\n * @param options 请求选项\r\n * @returns 响应对象\r\n */\r\n private async request(\r\n method: string,\r\n path: string,\r\n options: {\r\n headers?: Record<string, string>;\r\n body?: string | ArrayBuffer | Blob | Buffer;\r\n responseType?: 'text' | 'arraybuffer' | 'blob' | 'json';\r\n } = {}\r\n ): Promise<{ data: any; status: number; headers: Record<string, string> }> {\r\n const normalizedPath = normalizePath(path);\r\n const url = joinUrl(this.baseUrl, normalizedPath);\r\n const headers = this.createHeaders(options.headers);\r\n \r\n // 创建AbortController用于超时处理\r\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;\r\n const timeoutId = controller ? setTimeout(() => controller.abort(), this.timeout) : undefined;\r\n \r\n try {\r\n // 使用fetch API(浏览器和现代Node.js都支持)\r\n const response = await fetch(url, {\r\n method,\r\n headers,\r\n body: options.body as BodyInit | null | undefined,\r\n signal: controller?.signal,\r\n credentials: 'include',\r\n });\r\n \r\n // 清除超时\r\n if (timeoutId) clearTimeout(timeoutId);\r\n \r\n // 提取响应头\r\n const responseHeaders: Record<string, string> = {};\r\n response.headers.forEach((value, key) => {\r\n responseHeaders[key.toLowerCase()] = value;\r\n });\r\n \r\n // 根据请求的responseType处理响应数据\r\n let data;\r\n if (options.responseType === 'arraybuffer') {\r\n data = await response.arrayBuffer();\r\n } else if (options.responseType === 'blob') {\r\n data = await response.blob();\r\n } else if (options.responseType === 'json') {\r\n data = await response.json();\r\n } else {\r\n data = await response.text();\r\n }\r\n \r\n return {\r\n data,\r\n status: response.status,\r\n headers: responseHeaders,\r\n };\r\n } catch (error: unknown) {\r\n // 清除超时\r\n if (timeoutId) clearTimeout(timeoutId);\r\n \r\n // 处理错误\r\n if ((error as Error).name === 'AbortError') {\r\n throw new TimeoutError(`请求超时: ${url}`);\r\n } else {\r\n throw new NetworkError(error as Error, `网络错误: ${(error as Error).message}`);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 处理响应错误\r\n * @param status HTTP状态码\r\n * @param path 请求路径\r\n * @param error 原始错误\r\n */\r\n private handleResponseError(status: number, path: string, error?: Error): never {\r\n switch (status) {\r\n case 401:\r\n throw new AuthenticationError(`认证失败: ${path}`);\r\n case 403:\r\n throw new AuthorizationError(`无权限访问: ${path}`);\r\n case 404:\r\n throw new NotFoundError(`资源不存在: ${path}`);\r\n case 409:\r\n throw new FileExistsError(`资源已存在: ${path}`);\r\n default:\r\n throw new ServerError(`服务器错误 (${status}): ${path}`, status);\r\n }\r\n }\r\n\r\n /**\r\n * 读取文件内容\r\n * @param path 文件路径\r\n * @param encodingOrOptions 编码字符串或读取选项对象\r\n * @returns 文件内容\r\n */\r\n async readFile(\r\n path: string, \r\n encodingOrOptions?: string | ReadFileOptions\r\n ): Promise<Buffer | string> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n // 处理参数:兼容 fs.promises.readFile(path, encoding) 和原有的 readFile(path, options)\r\n let options: ReadFileOptions = {};\r\n if (typeof encodingOrOptions === 'string') {\r\n // Node.js fs.promises 风格:readFile(path, encoding)\r\n options = { encoding: encodingOrOptions as BufferEncoding };\r\n } else if (encodingOrOptions) {\r\n // 原有风格:readFile(path, options)\r\n options = encodingOrOptions;\r\n }\r\n \r\n try {\r\n const response = await this.request('GET', normalizedPath, {\r\n headers: options.headers,\r\n responseType: 'arraybuffer',\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 处理响应数据(兼容浏览器和 Node.js)\r\n const arrayBuffer = response.data instanceof ArrayBuffer \r\n ? response.data \r\n : response.data.buffer || response.data;\r\n\r\n // 根据编码返回字符串或 Buffer\r\n if (options.encoding) {\r\n // 使用 TextDecoder 解码(浏览器兼容)\r\n const decoder = new TextDecoderImpl(options.encoding as string);\r\n return decoder.decode(arrayBuffer);\r\n } else {\r\n // 返回 Buffer(Node.js)或 Uint8Array(浏览器)\r\n if (typeof Buffer !== 'undefined') {\r\n return Buffer.from(arrayBuffer);\r\n } else {\r\n return new Uint8Array(arrayBuffer) as any;\r\n }\r\n }\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`读取文件失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 写入文件内容\r\n * @param path 文件路径\r\n * @param data 文件内容\r\n * @param options 写入选项\r\n * @returns 操作结果\r\n */\r\n async writeFile(\r\n path: string,\r\n data: Buffer | string,\r\n options: WriteFileOptions = {}\r\n ): Promise<WebDAVResult> {\r\n const normalizedPath = normalizePath(path);\r\n const getFilenameFromPath = (p: string) => {\r\n const parts = p.split('/');\r\n return parts[parts.length - 1] || '';\r\n };\r\n const contentType = options.contentType || getContentType(getFilenameFromPath(normalizedPath));\r\n // 检查文件是否存在(如果不允许覆盖)\r\n if (options.overwrite === false) {\r\n const exists = await this.exists(normalizedPath);\r\n if (exists) {\r\n throw new FileExistsError(normalizedPath);\r\n }\r\n }\r\n \r\n // 准备请求头\r\n const headers = {\r\n 'Content-Type': contentType,\r\n };\r\n \r\n try {\r\n const response = await this.request('PUT', normalizedPath, {\r\n headers,\r\n body: data,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`写入文件失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件\r\n * @param path 文件路径\r\n * @returns 操作结果\r\n */\r\n async deleteFile(path: string): Promise<WebDAVResult> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 确保是文件而不是目录\r\n const stat = await this.stat(normalizedPath);\r\n if (stat.isDirectory) {\r\n throw new ArgumentError(`路径指向一个目录,请使用rmdir方法: ${normalizedPath}`);\r\n }\r\n \r\n const response = await this.request('DELETE', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`删除文件失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 读取目录内容\r\n * @param path 目录路径\r\n * @param options 读取选项\r\n * @returns 文件统计信息数组\r\n */\r\n async readDir(path: string, options: ReaddirOptions = {}): Promise<Stats[]> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 准备PROPFIND请求\r\n const headers = {\r\n 'Depth': options.recursive ? 'infinity' : '1',\r\n 'Content-Type': 'application/xml',\r\n };\r\n \r\n const response = await this.request('PROPFIND', normalizedPath, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 解析XML响应\r\n const files = parseWebDAVXml(response.data, normalizedPath);\r\n \r\n // 过滤结果\r\n const result = files.filter(file => {\r\n // 排除当前目录\r\n if (file.path === normalizedPath) {\r\n return false;\r\n }\r\n \r\n // 处理隐藏文件\r\n if (!options.includeHidden && file.name.startsWith('.')) {\r\n return false;\r\n }\r\n \r\n return true;\r\n });\r\n \r\n return result;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`读取目录失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 创建目录\r\n * @param path 目录路径\r\n * @param options 创建选项\r\n * @returns 操作结果\r\n */\r\n async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n // 检查目录是否已存在\r\n let pathExists = false;\r\n let isDirectory = false;\r\n \r\n try {\r\n const stat = await this.stat(normalizedPath);\r\n pathExists = true;\r\n isDirectory = stat.isDirectory;\r\n } catch (error) {\r\n // stat 失败,路径不存在或其他错误\r\n pathExists = false;\r\n }\r\n \r\n // 如果路径已存在\r\n if (pathExists) {\r\n if (isDirectory) {\r\n // 是目录:递归模式下返回成功,非递归模式抛出错误\r\n if (options.recursive) {\r\n return; // 递归模式下目录存在是OK的\r\n }\r\n throw new FileExistsError(normalizedPath);\r\n } else {\r\n // 路径存在但不是目录,总是抛出错误\r\n throw new FileExistsError(normalizedPath);\r\n }\r\n }\r\n \r\n // 到这里说明目录不存在,需要创建\r\n try {\r\n // 如果需要递归创建父目录\r\n if (options.recursive) {\r\n const parentDir = getDirFromPath(normalizedPath);\r\n if (parentDir !== '/' && parentDir !== normalizedPath) {\r\n try {\r\n await this.stat(parentDir);\r\n // 父目录存在,继续\r\n } catch (error) {\r\n // 父目录不存在,递归创建\r\n await this.mkdir(parentDir, options);\r\n }\r\n }\r\n }\r\n \r\n // 创建目录\r\n const response = await this.request('MKCOL', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n // 特殊处理: 405 可能表示目录已存在\r\n if (response.status === 405 && options.recursive) {\r\n try {\r\n const stat = await this.stat(normalizedPath);\r\n if (stat.isDirectory) {\r\n return; // 目录确实存在,视为成功\r\n }\r\n } catch (e) {\r\n // 检查失败,继续抛出原始错误\r\n }\r\n }\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`创建目录失败: ${normalizedPath}`);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件或目录,行为类似于 Node.js 的 fs.rm\r\n * @param path 路径\r\n * @param options { recursive?: boolean, force?: boolean }\r\n */\r\n async rm(path: string, options?: { recursive?: boolean, force?: boolean }) {\r\n const { recursive = false, force = false } = options || {};\r\n try {\r\n const stat = await this.stat(path);\r\n if (stat.isDirectory) {\r\n if (recursive) {\r\n // 递归删除目录内容\r\n const files = await this.readDir(path);\r\n for (const file of files) {\r\n const childPath = path.replace(/\\/$/, '') + '/' + file.name;\r\n await this.rm(childPath, { recursive: true, force });\r\n }\r\n } else {\r\n // 非递归时,目录必须为空\r\n const files = await this.readDir(path);\r\n if (files.length > 0) {\r\n throw new WebDAVError(`Directory not empty: ${path}`);\r\n }\r\n }\r\n }\r\n // 删除文件或空目录\r\n await this._delete(path);\r\n } catch (err: unknown) {\r\n const error = err as any;\r\n if (force && (error.code === 'ENOENT' || error.status === 404)) {\r\n // force=true 时忽略不存在\r\n return;\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * 兼容旧的 rmdir 方法,内部重定向到 rm\r\n * @deprecated 请使用 rm\r\n */\r\n async rmdir(path: string, options?: boolean | { recursive?: boolean, force?: boolean }) {\r\n // 兼容 boolean 递归参数\r\n let opts: { recursive?: boolean, force?: boolean } = {};\r\n if (typeof options === 'boolean') {\r\n opts.recursive = options;\r\n } else if (typeof options === 'object' && options !== null) {\r\n opts = options;\r\n }\r\n return this.rm(path, opts);\r\n }\r\n\r\n /**\r\n * 删除文件或目录\r\n * @param path 文件或目录路径\r\n * @returns 操作结果\r\n */\r\n private async _delete(path: string) {\r\n // 实现 WebDAV DELETE 请求\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n const response = await this.request('DELETE', normalizedPath);\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`删除失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 获取文件或目录的统计信息\r\n * @param path 文件或目录路径\r\n * @returns 文件统计信息\r\n */\r\n async stat(path: string): Promise<Stats> {\r\n const normalizedPath = normalizePath(path);\r\n \r\n try {\r\n // 准备PROPFIND请求\r\n const headers = {\r\n 'Depth': '0',\r\n 'Content-Type': 'application/xml',\r\n };\r\n \r\n const response = await this.request('PROPFIND', normalizedPath, {\r\n headers,\r\n });\r\n \r\n if (response.status === 404) {\r\n throw new NotFoundError(normalizedPath);\r\n } else if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedPath);\r\n }\r\n \r\n // 解析XML响应\r\n const files = parseWebDAVXml(response.data, normalizedPath);\r\n \r\n if (files.length === 0) {\r\n throw new NotFoundError(normalizedPath);\r\n }\r\n \r\n const stat = files[0];\r\n \r\n return stat;\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`获取文件信息失败: ${normalizedPath}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 检查文件或目录是否存在\r\n * @param path 文件或目录路径\r\n * @returns 是否存在\r\n */\r\n async exists(path: string): Promise<boolean> {\r\n try {\r\n await this.stat(path);\r\n return true;\r\n } catch (error) {\r\n if (error instanceof NotFoundError) {\r\n return false;\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * 复制文件或目录\r\n * @param source 源路径\r\n * @param destination 目标路径\r\n * @param overwrite 是否覆盖已存在的文件\r\n * @returns 操作结果\r\n */\r\n async copy(source: string, destination: string, overwrite = true): Promise<WebDAVResult> {\r\n const normalizedSource = normalizePath(source);\r\n const normalizedDestination = normalizePath(destination);\r\n \r\n try {\r\n // 检查源文件是否存在\r\n await this.stat(normalizedSource);\r\n \r\n // 检查目标文件是否存在(如果不允许覆盖)\r\n if (!overwrite) {\r\n const exists = await this.exists(normalizedDestination);\r\n if (exists) {\r\n throw new FileExistsError(normalizedDestination);\r\n }\r\n }\r\n \r\n // 准备COPY请求\r\n const headers = {\r\n 'Destination': joinUrl(this.baseUrl, normalizedDestination),\r\n 'Overwrite': overwrite ? 'T' : 'F',\r\n };\r\n \r\n const response = await this.request('COPY', normalizedSource, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedSource);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`复制失败: ${normalizedSource} -> ${normalizedDestination}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 移动文件或目录\r\n * @param source 源路径\r\n * @param destination 目标路径\r\n * @param overwrite 是否覆盖已存在的文件\r\n * @returns 操作结果\r\n */\r\n async move(source: string, destination: string, overwrite = true): Promise<WebDAVResult> {\r\n const normalizedSource = normalizePath(source);\r\n const normalizedDestination = normalizePath(destination);\r\n \r\n try {\r\n // 检查源文件是否存在\r\n await this.stat(normalizedSource);\r\n \r\n // 检查目标文件是否存在(如果不允许覆盖)\r\n if (!overwrite) {\r\n const exists = await this.exists(normalizedDestination);\r\n if (exists) {\r\n throw new FileExistsError(normalizedDestination);\r\n }\r\n }\r\n \r\n // 准备MOVE请求\r\n const headers = {\r\n 'Destination': joinUrl(this.baseUrl, normalizedDestination),\r\n 'Overwrite': overwrite ? 'T' : 'F',\r\n };\r\n \r\n const response = await this.request('MOVE', normalizedSource, {\r\n headers,\r\n });\r\n \r\n if (response.status >= 400) {\r\n this.handleResponseError(response.status, normalizedSource);\r\n }\r\n \r\n return {\r\n success: response.status >= 200 && response.status < 300,\r\n statusCode: response.status,\r\n };\r\n } catch (error: unknown) {\r\n if (error instanceof WebDAVError) {\r\n throw error;\r\n }\r\n throw new WebDAVError(`移动失败: ${normalizedSource} -> ${normalizedDestination}`, undefined, error);\r\n }\r\n }\r\n\r\n /**\r\n * 删除文件(fs/unlink 兼容方法)\r\n * @param path 文件路径\r\n */\r\n async unlink(path: string): Promise<void> {\r\n await this.deleteFile(path);\r\n }\r\n\r\n // ============================================\r\n // Node.js fs.promises 兼容接口\r\n // 这些方法使 WebDAVFS 可以直接作为 IFileSystem 使用\r\n // ============================================\r\n\r\n /**\r\n * 读取目录(fs.promises.readdir 兼容)\r\n * @param path 目录路径\r\n * @returns 文件名数组\r\n */\r\n async readdir(path: string): Promise<string[]> {\r\n const stats = await this.readDir(path);\r\n return stats.map(stat => stat.name);\r\n }\r\n\r\n /**\r\n * 重命名/移动文件(fs.promises.rename 兼容)\r\n * @param oldPath 原路径\r\n * @param newPath 新路径\r\n */\r\n async rename(oldPath: string, newPath: string): Promise<void> {\r\n await this.move(oldPath, newPath, true);\r\n }\r\n}","import { WebDAVOptions } from './types';\r\nimport { WebDAVFS } from './webdav-fs';\r\nimport { WebDAVFileSystem } from './WebDAVFileSystem';\r\n\r\n/**\r\n * 创建 WebDAV 文件系统实例(工厂函数)\r\n * @param options WebDAV 配置选项\r\n * @returns WebDAVFileSystem 实例\r\n */\r\nexport function createWebDAVFileSystem(options: WebDAVOptions): WebDAVFileSystem {\r\n return new WebDAVFS(options);\r\n}"],"mappings":"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","names":["decode"]}
|