Skip to content

Commit

Permalink
back
Browse files Browse the repository at this point in the history
  • Loading branch information
aigem committed Sep 12, 2024
1 parent 2c03f37 commit 82bf923
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 204 deletions.
281 changes: 125 additions & 156 deletions src/handlers/webdavHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { listAll, fromR2Object, make_resource_path, generatePropfindResponse, generateMultiStatus } from '../utils/webdavUtils';
import { listAll, fromR2Object, make_resource_path, generatePropfindResponse } from '../utils/webdavUtils';
import { logger } from '../utils/logger';
{ WebDAVProps } from '../types';
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";
Expand Down Expand Up @@ -37,11 +38,15 @@ export async function handleWebDAV(request: Request, bucket: R2Bucket, bucketNam
}
} catch (error) {
logger.error("Error in WebDAV handling:", error);
return new Response("Internal Server Error", { status: 500 });
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, {
function handleOptions(): Response {
return new Response(null, {
status: 200,
headers: {
Allow: SUPPORT_METHODS.join(", "),
Expand All @@ -58,49 +63,83 @@ async function handleHead(request: Request, bucket: R2Bucket): Promise<Response>
return new Response(null, { status: 404 });
}

const headers = new Headers({
"Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
"Content-Length": object.size.toString(),
"ETag": object.etag,
"Last-Modified": object.uploaded.toUTCString()
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()
}
});

if (object.customMetadata?.resourcetype === "collection") {
headers.set("Content-Type", "httpd/unix-directory");
}

return new Response(null, { status: 200, headers });
}

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

const object = await bucket.get(resource_path);
if (!object) {
return new Response("Not Found", { status: });
if (request.url.endsWith("/")) {
// 处理目录
return await handleDirectory(bucket, resource_path, bucketName);
} else {
// 处理文件
return await handleFile(bucket, resource_path);
}
}

if (object.customMetadata?.resourcetype === "collection") {
// Return a directory listing
const items = await listDirectoryContents(bucket, resource_path);
const html = generateDirectoryListing(bucketName, resource_path, items);
return new Response(html, {
status: 200,
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" }
});
}

return new Response(object.body, {
const page = generateHTML("WebDAV File Browser", items);
return new Response(page, {
status: 200,
headers: {
"Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
"Content-Length": object.size.toString(),
"ETag": object.etag,
"Last-Modified": object.uploaded.toUTCString()
}
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);

Expand All @@ -111,78 +150,68 @@ async function handlePut(request: Request, bucket: R2Bucket): Promise<Response>
contentType: request.headers.get("Content-Type") || "application/octet-stream",
},
});
return new Response(null, { status: 201 });
return new Response("Created", { status: 201 });
} catch (error) {
logger.error("Error uploading file:", error);
return new Response("Internal Server Error", { status: 500 });
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);
const resource_path = make_resource_path(request);

try {
const object = await bucket.head(resource_path);
if (!object) {
return new Response("Not Found", { status: 404 });
}

if (object.customMetadata?.resourcetype === "collection") {
// Delete all objects within the directory
for await (const item of listAll(bucket, resource_path, true)) await bucket.delete(item.key);
}
}

bucket.delete(resource_path);
return new Response(null, { status: 204 });
await bucket.delete(resource_path);
return new Response("No Content", { status: 204 });
} catch (error) {
logger.error("Error deleting object:", error);
return new Response("Internal Server Error", { status: 500 });
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 });
return new Response("Method Not Allowed", { status: 405 });
}

try {
const existingObject = await bucket.head(resource_path);
if (existingObject) {
return new Response("Method Not Allowed", { status: 405 });
}

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

async function handlefind(request: Request, bucket: R2Bucket, bucketName: string): Promise<Response> {
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[] = [];
const object = await bucket.head(resource_path);

if (!object) {
return new Response("Not Found", { status: 404 });
}

props.push(fromR2Object(object));

if (depth !== "0" && object.customMetadata?.resourcetype === "collection") {
for await (const item of listAll(bucket, resource_path {
if (item.key !== resource_path) {
props.push(fromR2Object(item));
}
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 });
}
}

Expand All @@ -193,7 +222,10 @@ async function handlefind(request: Request, bucket: R2Bucket, bucketName: string
});
} catch (error) {
logger.error("Error in PROPFIND:", error);
return new Response("Internal Server Error", { status: 500 });
return new Response(generateErrorHTML("Error in PROPFIND", error.message), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" }
});
}
}

Expand All @@ -212,27 +244,18 @@ async function handleCopy(request: Request, bucket: R2Bucket): Promise<Response>
return new Response("Not Found", { status: 404 });
}

if (sourceObject.customMetadata?.resourcetype === "collection") {
// Copy directory
for await (const item of listAll(bucket, sourcePath, true)) {
const newPath = item.key.replace(sourcePath, destinationPath);
await bucket.put(newPath, await (await bucket.get(item.key))!.arrayBuffer(), {
httpMetadata: item.httpMetadata,
customMetadata: item.customMetadata
});
}
} else {
// Copy file
await bucket.put(destinationPath,Object.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});
}
await bucket.put(destinationPath, sourceObject.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});

return new Response(null, { status: 201 });
return new Response("Created", { status: 201 });
} catch (error) {
logger.error("Error in COPY:", error);
return new Response("Internal Server Error", { status: 500 });
return new Response(generateErrorHTML("Error copying file", error.message), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" }
});
}
}

Expand All @@ -251,73 +274,19 @@ async function handleMove(request: Request, bucket: R2Bucket): Promise<Response>
return new Response("Not Found", { status: 404 });
}

if (sourceObject.customMetadata?.resourcetype === "collection") {
// Move directory
for await (const item of listAll(bucket, sourcePath, true)) {
const newPath = item.key.replace(sourcePath,Path);
await bucket.put(newPath, await (await bucket.get(item.key))!.arrayBuffer(), {
httpMetadata: item.httpMetadata,
customMetadata: item.customMetadata
});
await bucket.delete(item.key);
}
} {
// Move file
await bucket.put(destinationPath, sourceObject.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});
await bucket.delete(sourcePath);
}
await bucket.put(destinationPath, sourceObject.body, {
httpMetadata: sourceObject.httpMetadata,
customMetadata: sourceObject.customMetadata
});

await bucket.delete(sourcePath);

return new Response(null, { status: 204 });
return new Response("No Content", { status: 204 });
} catch (error) {
logger.error("Error in MOVE:", error);
return new Response("Internal Server Error", { status: 500 });
}
}

async function listDirectoryContents(bucket: R2Bucket, prefix: string): Promise<R2Object[]> {
const items: R2Object[] = [];
for await (const item of listAll(bucket, prefix)) {
if (item.key !== prefix) {
items.push(item);
}
return new Response(generateErrorHTML("Error moving file", error.message), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" }
});
}
return items;
}

function generateDirectoryListing(bucketName: string, path: string, items: R2Object[]): string {
const listItems = items.map(item => {
const isDirectory = item.customMetadata?.resourcetype === "collection";
const name = item.key.split('/').pop() || item.key;
const href = `/${bucketName}/${item.key}${isDirectory ? '/' : ''}`;
return `<li><a href="${href}">${isDirectory ? '📁 ' : '📄 '}${name}</a></li>`;
}).join('\n');

return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Directory Listing for /${bucketName}/${path}</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; padding: 20px; }
h1 { color: #333; }
ul { list-style-type: none; padding: 0; }
li { margin-bottom: 10px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>Directory Listing for /${bucketName}/${path}</h1>
<ul>
${path !== '' ? '<li><a href="../">📁 ..</a></li>' : ''}
${listItems}
</ul>
</body>
</html>
`;
}
Loading

0 comments on commit 82bf923

Please sign in to comment.