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",
|
"name": "r2-webdav",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20230419.0",
|
"@cloudflare/workers-types": "^4.20230419.0",
|
||||||
"@types/xml2js": "^0.4.14",
|
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
}
|
}
|
||||||
@ -495,24 +491,6 @@
|
|||||||
"node": ">=14"
|
"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": {
|
"node_modules/acorn": {
|
||||||
"version": "8.10.0",
|
"version": "8.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||||
@ -982,11 +960,6 @@
|
|||||||
"estree-walker": "^0.6.1"
|
"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": {
|
"node_modules/selfsigned": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz",
|
||||||
@ -1088,12 +1061,6 @@
|
|||||||
"node": ">=14.0"
|
"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": {
|
"node_modules/workerd": {
|
||||||
"version": "1.20231016.0",
|
"version": "1.20231016.0",
|
||||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231016.0.tgz",
|
"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": {
|
"node_modules/xxhash-wasm": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz",
|
||||||
|
@ -9,11 +9,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20230419.0",
|
"@cloudflare/workers-types": "^4.20230419.0",
|
||||||
"@types/xml2js": "^0.4.14",
|
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {}
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
173
src/index.ts
173
src/index.ts
@ -29,13 +29,10 @@ 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",
|
||||||
"PROPFIND",
|
"PROPFIND",
|
||||||
"PROPPATCH",
|
|
||||||
"MKCOL",
|
"MKCOL",
|
||||||
"GET",
|
"GET",
|
||||||
"HEAD",
|
"HEAD",
|
||||||
@ -95,7 +92,7 @@ export default {
|
|||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
|
|
||||||
let resource_path = new URL(request.url).pathname;
|
let resource_path = new URL(request.url).pathname.slice(1);
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'OPTIONS': {
|
case 'OPTIONS': {
|
||||||
@ -113,9 +110,13 @@ export default {
|
|||||||
if (request.url.endsWith('/')) {
|
if (request.url.endsWith('/')) {
|
||||||
let r2_objects = await bucket.list({
|
let r2_objects = await bucket.list({
|
||||||
prefix: resource_path,
|
prefix: resource_path,
|
||||||
|
delimiter: '/',
|
||||||
});
|
});
|
||||||
let page = '';
|
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>`;
|
page += `<a href="${object.key}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`;
|
||||||
}
|
}
|
||||||
response = new Response(page, { status: 200, headers: { 'Content-Type': 'text/html' } });
|
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 });
|
response = new Response('Method Not Allowed', { status: 405 });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the parent directory exists
|
||||||
let dirpath = resource_path.split('/').slice(0, -1).join('/') + '/';
|
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();
|
let body = await request.arrayBuffer();
|
||||||
await bucket.put(resource_path, body, {
|
await bucket.put(resource_path, body, {
|
||||||
onlyIf: request.headers,
|
onlyIf: request.headers,
|
||||||
httpMetadata: request.headers,
|
httpMetadata: request.headers,
|
||||||
});
|
});
|
||||||
response = new Response('', { status: 201 });
|
response = new Response('', { status: 201 });
|
||||||
} else {
|
|
||||||
response = new Response('Conflict', { status: 409 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'DELETE': {
|
case 'DELETE': {
|
||||||
@ -196,28 +200,55 @@ export default {
|
|||||||
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 });
|
||||||
} 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("/") + '/';
|
let parent_dir = resource_path.split('/').slice(0, -2).join("/") + '/';
|
||||||
|
|
||||||
if (!resource_path.endsWith('/')) {
|
if (parent_dir !== '/' && !await bucket.head(parent_dir)) {
|
||||||
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)) {
|
|
||||||
response = new Response('Conflict', { status: 409 });
|
response = new Response('Conflict', { status: 409 });
|
||||||
} else {
|
break;
|
||||||
await bucket.put(resource_path, new Uint8Array(), {
|
}
|
||||||
httpMetadata: request.headers,
|
|
||||||
});
|
await bucket.put(resource_path, new Uint8Array(), { httpMetadata: request.headers });
|
||||||
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) {
|
||||||
case '0': {
|
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);
|
let object = await bucket.get(resource_path);
|
||||||
if (object === null && !resource_path.endsWith('/')) {
|
if (object === null && !resource_path.endsWith('/')) {
|
||||||
response = new Response('Not Found', { status: 404 });
|
response = new Response('Not Found', { status: 404 });
|
||||||
@ -237,31 +268,43 @@ export default {
|
|||||||
<status>HTTP/1.1 200 OK</status>
|
<status>HTTP/1.1 200 OK</status>
|
||||||
</propstat>
|
</propstat>
|
||||||
</response>
|
</response>
|
||||||
</multistatus>`;
|
</multistatus>
|
||||||
|
`;
|
||||||
response = new Response(page, {
|
response = new Response(page, {
|
||||||
status: 207,
|
status: 207,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/xml',
|
'Content-Type': 'text/xml',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(await request.text(), page);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '1': {
|
case '1': {
|
||||||
let r2_objects = await bucket.list({
|
|
||||||
prefix: resource_path,
|
|
||||||
});
|
|
||||||
let page = `<?xml version="1.0" encoding="utf-8"?>
|
let page = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
<multistatus xmlns="DAV:">`;
|
<multistatus xmlns="DAV:">`;
|
||||||
for (let object of r2_objects.objects.filter(
|
|
||||||
object => {
|
let cursor: string | undefined = undefined;
|
||||||
let path = object.key.slice(resource_path.length);
|
do {
|
||||||
return !path.includes('/') ||
|
var r2_objects = await bucket.list({
|
||||||
(path.split('/').length === 2 && path.endsWith('/'));
|
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 += `
|
page += `
|
||||||
<response>
|
<response>
|
||||||
<href>${object.key}</href>
|
<href>${object.key}</href>
|
||||||
@ -270,55 +313,25 @@ export default {
|
|||||||
${Object.entries(fromR2Object(object))
|
${Object.entries(fromR2Object(object))
|
||||||
.filter(([_, value]) => value !== undefined)
|
.filter(([_, value]) => value !== undefined)
|
||||||
.map(([key, value]) => `<${key}>${value}</${key}>`)
|
.map(([key, value]) => `<${key}>${value}</${key}>`)
|
||||||
.join('\n')
|
.join('\n ')
|
||||||
}
|
}
|
||||||
</prop>
|
</prop>
|
||||||
<status>HTTP/1.1 200 OK</status>
|
<status>HTTP/1.1 200 OK</status>
|
||||||
</propstat>
|
</propstat>
|
||||||
</response>`;
|
</response>`;
|
||||||
}
|
}
|
||||||
page += '</multistatus>';
|
|
||||||
|
if (r2_objects.truncated) {
|
||||||
|
cursor = r2_objects.cursor;
|
||||||
|
}
|
||||||
|
} while (r2_objects.truncated)
|
||||||
|
page += '\n</multistatus>\n';
|
||||||
response = new Response(page, {
|
response = new Response(page, {
|
||||||
status: 207,
|
status: 207,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/xml',
|
'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;
|
break;
|
||||||
default: {
|
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: {
|
default: {
|
||||||
response = new Response('Method Not Allowed', {
|
response = new Response('Method Not Allowed', {
|
||||||
status: 405,
|
status: 405,
|
||||||
|
Loading…
Reference in New Issue
Block a user