Refactor index.ts
This commit is contained in:
parent
316c90b62c
commit
23cfd81123
2375
package-lock.json
generated
2375
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -1,19 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "r2-webdav",
|
"name": "r2-webdav",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "wrangler deploy",
|
"deploy": "wrangler deploy",
|
||||||
"dev": "wrangler dev",
|
"dev": "wrangler dev",
|
||||||
"start": "wrangler dev"
|
"start": "wrangler dev"
|
||||||
},
|
},
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
197
src/index.ts
197
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);
|
||||||
let body = await request.arrayBuffer();
|
if (!dir || dir.customMetadata?.resourcetype !== '<collection />') {
|
||||||
await bucket.put(resource_path, body, {
|
|
||||||
onlyIf: request.headers,
|
|
||||||
httpMetadata: request.headers,
|
|
||||||
});
|
|
||||||
response = new Response('', { status: 201 });
|
|
||||||
} else {
|
|
||||||
response = new Response('Conflict', { status: 409 });
|
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 });
|
||||||
}
|
}
|
||||||
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;
|
||||||
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)) {
|
|
||||||
response = new Response('Conflict', { status: 409 });
|
|
||||||
} else {
|
|
||||||
await bucket.put(resource_path, new Uint8Array(), {
|
|
||||||
httpMetadata: request.headers,
|
|
||||||
});
|
|
||||||
response = new Response('', { status: 201 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (parent_dir !== '/' && !await bucket.head(parent_dir)) {
|
||||||
|
response = new Response('Conflict', { status: 409 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bucket.put(resource_path, new Uint8Array(), { httpMetadata: request.headers });
|
||||||
|
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,88 +268,70 @@ 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>`;
|
||||||
}
|
}
|
||||||
)) {
|
|
||||||
page += `
|
for (let object of r2_objects.objects.filter(object => !object.key.endsWith('/'))) {
|
||||||
|
page += `
|
||||||
<response>
|
<response>
|
||||||
<href>${object.key}</href>
|
<href>${object.key}</href>
|
||||||
<propstat>
|
<propstat>
|
||||||
<prop>
|
<prop>
|
||||||
${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