From e014ed3e47017bfa0fe185ce4a7ea34bcc2da47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ai=E5=90=9B?= Date: Wed, 11 Sep 2024 16:42:54 +0000 Subject: [PATCH] updata --- output.py | 2 +- output.txt | 359 ---------------------------------- src/handlers/webdavHandler.ts | 127 +++--------- src/utils/webdavUtils.ts | 77 +++++--- 4 files changed, 75 insertions(+), 490 deletions(-) delete mode 100644 output.txt diff --git a/output.py b/output.py index f81cd15..6b6a745 100644 --- a/output.py +++ b/output.py @@ -19,7 +19,7 @@ def export_files(files, output_file): files_to_export = [ # "tsconfig.json", - "src/utils/templates.ts", + # "src/utils/templates.ts", # "package.json", "src/index.ts", "src/types.ts", diff --git a/output.txt b/output.txt deleted file mode 100644 index 2ef1c51..0000000 --- a/output.txt +++ /dev/null @@ -1,359 +0,0 @@ -文件名:src/utils/templates.ts -export function generateHTML(title: string, items: { name: string, href: string }[]): string { - return ` - - - - - - ${title} - - - -

${title}

-
- ${items.map(item => ` - - `).join('')} -
- - - `; - } - - export function generateErrorHTML(title: string, message: string): string { - return ` - - - - - - ${title} - - - -

${title}

-
- ${message} -
- - - `; - } - -文件名:src/index.ts -import { handleRequest } from './handlers/requestHandler'; -import { Env } from './types'; - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - return handleRequest(request, env, ctx); - } -}; - -文件名:src/types.ts -export interface Env { - bucket: R2Bucket; - USERNAME: string; - PASSWORD: string; -} - -export interface CacheableResponse { - response: Response; - expiry: number; -} - -文件名:src/handlers/requestHandler.ts -import { Env } from '../types'; -import { handleWebDAV } from './webdavHandler'; -import { authenticate } from '../utils/auth'; -import { setCORSHeaders } from '../utils/cors'; -import { logger } from '../utils/logger'; - -export async function handleRequest(request: Request, env: Env, ctx: ExecutionContext): Promise { - try { - if (request.method !== "OPTIONS" && !authenticate(request, env)) { - return new Response("Unauthorized", { - status: 401, - headers: { - "WWW-Authenticate": 'Basic realm="webdav"' - } - }); - } - - const response = await handleWebDAV(request, env.bucket); - - setCORSHeaders(response, request); - return response; - } catch (error) { - logger.error("Error in request handling:", error); - return new Response("Internal Server Error", { status: 500 }); - } -} - -文件名:src/handlers/webdavHandler.ts -import { listAll, fromR2Object, make_resource_path } from '../utils/webdavUtils'; -import { logger } from '../utils/logger'; -import { generateHTML, generateErrorHTML } from '../utils/templates'; - -export async function handleWebDAV(request: Request, bucket: R2Bucket): Promise { - const SUPPORT_METHODS = ["OPTIONS", "PROPFIND", "MKCOL", "GET", "HEAD", "PUT", "COPY", "MOVE", "DELETE"]; - const DAV_CLASS = "1"; - - try { - switch (request.method) { - case "OPTIONS": - return new Response(null, { - status: 204, - headers: { - Allow: SUPPORT_METHODS.join(", "), - DAV: DAV_CLASS - } - }); - case "HEAD": - return await handle_head(request, bucket); - case "GET": - return await handle_get(request, bucket); - case "PUT": - return await handle_put(request, bucket); - case "DELETE": - return await handle_delete(request, bucket); - case "MKCOL": - return await handle_mkcol(request, bucket); - case "PROPFIND": - return await handle_propfind(request, bucket); - case "COPY": - return await handle_copy(request, bucket); - case "MOVE": - return await handle_move(request, bucket); - default: - return new Response("Method Not Allowed", { - status: 405, - headers: { - Allow: SUPPORT_METHODS.join(", "), - DAV: DAV_CLASS - } - }); - } - } catch (error) { - logger.error("Error in WebDAV handling:", error); - return new Response(generateErrorHTML("Internal Server Error", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } -} - -async function handle_get(request: Request, bucket: R2Bucket): Promise { - const resource_path = make_resource_path(request); - - if (request.url.endsWith("/")) { - // 处理目录 - let items = []; - - if (resource_path !== "") { - items.push({ name: "📁 ..", href: "../" }); - } - - try { - for await (const object of listAll(bucket, resource_path)) { - if (object.key === resource_path) continue; - const isDirectory = object.customMetadata?.resourcetype === ""; - const displayName = object.key.split('/').pop() || object.key; - const href = `/${object.key + (isDirectory ? "/" : "")}`; - items.push({ name: `${isDirectory ? '📁 ' : '📄 '}${displayName}`, href }); - } - } catch (error) { - logger.error("Error listing objects:", error); - return new Response(generateErrorHTML("Error listing directory contents", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } - - const page = generateHTML("WebDAV File Browser", items); - return new Response(page, { - status: 200, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } else { - // 处理文件 - try { - const object = await bucket.get(resource_path); - if (!object) { - return new Response("Not Found", { status: 404 }); - } - return new Response(object.body, { - status: 200, - headers: { - "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream", - "Content-Length": object.size.toString(), - } - }); - } catch (error) { - logger.error("Error getting object:", error); - return new Response(generateErrorHTML("Error retrieving file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } - } -} - -async function handle_put(request: Request, bucket: R2Bucket): Promise { - const resource_path = make_resource_path(request); - - try { - const body = await request.arrayBuffer(); - await bucket.put(resource_path, body, { - httpMetadata: { - contentType: request.headers.get("Content-Type") || "application/octet-stream", - }, - }); - return new Response("Created", { status: 201 }); - } catch (error) { - logger.error("Error uploading file:", error); - return new Response(generateErrorHTML("Error uploading file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } -} - -// 实现其他WebDAV方法的处理函数:handle_head, handle_delete, handle_mkcol, handle_propfind, handle_copy, handle_move -// ... - - -文件名:src/utils/auth.ts -import { Env } from '../types'; - -export function authenticate(request: Request, env: Env): boolean { - const authHeader = request.headers.get("Authorization"); - const expectedAuth = `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`; - return authHeader === expectedAuth; -} - - -文件名:src/utils/cors.ts -export function setCORSHeaders(response: Response, request: Request): void { - const SUPPORT_METHODS = ["OPTIONS", "PROPFIND", "MKCOL", "GET", "HEAD", "PUT", "COPY", "MOVE", "DELETE"]; - - response.headers.set("Access-Control-Allow-Origin", request.headers.get("Origin") ?? "*"); - response.headers.set("Access-Control-Allow-Methods", SUPPORT_METHODS.join(", ")); - response.headers.set( - "Access-Control-Allow-Headers", - ["authorization", "content-type", "depth", "overwrite", "destination", "range"].join(", ") - ); - response.headers.set( - "Access-Control-Expose-Headers", - ["content-type", "content-length", "dav", "etag", "last-modified", "location", "date", "content-range"].join(", ") - ); - response.headers.set("Access-Control-Allow-Credentials", "false"); - response.headers.set("Access-Control-Max-Age", "86400"); -} - -文件名:src/utils/logger.ts -export const logger = { - info: (message: string, ...args: any[]) => console.log(`[INFO] ${message}`, ...args), - error: (message: string, ...args: any[]) => console.error(`[ERROR] ${message}`, ...args), - warn: (message: string, ...args: any[]) => console.warn(`[WARN] ${message}`, ...args), -}; - -文件名:src/utils/webdavUtils.ts -export async function* listAll(bucket: R2Bucket, prefix: string, isRecursive = false) { - let cursor: string | undefined = undefined; - do { - const r2_objects = await bucket.list({ - prefix, - delimiter: isRecursive ? undefined : "/", - cursor, - include: ["httpMetadata", "customMetadata"] - }); - for (const object of r2_objects.objects) { - yield object; - } - cursor = r2_objects.truncated ? r2_objects.cursor : undefined; - } while (cursor); -} - -export function fromR2Object(object: R2Object | null) { - if (!object) { - return { - creationdate: new Date().toUTCString(), - displayname: undefined, - getcontentlanguage: undefined, - getcontentlength: "0", - getcontenttype: undefined, - getetag: undefined, - getlastmodified: new Date().toUTCString(), - resourcetype: "" - }; - } - return { - creationdate: object.uploaded.toUTCString(), - displayname: object.httpMetadata?.contentDisposition, - getcontentlanguage: object.httpMetadata?.contentLanguage, - getcontentlength: object.size.toString(), - getcontenttype: object.httpMetadata?.contentType, - getetag: object.etag, - getlastmodified: object.uploaded.toUTCString(), - resourcetype: object.customMetadata?.resourcetype ?? "" - }; -} - -export function make_resource_path(request: Request) { - let path = new URL(request.url).pathname.slice(1); - return path.endsWith("/") ? path.slice(0, -1) : path; -} - diff --git a/src/handlers/webdavHandler.ts b/src/handlers/webdavHandler.ts index 4787804..bd99f8e 100644 --- a/src/handlers/webdavHandler.ts +++ b/src/handlers/webdavHandler.ts @@ -1,6 +1,5 @@ import { listAll, fromR2Object, make_resource_path, generatePropfindResponse } from '../utils/webdavUtils'; import { logger } from '../utils/logger'; -import { generateHTML, generateErrorHTML } from '../utils/templates'; import { WebDAVProps } from '../types'; const SUPPORT_METHODS = ["OPTIONS", "PROPFIND", "MKCOL", "GET", "HEAD", "PUT", "COPY", "MOVE", "DELETE"]; @@ -14,7 +13,7 @@ export async function handleWebDAV(request: Request, bucket: R2Bucket, bucketNam case "HEAD": return await handleHead(request, bucket); case "GET": - return await handleGet(request, bucket, bucketName); + return await handleGet(request, bucket); case "PUT": return await handlePut(request, bucket); case "DELETE": @@ -37,11 +36,7 @@ export async function handleWebDAV(request: Request, bucket: R2Bucket, bucketNam }); } } catch (error) { - logger.error("Error in WebDAV handling:", error); - return new Response(generateErrorHTML("Internal Server Error", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); + logger.error("Error in WebDAV handling:",return new Response("Internal Server Error", { status: 500 }); } } @@ -53,10 +48,7 @@ function handleOptions(): Response { DAV: DAV_CLASS } }); -} - -async function handleHead(request: Request, bucket: R2Bucket): Promise { - const resource_path = make_resource_path(request); +} function handleHead(request: Request, bucket: R2Bucket): Promise { resource_path = make_resource_path(request); const object = await bucket.head(resource_path); if (!object) { @@ -74,70 +66,23 @@ async function handleHead(request: Request, bucket: R2Bucket): Promise }); } -async function handleGet(request: Request, bucket: R2Bucket, bucketName: string): Promise { +async function handleGet(request: Request, bucket: R2Bucket): Promise { const resource_path = make_resource_path(request); + const object = await bucket.get(resource_path); - if (request.url.endsWith("/")) { - // 处理目录 - return await handleDirectory(bucket, resource_path, bucketName); - } else { - // 处理文件 - return await handleFile(bucket, resource_path); - } -} - -async function handleDirectory(bucket: R2Bucket, resource_path: string, bucketName: string): Promise { - let items = []; - - if (resource_path !== "") { - items.push({ name: "📁 ..", href: "../" }); - } - - try { - for await (const object of listAll(bucket, resource_path)) { - if (object.key === resource_path) continue; - const isDirectory = object.customMetadata?.resourcetype === ""; - const displayName = object.key.split('/').pop() || object.key; - const href = `/${object.key + (isDirectory ? "/" : "")}`; - items.push({ name: `${isDirectory ? '📁 ' : '📄 '}${displayName}`, href }); - } - } catch (error) { - logger.error("Error listing objects:", error); - return new Response(generateErrorHTML("Error listing directory contents", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); + if (!object) { + return new Response("Not Found", { status: 404 }); } - const page = generateHTML("WebDAV File Browser", items); - return new Response(page, { + return new Response(object.body, { status: 200, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); -} - -async function handleFile(bucket: R2Bucket, resource_path: string): Promise { - try { - const object = await bucket.get(resource_path); - if (!object) { - return new Response("Not Found", { status: 404 }); + headers: { + "Content-Type": object.httpMetadata?.contentType ?? "application/oc + "Content-Length": object.size.toString(), + "ETag": object.etag, + "Last-Modified": object.uploaded.toUTCString() } - return new Response(object.body, { - status: 200, - headers: { - "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream", - "Content-Length": object.size.toString(), - "ETag": object.etag, - "Last-Modified": object.uploaded.toUTCString() - } - }); - } catch (error) { - logger.error("Error getting object:", error); - return new Response(generateErrorHTML("Error retrieving file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); - } + }); } async function handlePut(request: Request, bucket: R2Bucket): Promise { @@ -152,11 +97,8 @@ async function handlePut(request: Request, bucket: R2Bucket): Promise }); return new Response("Created", { status: 201 }); } catch (error) { - logger.error("Error uploading file:", error); - return new Response(generateErrorHTML("Error uploading file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); + logger.error("Error in PUT:", error); + return new Response("Internal Server Error", { status: 500 }); } } @@ -167,11 +109,8 @@ async function handleDelete(request: Request, bucket: R2Bucket): Promise return new Response("Created", { status: 201 }); } catch (error) { logger.error("Error in COPY:", error); - return new Response(generateErrorHTML("Error copying file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); + return new Response("Internal Server Error", { status: 500 }); } } @@ -284,9 +214,6 @@ async function handleMove(request: Request, bucket: R2Bucket): Promise return new Response("No Content", { status: 204 }); } catch (error) { logger.error("Error in MOVE:", error); - return new Response(generateErrorHTML("Error moving file", error.message), { - status: 500, - headers: { "Content-Type": "text/html; charset=utf-8" } - }); + return new Response("Internal Server Error", { status: 500 }); } -} +} \ No newline at end of file diff --git a/src/utils/webdavUtils.ts b/src/utils/webdavUtils.ts index 0cb2982..97dee40 100644 --- a/src/utils/webdavUtils.ts +++ b/src/utils/webdavUtils.ts @@ -12,32 +12,25 @@ export async function* listAll(bucket: R2Bucket, prefix: string, isRecursive = f for (const object of r2_objects.objects) { yield object; } + for (const prefix of r2_objects.delimitedPrefixes) { + yield { key: prefix, customMetadata: { resourcetype: 'collection' } as R2Object; + } cursor = r2_objects.truncated ? r2_objects.cursor : undefined; } while (cursor); } -export function fromR2Object(object: R2Object | null): WebDAVProps { - if (!object) { - return { - creationdate: new Date().toUTCString(), - displayname: undefined, - getcontentlanguage: undefined, - getcontentlength: "0", - getcontenttype: undefined, - getetag: undefined, - getlastmodified: new Date().toUTCString(), - resourcetype: "" - }; - } - return { - creationdate: object.uploaded.toUTCString(), - displayname: object.httpMetadata?.contentDisposition, - getcontentlanguage: object.httpMetadata?.contentLanguage, - getcontentlength: object.size.toString(), - getcontenttype: object.httpMetadata?.contentType, - getetag: object.etag, - getlastmodified: object.uploaded.toUTCString(), - resourcetype: object.customMetadata?.resourcetype ?? "" +export function fromR2Object(object: R2Object, bucketName: string): WebDAVProps { + const isCollection = object.key.endsWith('/') || object.customMetadata?.resourcetype === 'collection'; + const displayName = object.key.split('/').pop() || object.key; + return: `/${bucobject.key}${isCollection ? '/' : ''}`, + creationdate: object.uploaded?.toISOString() || new Date().toISOString(), + displayname: displayName, + getcontentlanguage: object.httpMetadata?.contentLanguage || '', + getcontentlength: isCollection ? object.size?.toString() || '0', + getcontenttype: isCollection ? 'httpd/unix-directory' : (object.httpMetadata?.contentType || 'application/octet-stream'), + getetag: object.etag || `"${new Date().getTime().toString(16)}"`, + getlastmodified: object.uploaded?.toUTCString() || new Date().toUTCString(), + resourcetype: isCollection ? 'collection' : '' }; } @@ -49,25 +42,49 @@ export function make_resource_path(request: Request): string { export function generatePropfindResponse(bucketName: string, basePath: string, props: WebDAVProps[]): string { const xml = ` -${props.map(prop => generatePropResponse(bucketName, basePath, prop)).join('\n')} +${props.map(prop => generatePropResponse(prop)).join('\n')} `; return xml; } -function generatePropResponse(bucketName: string, basePath: string, prop: WebDAVProps): string { - const resourcePath = `/${basePath}${prop.displayname ? '/' + prop.displayname : ''}`; +function generatePropResponse(prop: WebDAVProps): string { return ` - ${resourcePath} + ${prop.href} ${prop.creationdate} + ${prop.displayname} + ${prop.getcontentlanguage} ${prop.getcontentlength} - ${prop.getcontenttype || ''} - ${prop.getetag || ''} - ${prop.getlastmodified} - ${prop.resourcetype ? '' : ''} + ${prop.getcontenttype} + ${prop.getetag} + ${prop.getlastmodified}resourcetype>${prop.resourcetype === ? '' : ''} + + + + + + + + + + HTTP/1.1 200 OK `; } + +// Helper function to escape XML special characters +function escapeXml(unsafe: string): string { + return unsafe.replace(/[<>&'"]/g, function (c) { + switch (c) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + case '\'': return '''; + case '"': return '"'; + } + return c; + }); +} \ No newline at end of file