Skip to content

Commit

Permalink
修正
Browse files Browse the repository at this point in the history
  • Loading branch information
aigem committed Sep 12, 2024
1 parent f4e2779 commit 191ae7b
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 57 deletions.
4 changes: 2 additions & 2 deletions output.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ 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",
"src/handlers/requestHandler.ts",
# "src/handlers/requestHandler.ts",
"src/handlers/webdavHandler.ts",
# "src/utils/auth.ts",
# "src/utils/cors.ts",
Expand Down
373 changes: 373 additions & 0 deletions output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
文件名:src/handlers/webdavHandler.ts
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"];
const DAV_CLASS = "1, 2";

export async function handleWebDAV(request: Request, bucket: R2Bucket, bucketName: string): Promise<Response> {
try {
switch (request.method) {
case "OPTIONS":
return handleOptions();
case "HEAD":
return await handleHead(request, bucket);
case "GET":
return await handleGet(request, bucket, bucketName);
case "PUT":
return await handlePut(request, bucket);
case "DELETE":
return await handleDelete(request, bucket);
case "MKCOL":
return await handleMkcol(request, bucket);
case "PROPFIND":
return await handlePropfind(request, bucket, bucketName);
case "COPY":
return await handleCopy(request, bucket);
case "MOVE":
return await handleMove(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" }
});
}
}

function handleOptions(): Response {
return new Response(null, {
status: 200,
headers: {
Allow: SUPPORT_METHODS.join(", "),
DAV: DAV_CLASS
}
});
}

async function handleHead(request: Request, bucket: R2Bucket): Promise<Response> {
const resource_path = make_resource_path(request);
const object = await bucket.head(resource_path);

if (!object) {
return new Response(null, { status: 404 });
}

return new Response(null, {
status: 200,
headers: {
"Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
"Content-Length": object.size.toString(),
"ETag": object.etag,
"Last-Modified": object.uploaded.toUTCString()
}
});
}

async function handleGet(request: Request, bucket: R2Bucket, bucketName: string): Promise<Response> {
const resource_path = make_resource_path(request);

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<Response> {
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" }
});
}

async function handleFile(bucket: R2Bucket, resource_path: string): Promise<Response> {
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(),
"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<Response> {
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" }
});
}
}

async function handleDelete(request: Request, bucket: R2Bucket): Promise<Response> {
const resource_path = make_resource_path(request);

try {
await bucket.delete(resource_path);
return new Response("No Content", { status: 204 });
} catch (error) {
logger.error("Error deleting object:", error);
return new Response(generateErrorHTML("Error deleting file", error.message), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" }
});
}
}

async function handleMkcol(request: Request, bucket: R2Bucket): Promise<Response> {
const resource_path = make_resource_path(request);

if (resource_path === "") {
return new Response("Method Not Allowed", { status: 405 });
}

try {
await bucket.put(resource_path + "/.keep", new Uint8Array(), {
customMetadata: { resourcetype: "" }
});
return new Response("Created", { status: 201 });
} catch (error) {
logger.error("Error creating collection:", error);
return new Response(generateErrorHTML("Error creating collection", error.message), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" }
});
}
}

async function handlePropfind(request: Request, bucket: R2Bucket, bucketName: string): Promise<Response> {
const resource_path = make_resource_path(request);
const depth = request.headers.get("Depth") || "infinity";

try {
const props: WebDAVProps[] = [];
if (depth !== "0") {
for await (const object of listAll(bucket, resource_path)) {
props.push(fromR2Object(object));
}
} else {
const object = await bucket.head(resource_path);
if (object) {
props.push(fromR2Object(object));
} else {
return new Response("Not Found", { status: 404 });
}
}

const xml = generatePropfindResponse(bucketName, resource_path, props);
logger.info("Generated XML for PROPFIND:", xml); // Add this line for logging
return new Response(xml, {
status: 207,
headers: { "Content-Type": "application/xml; charset=utf-8" }
});
} catch (error) {
logger.error("Error in PROPFIND:", error);
return new Response(generateErrorHTML("Error in PROPFIND", error.message), {
status: 500,
headers: { "Content-Type": "application/xml; charset=utf-8" }
});
}
}


async function handleCopy(request: Request, bucket: R2Bucket): Promise<Response> {
const sourcePath = make_resource_path(request);
const destinationHeader = request.headers.get("Destination");
if (!destinationHeader) {
return new Response("Bad Request: Destination header is missing", { status: 400 });
}
const destinationUrl = new URL(destinationHeader);
const destinationPath = make_resource_path(new Request(destinationUrl));

try {
const sourceObject = await bucket.get(sourcePath);
if (!sourceObject) {
return new Response("Not Found", { status: 404 });
}

await bucket.put(destinationPath, sourceObject.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});

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" }
});
}
}

async function handleMove(request: Request, bucket: R2Bucket): Promise<Response> {
const sourcePath = make_resource_path(request);
const destinationHeader = request.headers.get("Destination");
if (!destinationHeader) {
return new Response("Bad Request: Destination header is missing", { status: 400 });
}
const destinationUrl = new URL(destinationHeader);
const destinationPath = make_resource_path(new Request(destinationUrl));

try {
const sourceObject = await bucket.get(sourcePath);
if (!sourceObject) {
return new Response("Not Found", { status: 404 });
}

await bucket.put(destinationPath, sourceObject.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});

await bucket.delete(sourcePath);

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" }
});
}
}


文件名:src/utils/webdavUtils.ts
import { WebDAVProps } from '../types';

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): 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 make_resource_path(request: Request): string {
let path = new URL(request.url).pathname.slice(1);
return path.endsWith("/") ? path.slice(0, -1) : path;
}

export function generatePropfindResponse(bucketName: string, basePath: string, props: WebDAVProps[]): string {
const xml = `<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
${props.map(prop => generatePropResponse(bucketName, basePath, prop)).join('\n')}
</D:multistatus>`;
return xml;
}

function generatePropResponse(bucketName: string, basePath: string, prop: WebDAVProps): string {
const resourcePath = `/${basePath}${prop.displayname ? '/' + prop.displayname : ''}`;
return ` <D:response>
<D:href>${resourcePath}</D:href>
<D:propstat>
<D:prop>
<D:creationdate>${prop.creationdate}</D:creationdate>
<D:getcontentlength>${prop.getcontentlength}</D:getcontentlength>
<D:getcontenttype>${prop.getcontenttype || ''}</D:getcontenttype>
<D:getetag>${prop.getetag || ''}</D:getetag>
<D:getlastmodified>${prop.getlastmodified}</D:getlastmodified>
<D:resourcetype>${prop.resourcetype ? '<D:collection/>' : ''}</D:resourcetype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>`;
}


Loading

0 comments on commit 191ae7b

Please sign in to comment.