Refactor index.ts
This commit is contained in:
parent
316c90b62c
commit
23cfd81123
53
package-lock.json
generated
53
package-lock.json
generated
@ -7,12 +7,8 @@
|
||||
"": {
|
||||
"name": "r2-webdav",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230419.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"typescript": "^5.0.4",
|
||||
"wrangler": "^3.0.0"
|
||||
}
|
||||
@ -495,24 +491,6 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
@ -982,11 +960,6 @@
|
||||
"estree-walker": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||
},
|
||||
"node_modules/selfsigned": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz",
|
||||
@ -1088,12 +1061,6 @@
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/workerd": {
|
||||
"version": "1.20231016.0",
|
||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231016.0.tgz",
|
||||
@ -1166,26 +1133,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xxhash-wasm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz",
|
||||
|
@ -9,11 +9,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230419.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"typescript": "^5.0.4",
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
|
171
src/index.ts
171
src/index.ts
@ -29,13 +29,10 @@ export interface Env {
|
||||
PASSWORD: string;
|
||||
}
|
||||
|
||||
import * as xml2js from 'xml2js';
|
||||
|
||||
const DAV_CLASS = "1";
|
||||
const SUPPORT_METHODS = [
|
||||
"OPTIONS",
|
||||
"PROPFIND",
|
||||
"PROPPATCH",
|
||||
"MKCOL",
|
||||
"GET",
|
||||
"HEAD",
|
||||
@ -95,7 +92,7 @@ export default {
|
||||
|
||||
let response: Response;
|
||||
|
||||
let resource_path = new URL(request.url).pathname;
|
||||
let resource_path = new URL(request.url).pathname.slice(1);
|
||||
|
||||
switch (request.method) {
|
||||
case 'OPTIONS': {
|
||||
@ -113,9 +110,13 @@ export default {
|
||||
if (request.url.endsWith('/')) {
|
||||
let r2_objects = await bucket.list({
|
||||
prefix: resource_path,
|
||||
delimiter: '/',
|
||||
});
|
||||
let page = '';
|
||||
for (let object of r2_objects.objects) {
|
||||
for (let dirname of r2_objects.delimitedPrefixes) {
|
||||
page += `<a href="${dirname}">${dirname}</a><br>`;
|
||||
}
|
||||
for (let object of r2_objects.objects.filter(object => !object.key.endsWith('/'))) {
|
||||
page += `<a href="${object.key}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`;
|
||||
}
|
||||
response = new Response(page, { status: 200, headers: { 'Content-Type': 'text/html' } });
|
||||
@ -166,17 +167,20 @@ export default {
|
||||
response = new Response('Method Not Allowed', { status: 405 });
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the parent directory exists
|
||||
let dirpath = resource_path.split('/').slice(0, -1).join('/') + '/';
|
||||
if (await bucket.head(dirpath)) {
|
||||
let dir = await bucket.head(dirpath);
|
||||
if (!dir || dir.customMetadata?.resourcetype !== '<collection />') {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
}
|
||||
|
||||
let body = await request.arrayBuffer();
|
||||
await bucket.put(resource_path, body, {
|
||||
onlyIf: request.headers,
|
||||
httpMetadata: request.headers,
|
||||
});
|
||||
response = new Response('', { status: 201 });
|
||||
} else {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'DELETE': {
|
||||
@ -196,28 +200,55 @@ export default {
|
||||
case 'MKCOL': {
|
||||
if (request.body) {
|
||||
response = new Response('Unsupported Media Type', { status: 415 });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
resource_path = resource_path.endsWith('/') ? resource_path : resource_path + '/';
|
||||
|
||||
// Check if the resource already exists
|
||||
if (await bucket.head(resource_path)) {
|
||||
response = new Response('Method Not Allowed', { status: 405 });
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the parent directory exists
|
||||
let parent_dir = resource_path.split('/').slice(0, -2).join("/") + '/';
|
||||
|
||||
if (!resource_path.endsWith('/')) {
|
||||
response = new Response('Forbidden', { status: 403 });
|
||||
} else if (await bucket.head(resource_path)) {
|
||||
response = new Response('Method Not Allowed', { status: 405 });
|
||||
} else if (parent_dir !== '/' && !await bucket.head(parent_dir)) {
|
||||
if (parent_dir !== '/' && !await bucket.head(parent_dir)) {
|
||||
response = new Response('Conflict', { status: 409 });
|
||||
} else {
|
||||
await bucket.put(resource_path, new Uint8Array(), {
|
||||
httpMetadata: request.headers,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
await bucket.put(resource_path, new Uint8Array(), { httpMetadata: request.headers });
|
||||
response = new Response('', { status: 201 });
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'PROPFIND': {
|
||||
let depth = request.headers.get('Depth') ?? 'infinity';
|
||||
switch (depth) {
|
||||
case '0': {
|
||||
if (resource_path === "") {
|
||||
response = new Response(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<multistatus xmlns="DAV:">
|
||||
<response>
|
||||
<href>${resource_path}</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
<resourcetype><collection /></resourcetype>
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>
|
||||
</multistatus>
|
||||
`, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
let object = await bucket.get(resource_path);
|
||||
if (object === null && !resource_path.endsWith('/')) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
@ -237,31 +268,43 @@ export default {
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>
|
||||
</multistatus>`;
|
||||
</multistatus>
|
||||
`;
|
||||
response = new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(await request.text(), page);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '1': {
|
||||
let r2_objects = await bucket.list({
|
||||
prefix: resource_path,
|
||||
});
|
||||
let page = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<multistatus xmlns="DAV:">`;
|
||||
for (let object of r2_objects.objects.filter(
|
||||
object => {
|
||||
let path = object.key.slice(resource_path.length);
|
||||
return !path.includes('/') ||
|
||||
(path.split('/').length === 2 && path.endsWith('/'));
|
||||
|
||||
let cursor: string | undefined = undefined;
|
||||
do {
|
||||
var r2_objects = await bucket.list({
|
||||
prefix: resource_path,
|
||||
delimiter: '/',
|
||||
cursor: cursor,
|
||||
});
|
||||
|
||||
for (let dirname of r2_objects.delimitedPrefixes) {
|
||||
page += `
|
||||
<response>
|
||||
<href>${dirname}</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
<resourcetype><collection /></resourcetype>
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>`;
|
||||
}
|
||||
)) {
|
||||
|
||||
for (let object of r2_objects.objects.filter(object => !object.key.endsWith('/'))) {
|
||||
page += `
|
||||
<response>
|
||||
<href>${object.key}</href>
|
||||
@ -277,48 +320,18 @@ export default {
|
||||
</propstat>
|
||||
</response>`;
|
||||
}
|
||||
page += '</multistatus>';
|
||||
|
||||
if (r2_objects.truncated) {
|
||||
cursor = r2_objects.cursor;
|
||||
}
|
||||
} while (r2_objects.truncated)
|
||||
page += '\n</multistatus>\n';
|
||||
response = new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(await request.text(), page);
|
||||
}
|
||||
break;
|
||||
case 'infinity': {
|
||||
let r2_objects = await bucket.list({
|
||||
prefix: resource_path,
|
||||
});
|
||||
let page = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<multistatus xmlns="DAV:">`;
|
||||
for (let object of r2_objects.objects) {
|
||||
page += `
|
||||
<response>
|
||||
<href>${object.key}</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
${Object.entries(fromR2Object(object))
|
||||
.filter(([_, value]) => value !== undefined)
|
||||
.map(([key, value]) => `<${key}>${value}</${key}>`)
|
||||
.join('\n')
|
||||
}
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>`;
|
||||
}
|
||||
page += '</multistatus>';
|
||||
response = new Response(page, {
|
||||
status: 207,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(await request.text(), page);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
@ -460,26 +473,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'PROPPATCH': {
|
||||
let request_xml = await xml2js.parseStringPromise(await request.text());
|
||||
let setCustomMetadata = request_xml["D:propertyupdate"]["D:set"]["D:prop"];
|
||||
let removeCustomMetadataKeys = Object.keys(request_xml["D:propertyupdate"]["D:remove"]["D:prop"]);
|
||||
let object = await bucket.get(resource_path);
|
||||
if (object === null) {
|
||||
response = new Response('Not Found', { status: 404 });
|
||||
} else {
|
||||
await bucket.put(resource_path, object.body, {
|
||||
httpMetadata: object.httpMetadata,
|
||||
customMetadata: {
|
||||
...Object.fromEntries(Object.entries(object.customMetadata ?? {}).filter(
|
||||
([name, _]) => !removeCustomMetadataKeys.includes(name)
|
||||
)),
|
||||
...setCustomMetadata,
|
||||
},
|
||||
});
|
||||
response = new Response('', { status: 200 });
|
||||
}
|
||||
}
|
||||
default: {
|
||||
response = new Response('Method Not Allowed', {
|
||||
status: 405,
|
||||
|
Loading…
Reference in New Issue
Block a user