MCPcopy
hub / github.com/codedogQBY/ReadAny / WebDavClient

Class WebDavClient

packages/core/src/sync/webdav-client.ts:236–695  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

234}
235
236export class WebDavClient {
237 private baseUrl: string;
238 private authHeader: string;
239 private allowInsecure: boolean;
240 /**
241 * Flips to true after the first 2xx/207 response. Once true, a 401 is
242 * treated as a server-side throttle (retry-worthy) rather than a credential
243 * failure. Reset per WebDavClient instance.
244 */
245 private hadAuthSuccess = false;
246
247 constructor(url: string, username: string, password: string, allowInsecure?: boolean) {
248 // Normalize: remove control chars/whitespace and trailing slash
249 this.baseUrl = sanitizeWebDavUrl(url);
250 // Basic auth header
251 const credentials = `${username}:${password}`;
252 // Use UTF-8 safe base64 encoding; btoa is unreliable in React Native/Android.
253 const encoded = Buffer.from(credentials, "utf8").toString("base64");
254 this.authHeader = `Basic ${encoded}`;
255 console.log("[WebDAV] auth configured", {
256 username: username.length > 2 ? `${username[0]}***${username[username.length - 1]}` : "***",
257 passwordLength: password.length,
258 });
259 this.allowInsecure = allowInsecure ?? false;
260 }
261
262 private getTimeout(method: string, explicitTimeoutMs?: number): number {
263 if (explicitTimeoutMs !== undefined) return explicitTimeoutMs;
264 const isTransferOperation = method === "PUT" || method === "GET";
265 return isTransferOperation ? TRANSFER_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
266 }
267
268 private buildUrl(path: string): string {
269 const normalizedPath = path.startsWith("/") ? path : `/${path}`;
270 // Encode path segments but preserve /
271 const encoded = normalizedPath
272 .split("/")
273 .map((segment) => encodeURIComponent(segment))
274 .join("/");
275 return `${this.baseUrl}${encoded}`;
276 }
277
278 private getAuthHeaders(): Record<string, string> {
279 return { Authorization: this.authHeader };
280 }
281
282 /** True if the status code indicates a transient, retry-worthy failure. */
283 private isTransientStatus(status: number): boolean {
284 // 401 BEFORE any successful auth = real credential failure, do not retry.
285 // 401 AFTER a successful response = server is throttling / temporarily
286 // rejecting valid credentials under burst load (Jianguoyun, some NAS).
287 if (status === 401 && this.hadAuthSuccess) return true;
288 if (status === 429) return true;
289 if (status >= 500 && status < 600) return true;
290 return false;
291 }
292
293 private async doFetch(

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected