Fix resource type in PROPFIND response and support COPY and MOVE

This commit is contained in:
abersheeran 2023-11-28 23:53:49 +08:00
parent 6e0741321a
commit 6a3ccf86af

View File

@ -29,6 +29,8 @@ export interface Env {
PASSWORD: string; PASSWORD: string;
} }
import * as xml2js from 'xml2js';
const DAV_CLASS = "1"; const DAV_CLASS = "1";
const SUPPORT_METHODS = [ const SUPPORT_METHODS = [
"OPTIONS", "OPTIONS",
@ -50,6 +52,7 @@ type DavProperties = {
getcontenttype: string | undefined; getcontenttype: string | undefined;
getetag: string | undefined; getetag: string | undefined;
getlastmodified: string | undefined; getlastmodified: string | undefined;
resourcetype: string;
} }
function fromR2Object(object: R2Object | null | undefined): DavProperties { function fromR2Object(object: R2Object | null | undefined): DavProperties {
@ -62,6 +65,7 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
getcontenttype: undefined, getcontenttype: undefined,
getetag: undefined, getetag: undefined,
getlastmodified: undefined, getlastmodified: undefined,
resourcetype: '',
}; };
} }
@ -73,6 +77,7 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
getcontenttype: object.httpMetadata?.contentType, getcontenttype: object.httpMetadata?.contentType,
getetag: object.etag, getetag: object.etag,
getlastmodified: object.uploaded.toUTCString(), getlastmodified: object.uploaded.toUTCString(),
resourcetype: object.key.endsWith('/') ? '<collection />' : '',
}; };
} }
@ -101,8 +106,8 @@ export default {
'Allow': SUPPORT_METHODS.join(', '), 'Allow': SUPPORT_METHODS.join(', '),
} }
}); });
break;
} }
break;
case 'HEAD': case 'HEAD':
case 'GET': { case 'GET': {
if (request.url.endsWith('/')) { if (request.url.endsWith('/')) {
@ -154,21 +159,26 @@ export default {
}); });
} }
} }
break;
} }
break;
case 'PUT': { case 'PUT': {
if (resource_path.endsWith('/')) { if (resource_path.endsWith('/')) {
response = new Response('Forbidden', { status: 403 }); response = new Response('Method Not Allowed', { status: 405 });
break; break;
} }
let body = await request.arrayBuffer(); let dirpath = resource_path.split('/').slice(0, -1).join('/') + '/';
await bucket.put(resource_path, body, { if (await bucket.head(dirpath)) {
onlyIf: request.headers, let body = await request.arrayBuffer();
httpMetadata: request.headers, await bucket.put(resource_path, body, {
}); onlyIf: request.headers,
response = new Response('', { status: 201 }); httpMetadata: request.headers,
break; });
response = new Response('', { status: 201 });
} else {
response = new Response('Conflict', { status: 409 });
}
} }
break;
case 'DELETE': { case 'DELETE': {
if (resource_path.endsWith('/')) { if (resource_path.endsWith('/')) {
let r2_objects = await bucket.list({ let r2_objects = await bucket.list({
@ -181,8 +191,8 @@ export default {
await bucket.delete(resource_path); await bucket.delete(resource_path);
} }
response = new Response(null, { status: 204 }); response = new Response(null, { status: 204 });
break;
} }
break;
case 'MKCOL': { case 'MKCOL': {
if (request.body) { if (request.body) {
response = new Response('Unsupported Media Type', { status: 415 }); response = new Response('Unsupported Media Type', { status: 415 });
@ -202,8 +212,8 @@ export default {
response = new Response('', { status: 201 }); response = new Response('', { status: 201 });
} }
} }
break;
} }
break;
case 'PROPFIND': { case 'PROPFIND': {
let depth = request.headers.get('Depth') ?? 'infinity'; let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) { switch (depth) {
@ -237,8 +247,8 @@ export default {
console.log(await request.text(), page); console.log(await request.text(), page);
} }
break;
} }
break;
case '1': { case '1': {
let r2_objects = await bucket.list({ let r2_objects = await bucket.list({
prefix: resource_path, prefix: resource_path,
@ -276,9 +286,8 @@ export default {
}); });
console.log(await request.text(), page); console.log(await request.text(), page);
break;
} }
break;
case 'infinity': { case 'infinity': {
let r2_objects = await bucket.list({ let r2_objects = await bucket.list({
prefix: resource_path, prefix: resource_path,
@ -310,24 +319,149 @@ export default {
}); });
console.log(await request.text(), page); console.log(await request.text(), page);
break;
} }
break;
default: { default: {
response = new Response('Bad Request', { status: 400 }); response = new Response('Bad Request', { status: 400 });
}
}
}
break;
case 'COPY': {
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;
}
let destination = new URL(destination_header).pathname.slice(1);
let destination_exists = await bucket.head(destination);
if (dont_overwrite && destination_exists) {
response = new Response('Precondition Failed', { status: 412 });
break;
}
if (resource_path.endsWith('/')) {
let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) {
case 'infinity': {
let r2_objects = await bucket.list({
prefix: resource_path,
});
await Promise.all(r2_objects.objects.map(
object => (async () => {
let target = destination + object.key.slice(resource_path.length);
let src = await bucket.get(object.key);
if (src !== null) {
await bucket.put(target, src.body, {
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
});
}
})()
));
response = new Response('', { status: 201 });
}
break;
case '0': {
let object = await bucket.get(resource_path);
if (object === null) {
response = new Response('Not Found', { status: 404 });
break;
}
await bucket.put(destination, object.body, {
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
});
response = new Response('', { status: 201 });
}
break;
default: {
response = 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;
}
await bucket.put(destination, src.body, {
httpMetadata: src.httpMetadata,
customMetadata: src.customMetadata,
});
if (destination_exists) {
response = new Response(null, { status: 204 });
} else {
response = new Response('', { status: 201 });
}
}
}
break;
case 'MOVE': {
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;
}
let destination = new URL(destination_header).pathname.slice(1);
let destination_exists = await bucket.head(destination);
if (destination_exists) {
if (overwrite) {
await Promise.all((await bucket.list({
prefix: destination,
})).objects.map(object => bucket.delete(object.key)));
} else {
response = new Response('Precondition Failed', { status: 412 });
break; break;
} }
} }
break; if (resource_path.endsWith('/')) {
let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) {
case 'infinity': {
let r2_objects = await bucket.list({
prefix: resource_path,
});
await Promise.all(r2_objects.objects.map(
object => (async () => {
let target = destination + object.key.slice(resource_path.length);
let src = await bucket.get(object.key);
if (src !== null) {
await bucket.put(target, src.body, {
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
});
await bucket.delete(object.key);
}
})()
));
response = new Response('', { status: 201 });
}
break;
default: {
response = 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;
}
await bucket.put(destination, src.body, {
httpMetadata: src.httpMetadata,
customMetadata: src.customMetadata,
});
if (destination_exists) {
response = new Response(null, { status: 204 });
} else {
response = new Response('', { status: 201 });
}
}
} }
case 'PROPPATCH': { case 'PROPPATCH': {
}
case 'COPY': {
}
case 'MOVE': {
} }
default: { default: {
response = new Response('Method Not Allowed', { response = new Response('Method Not Allowed', {
@ -337,7 +471,6 @@ export default {
'DAV': DAV_CLASS, 'DAV': DAV_CLASS,
} }
}); });
break;
} }
} }