Refactor code to improve performance and readability
This commit is contained in:
parent
0e6f45e368
commit
4295fddbdb
234
src/index.ts
234
src/index.ts
@ -78,25 +78,13 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
const { bucket } = env;
|
||||
|
||||
if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) {
|
||||
return new Response('Unauthorized', {
|
||||
status: 401, headers: {
|
||||
'WWW-Authenticate': 'Basic realm="webdav"',
|
||||
}
|
||||
});
|
||||
function make_resource_path(request: Request): string {
|
||||
return new URL(request.url).pathname.slice(1)
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
|
||||
let resource_path = new URL(request.url).pathname.slice(1);
|
||||
|
||||
switch (request.method) {
|
||||
case 'OPTIONS': {
|
||||
response = new Response(null, {
|
||||
async function handle_options(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'DAV': DAV_CLASS,
|
||||
@ -104,9 +92,19 @@ export default {
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'HEAD':
|
||||
case 'GET': {
|
||||
|
||||
async function handle_head(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let response = await handle_get(request, bucket);
|
||||
return new Response(null, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
});
|
||||
}
|
||||
|
||||
async function handle_get(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
|
||||
if (request.url.endsWith('/')) {
|
||||
let r2_objects = await bucket.list({
|
||||
prefix: resource_path,
|
||||
@ -119,7 +117,7 @@ export default {
|
||||
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
|
||||
page += `<a href="${href}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`;
|
||||
}
|
||||
response = new Response(page, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
||||
return new Response(page, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
||||
} else {
|
||||
let object = await bucket.get(resource_path, {
|
||||
onlyIf: request.headers,
|
||||
@ -131,11 +129,11 @@ export default {
|
||||
}
|
||||
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
return new Response('Not Found', { status: 404 });
|
||||
} else if (!isR2ObjectBody(object)) {
|
||||
response = new Response("Precondition Failed", { status: 412 });
|
||||
return new Response("Precondition Failed", { status: 412 });
|
||||
} else {
|
||||
response = new Response(object.body, {
|
||||
return new Response(object.body, {
|
||||
status: object.range ? 206 : 200,
|
||||
headers: {
|
||||
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
|
||||
@ -161,11 +159,12 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'PUT': {
|
||||
|
||||
async function handle_put(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
|
||||
if (resource_path.endsWith('/')) {
|
||||
response = new Response('Method Not Allowed', { status: 405 });
|
||||
break;
|
||||
return new Response('Method Not Allowed', { status: 405 });
|
||||
}
|
||||
|
||||
// Check if the parent directory exists
|
||||
@ -173,8 +172,7 @@ export default {
|
||||
if (dirpath !== '') {
|
||||
let dir = await bucket.head(dirpath);
|
||||
if (!(dir && dir.customMetadata?.resourcetype === '<collection />')) {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
break;
|
||||
return new Response('Conflict', { status: 409 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,28 +181,27 @@ export default {
|
||||
onlyIf: request.headers,
|
||||
httpMetadata: request.headers,
|
||||
});
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
break;
|
||||
case 'DELETE': {
|
||||
|
||||
async function handle_delete(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
|
||||
if (!resource_path.endsWith('/')) {
|
||||
let resource = await bucket.head(resource_path);
|
||||
if (resource === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
} else {
|
||||
if (resource.customMetadata?.resourcetype !== '<collection />') {
|
||||
await bucket.delete(resource_path);
|
||||
response = new Response(null, { status: 204 });
|
||||
break;
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dirpath = resource_path.slice(0, -1);
|
||||
if (await bucket.head(dirpath) === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
await bucket.delete(dirpath);
|
||||
@ -225,44 +222,44 @@ export default {
|
||||
}
|
||||
} while (r2_objects.truncated);
|
||||
|
||||
response = new Response(null, { status: 204 });
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
break;
|
||||
case 'MKCOL': {
|
||||
|
||||
async function handle_mkcol(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
|
||||
if (request.body) {
|
||||
response = new Response('Unsupported Media Type', { status: 415 });
|
||||
break;
|
||||
return new Response('Unsupported Media Type', { status: 415 });
|
||||
}
|
||||
|
||||
resource_path = resource_path.endsWith('/') ? resource_path.slice(0, -1) : resource_path;
|
||||
|
||||
// Check if the resource already exists
|
||||
if (await bucket.head(resource_path)) {
|
||||
response = new Response('Method Not Allowed', { status: 405 });
|
||||
break;
|
||||
return new Response('Method Not Allowed', { status: 405 });
|
||||
}
|
||||
|
||||
// Check if the parent directory exists
|
||||
let parent_dir = resource_path.split('/').slice(0, -1).join("/");
|
||||
|
||||
if (parent_dir !== '' && !await bucket.head(parent_dir)) {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
break;
|
||||
return new Response('Conflict', { status: 409 });
|
||||
}
|
||||
|
||||
await bucket.put(resource_path, new Uint8Array(), {
|
||||
httpMetadata: request.headers,
|
||||
customMetadata: { resourcetype: '<collection />' }
|
||||
});
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
break;
|
||||
case 'PROPFIND': {
|
||||
|
||||
async function handle_propfind(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
let depth = request.headers.get('Depth') ?? 'infinity';
|
||||
switch (depth) {
|
||||
case '0': {
|
||||
if (resource_path === "") {
|
||||
response = new Response(`<?xml version="1.0" encoding="utf-8"?>
|
||||
return new Response(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<multistatus xmlns="DAV:">
|
||||
<response>
|
||||
<href>/</href>
|
||||
@ -280,7 +277,6 @@ export default {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
let object = await bucket.head(resource_path);
|
||||
@ -289,8 +285,7 @@ export default {
|
||||
}
|
||||
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
let page = `<?xml version="1.0" encoding="utf-8"?>
|
||||
@ -310,14 +305,13 @@ export default {
|
||||
</response>
|
||||
</multistatus>
|
||||
`;
|
||||
response = new Response(page, {
|
||||
return new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
case '1': {
|
||||
if (resource_path !== "") {
|
||||
let object = await bucket.head(resource_path);
|
||||
@ -326,8 +320,7 @@ export default {
|
||||
}
|
||||
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
if (object.customMetadata?.resourcetype !== '<collection />') {
|
||||
@ -348,13 +341,12 @@ export default {
|
||||
</response>
|
||||
</multistatus>
|
||||
`;
|
||||
response = new Response(page, {
|
||||
return new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,14 +385,13 @@ export default {
|
||||
}
|
||||
} while (r2_objects.truncated)
|
||||
page += '\n</multistatus>\n';
|
||||
response = new Response(page, {
|
||||
return new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'infinity': {
|
||||
if (resource_path !== "") {
|
||||
let object = await bucket.head(resource_path);
|
||||
@ -409,8 +400,7 @@ export default {
|
||||
}
|
||||
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
if (object.customMetadata?.resourcetype !== '<collection />') {
|
||||
@ -431,13 +421,12 @@ export default {
|
||||
</response>
|
||||
</multistatus>
|
||||
`;
|
||||
response = new Response(page, {
|
||||
return new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,34 +464,32 @@ export default {
|
||||
}
|
||||
} while (r2_objects.truncated);
|
||||
page += '\n</multistatus>\n';
|
||||
response = new Response(page, {
|
||||
return new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
response = new Response('Forbidden', { status: 403 });
|
||||
return new Response('Forbidden', { status: 403 });
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'COPY': {
|
||||
|
||||
async function handle_copy(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
let dont_overwrite = request.headers.get('Overwrite') === 'F';
|
||||
let destination_header = request.headers.get('Destination');
|
||||
if (destination_header === null) {
|
||||
response = new Response('Bad Request', { status: 400 });
|
||||
break;
|
||||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
let destination = new URL(destination_header).pathname.slice(1);
|
||||
|
||||
// Check if the parent directory exists
|
||||
let destination_parent = destination.split('/').slice(0, destination.endsWith('/') ? -2 : -1).join('/');
|
||||
if (destination_parent !== '' && !await bucket.head(destination_parent)) {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
break;
|
||||
return new Response('Conflict', { status: 409 });
|
||||
}
|
||||
|
||||
if (resource_path.endsWith('/')) {
|
||||
@ -513,8 +500,7 @@ export default {
|
||||
prefix: resource_path,
|
||||
});
|
||||
if (r2_objects.objects.length === 0) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
await Promise.all(r2_objects.objects.map(
|
||||
object => (async () => {
|
||||
@ -528,62 +514,56 @@ export default {
|
||||
}
|
||||
})()
|
||||
));
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
break;
|
||||
case '0': {
|
||||
let object = await bucket.get(resource_path.slice(0, -1));
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
await bucket.put(destination, object.body, {
|
||||
httpMetadata: object.httpMetadata,
|
||||
customMetadata: object.customMetadata,
|
||||
});
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
response = new Response('Bad Request', { status: 400 });
|
||||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let destination_exists = await bucket.head(destination);
|
||||
if (dont_overwrite && destination_exists) {
|
||||
response = new Response('Precondition Failed', { status: 412 });
|
||||
break;
|
||||
return new Response('Precondition Failed', { status: 412 });
|
||||
}
|
||||
|
||||
let src = await bucket.get(resource_path);
|
||||
if (src === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
await bucket.put(destination, src.body, {
|
||||
httpMetadata: src.httpMetadata,
|
||||
customMetadata: src.customMetadata,
|
||||
});
|
||||
if (destination_exists) {
|
||||
response = new Response(null, { status: 204 });
|
||||
return new Response(null, { status: 204 });
|
||||
} else {
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'MOVE': {
|
||||
|
||||
async function handle_move(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
let resource_path = make_resource_path(request);
|
||||
let overwrite = request.headers.get('Overwrite') === 'T';
|
||||
let destination_header = request.headers.get('Destination');
|
||||
if (destination_header === null) {
|
||||
response = new Response('Bad Request', { status: 400 });
|
||||
break;
|
||||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
let destination = new URL(destination_header).pathname.slice(1);
|
||||
let destination_exists = await bucket.head(destination);
|
||||
if (destination_exists && !overwrite) {
|
||||
response = new Response('Precondition Failed', { status: 412 });
|
||||
break;
|
||||
return new Response('Precondition Failed', { status: 412 });
|
||||
}
|
||||
|
||||
// TODO delete recursively (if destination is a directory)
|
||||
@ -619,33 +599,60 @@ export default {
|
||||
cursor = r2_objects.cursor;
|
||||
}
|
||||
} while (r2_objects.truncated)
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
response = new Response('Bad Request', { status: 400 });
|
||||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let src = await bucket.get(resource_path);
|
||||
if (src === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
break;
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
await bucket.put(destination, src.body, {
|
||||
httpMetadata: src.httpMetadata,
|
||||
customMetadata: src.customMetadata,
|
||||
});
|
||||
if (destination_exists) {
|
||||
response = new Response(null, { status: 204 });
|
||||
return new Response(null, { status: 204 });
|
||||
} else {
|
||||
response = new Response('', { status: 201 });
|
||||
return new Response('', { status: 201 });
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
async function dispatch_handler(request: Request, bucket: R2Bucket): Promise<Response> {
|
||||
switch (request.method) {
|
||||
case 'OPTIONS': {
|
||||
return await handle_options(request, bucket);
|
||||
}
|
||||
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: {
|
||||
response = new Response('Method Not Allowed', {
|
||||
return new Response('Method Not Allowed', {
|
||||
status: 405,
|
||||
headers: {
|
||||
'Allow': SUPPORT_METHODS.join(', '),
|
||||
@ -654,15 +661,22 @@ export default {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.method === 'HEAD') {
|
||||
response = new Response(null, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
const { bucket } = env;
|
||||
|
||||
if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) {
|
||||
return new Response('Unauthorized', {
|
||||
status: 401, headers: {
|
||||
'WWW-Authenticate': 'Basic realm="webdav"',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let response: Response = await dispatch_handler(request, bucket);
|
||||
|
||||
// Set CORS headers
|
||||
response.headers.set('Access-Control-Allow-Origin', request.headers.get('Origin') ?? '*');
|
||||
response.headers.set('Access-Control-Allow-Methods', SUPPORT_METHODS.join(', '));
|
||||
|
Loading…
Reference in New Issue
Block a user