pettier . --write

This commit is contained in:
abersheeran 2023-12-24 13:51:38 +08:00
parent bdef65eafd
commit 988fc29dda
4 changed files with 136 additions and 104 deletions

View File

@ -18,8 +18,8 @@ USERNAME = "USERNAME"
PASSWORD = "PASSWORD" PASSWORD = "PASSWORD"
``` ```
* USERNAME: The username of WebDav. - USERNAME: The username of WebDav.
* PASSWORD: The password of WebDav. - PASSWORD: The password of WebDav.
## Development ## Development

16
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20231121.0", "@cloudflare/workers-types": "^4.20231121.0",
"prettier": "3.1.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"wrangler": "^3.0.0" "wrangler": "^3.0.0"
} }
@ -970,6 +971,21 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/prettier": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/printable-characters": { "node_modules/printable-characters": {
"version": "1.0.42", "version": "1.0.42",
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",

View File

@ -5,10 +5,13 @@
"scripts": { "scripts": {
"deploy": "wrangler deploy", "deploy": "wrangler deploy",
"dev": "wrangler dev", "dev": "wrangler dev",
"start": "wrangler dev" "start": "wrangler dev",
"lint-perttier": "prettier . --write",
"check-perttier": "prettier . --check"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20231121.0", "@cloudflare/workers-types": "^4.20231121.0",
"prettier": "3.1.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"wrangler": "^3.0.0" "wrangler": "^3.0.0"
}, },

View File

@ -46,21 +46,11 @@ async function* listAll(bucket: R2Bucket, prefix: string, isRecursive: boolean =
if (r2_objects.truncated) { if (r2_objects.truncated) {
cursor = r2_objects.cursor; cursor = r2_objects.cursor;
} }
} while (r2_objects.truncated) } while (r2_objects.truncated);
} }
const DAV_CLASS = '1';
const DAV_CLASS = "1"; const SUPPORT_METHODS = ['OPTIONS', 'PROPFIND', 'MKCOL', 'GET', 'HEAD', 'PUT', 'COPY', 'MOVE'];
const SUPPORT_METHODS = [
"OPTIONS",
"PROPFIND",
"MKCOL",
"GET",
"HEAD",
"PUT",
"COPY",
"MOVE",
];
type DavProperties = { type DavProperties = {
creationdate: string | undefined; creationdate: string | undefined;
@ -71,7 +61,7 @@ type DavProperties = {
getetag: string | undefined; getetag: string | undefined;
getlastmodified: string | undefined; getlastmodified: string | undefined;
resourcetype: string; resourcetype: string;
} };
function fromR2Object(object: R2Object | null | undefined): DavProperties { function fromR2Object(object: R2Object | null | undefined): DavProperties {
if (object === null || object === undefined) { if (object === null || object === undefined) {
@ -79,11 +69,11 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
creationdate: new Date().toUTCString(), creationdate: new Date().toUTCString(),
displayname: undefined, displayname: undefined,
getcontentlanguage: undefined, getcontentlanguage: undefined,
getcontentlength: "0", getcontentlength: '0',
getcontenttype: undefined, getcontenttype: undefined,
getetag: undefined, getetag: undefined,
getlastmodified: new Date().toUTCString(), getlastmodified: new Date().toUTCString(),
resourcetype: "<collection />", resourcetype: '<collection />',
}; };
} }
@ -99,7 +89,6 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
}; };
} }
function make_resource_path(request: Request): string { function make_resource_path(request: Request): string {
let path = new URL(request.url).pathname.slice(1); let path = new URL(request.url).pathname.slice(1);
path = path.endsWith('/') ? path.slice(0, -1) : path; path = path.endsWith('/') ? path.slice(0, -1) : path;
@ -110,9 +99,9 @@ async function handle_options(request: Request, bucket: R2Bucket): Promise<Respo
return new Response(null, { return new Response(null, {
status: 204, status: 204,
headers: { headers: {
'DAV': DAV_CLASS, DAV: DAV_CLASS,
'Allow': SUPPORT_METHODS.join(', '), Allow: SUPPORT_METHODS.join(', '),
} },
}); });
} }
@ -133,7 +122,7 @@ async function handle_get(request: Request, bucket: R2Bucket): Promise<Response>
if (resource_path !== '') page += `<a href="../">..</a><br>`; if (resource_path !== '') page += `<a href="../">..</a><br>`;
for await (const object of listAll(bucket, resource_path)) { for await (const object of listAll(bucket, resource_path)) {
if (object.key === resource_path) { if (object.key === resource_path) {
continue continue;
} }
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`; let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
page += `<a href="${href}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`; page += `<a href="${href}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`;
@ -147,12 +136,12 @@ async function handle_get(request: Request, bucket: R2Bucket): Promise<Response>
let isR2ObjectBody = (object: R2Object | R2ObjectBody): object is R2ObjectBody => { let isR2ObjectBody = (object: R2Object | R2ObjectBody): object is R2ObjectBody => {
return 'body' in object; return 'body' in object;
} };
if (object === null) { if (object === null) {
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });
} else if (!isR2ObjectBody(object)) { } else if (!isR2ObjectBody(object)) {
return new Response("Precondition Failed", { status: 412 }); return new Response('Precondition Failed', { status: 412 });
} else { } else {
return new Response(object.body, { return new Response(object.body, {
status: object.range ? 206 : 200, status: object.range ? 206 : 200,
@ -160,22 +149,32 @@ async function handle_get(request: Request, bucket: R2Bucket): Promise<Response>
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream', 'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
// TODO: Content-Length, Content-Range // TODO: Content-Length, Content-Range
...(object.httpMetadata?.contentDisposition ? { ...(object.httpMetadata?.contentDisposition
'Content-Disposition': object.httpMetadata.contentDisposition, ? {
} : {}), 'Content-Disposition': object.httpMetadata.contentDisposition,
...(object.httpMetadata?.contentEncoding ? { }
'Content-Encoding': object.httpMetadata.contentEncoding, : {}),
} : {}), ...(object.httpMetadata?.contentEncoding
...(object.httpMetadata?.contentLanguage ? { ? {
'Content-Language': object.httpMetadata.contentLanguage, 'Content-Encoding': object.httpMetadata.contentEncoding,
} : {}), }
...(object.httpMetadata?.cacheControl ? { : {}),
'Cache-Control': object.httpMetadata.cacheControl, ...(object.httpMetadata?.contentLanguage
} : {}), ? {
...(object.httpMetadata?.cacheExpiry ? { 'Content-Language': object.httpMetadata.contentLanguage,
'Cache-Expiry': object.httpMetadata.cacheExpiry.toISOString(), }
} : {}), : {}),
} ...(object.httpMetadata?.cacheControl
? {
'Cache-Control': object.httpMetadata.cacheControl,
}
: {}),
...(object.httpMetadata?.cacheExpiry
? {
'Cache-Expiry': object.httpMetadata.cacheExpiry.toISOString(),
}
: {}),
},
}); });
} }
} }
@ -209,10 +208,11 @@ async function handle_delete(request: Request, bucket: R2Bucket): Promise<Respon
let resource_path = make_resource_path(request); let resource_path = make_resource_path(request);
if (resource_path === '') { if (resource_path === '') {
let r2_objects, cursor: string | undefined = undefined; let r2_objects,
cursor: string | undefined = undefined;
do { do {
r2_objects = await bucket.list({ cursor: cursor }); r2_objects = await bucket.list({ cursor: cursor });
let keys = r2_objects.objects.map(object => object.key); let keys = r2_objects.objects.map((object) => object.key);
if (keys.length > 0) { if (keys.length > 0) {
await bucket.delete(keys); await bucket.delete(keys);
} }
@ -234,13 +234,14 @@ async function handle_delete(request: Request, bucket: R2Bucket): Promise<Respon
return new Response(null, { status: 204 }); return new Response(null, { status: 204 });
} }
let r2_objects, cursor: string | undefined = undefined; let r2_objects,
cursor: string | undefined = undefined;
do { do {
r2_objects = await bucket.list({ r2_objects = await bucket.list({
prefix: resource_path + "/", prefix: resource_path + '/',
cursor: cursor, cursor: cursor,
}); });
let keys = r2_objects.objects.map(object => object.key); let keys = r2_objects.objects.map((object) => object.key);
if (keys.length > 0) { if (keys.length > 0) {
await bucket.delete(keys); await bucket.delete(keys);
} }
@ -267,15 +268,15 @@ async function handle_mkcol(request: Request, bucket: R2Bucket): Promise<Respons
} }
// Check if the parent directory exists // Check if the parent directory exists
let parent_dir = resource_path.split('/').slice(0, -1).join("/"); let parent_dir = resource_path.split('/').slice(0, -1).join('/');
if (parent_dir !== '' && !await bucket.head(parent_dir)) { if (parent_dir !== '' && !(await bucket.head(parent_dir))) {
return new Response('Conflict', { status: 409 }); return new Response('Conflict', { status: 409 });
} }
await bucket.put(resource_path, new Uint8Array(), { await bucket.put(resource_path, new Uint8Array(), {
httpMetadata: request.headers, httpMetadata: request.headers,
customMetadata: { resourcetype: '<collection />' } customMetadata: { resourcetype: '<collection />' },
}); });
return new Response('', { status: 201 }); return new Response('', { status: 201 });
} }
@ -287,7 +288,7 @@ async function handle_propfind(request: Request, bucket: R2Bucket): Promise<Resp
let page = `<?xml version="1.0" encoding="utf-8"?> let page = `<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">`; <multistatus xmlns="DAV:">`;
if (resource_path === "") { if (resource_path === '') {
page += ` page += `
<response> <response>
<href>/</href> <href>/</href>
@ -321,53 +322,55 @@ async function handle_propfind(request: Request, bucket: R2Bucket): Promise<Resp
</prop> </prop>
<status>HTTP/1.1 200 OK</status> <status>HTTP/1.1 200 OK</status>
</propstat> </propstat>
</response>` </response>`;
}; }
if (is_collection) { if (is_collection) {
let depth = request.headers.get('Depth') ?? 'infinity'; let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) { switch (depth) {
case '0': break; case '0':
case '1': {
let prefix = resource_path === "" ? resource_path : resource_path + '/';
for await (let object of listAll(bucket, prefix)) {
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
page += `
<response>
<href>${href}</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>`;
}
}
break; break;
case 'infinity': { case '1':
let prefix = resource_path === "" ? resource_path : resource_path + '/'; {
for await (let object of listAll(bucket, prefix, true)) { let prefix = resource_path === '' ? resource_path : resource_path + '/';
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`; for await (let object of listAll(bucket, prefix)) {
page += ` let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
page += `
<response> <response>
<href>${href}</href> <href>${href}</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>`;
}
}
break;
case 'infinity':
{
let prefix = resource_path === '' ? resource_path : resource_path + '/';
for await (let object of listAll(bucket, prefix, true)) {
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
page += `
<response>
<href>${href}</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>`;
}
} }
}
break; break;
default: { default: {
return new Response('Forbidden', { status: 403 }); return new Response('Forbidden', { status: 403 });
@ -395,8 +398,11 @@ async function handle_copy(request: Request, bucket: R2Bucket): Promise<Response
destination = destination.endsWith('/') ? destination.slice(0, -1) : destination; destination = destination.endsWith('/') ? destination.slice(0, -1) : destination;
// Check if the parent directory exists // Check if the parent directory exists
let destination_parent = destination.split('/').slice(0, destination.endsWith('/') ? -2 : -1).join('/'); let destination_parent = destination
if (destination_parent !== '' && !await bucket.head(destination_parent)) { .split('/')
.slice(0, destination.endsWith('/') ? -2 : -1)
.join('/');
if (destination_parent !== '' && !(await bucket.head(destination_parent))) {
return new Response('Conflict', { status: 409 }); return new Response('Conflict', { status: 409 });
} }
@ -417,10 +423,10 @@ async function handle_copy(request: Request, bucket: R2Bucket): Promise<Response
let depth = request.headers.get('Depth') ?? 'infinity'; let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) { switch (depth) {
case 'infinity': { case 'infinity': {
let prefix = resource_path + "/"; let prefix = resource_path + '/';
const copy = async (object: R2Object) => { const copy = async (object: R2Object) => {
let target = destination + "/" + object.key.slice(prefix.length); let target = destination + '/' + object.key.slice(prefix.length);
target = target.endsWith("/") ? target.slice(0, -1) : target; target = target.endsWith('/') ? target.slice(0, -1) : target;
let src = await bucket.get(object.key); let src = await bucket.get(object.key);
if (src !== null) { if (src !== null) {
await bucket.put(target, src.body, { await bucket.put(target, src.body, {
@ -487,8 +493,11 @@ async function handle_move(request: Request, bucket: R2Bucket): Promise<Response
destination = destination.endsWith('/') ? destination.slice(0, -1) : destination; destination = destination.endsWith('/') ? destination.slice(0, -1) : destination;
// Check if the parent directory exists // Check if the parent directory exists
let destination_parent = destination.split('/').slice(0, destination.endsWith('/') ? -2 : -1).join('/'); let destination_parent = destination
if (destination_parent !== '' && !await bucket.head(destination_parent)) { .split('/')
.slice(0, destination.endsWith('/') ? -2 : -1)
.join('/');
if (destination_parent !== '' && !(await bucket.head(destination_parent))) {
return new Response('Conflict', { status: 409 }); return new Response('Conflict', { status: 409 });
} }
@ -506,7 +515,8 @@ async function handle_move(request: Request, bucket: R2Bucket): Promise<Response
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
if (destination_exists) { // Delete the destination first if (destination_exists) {
// Delete the destination first
await handle_delete(new Request(new URL(destination_header), request), bucket); await handle_delete(new Request(new URL(destination_header), request), bucket);
} }
@ -516,10 +526,10 @@ async function handle_move(request: Request, bucket: R2Bucket): Promise<Response
let depth = request.headers.get('Depth') ?? 'infinity'; let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) { switch (depth) {
case 'infinity': { case 'infinity': {
let prefix = resource_path + "/"; let prefix = resource_path + '/';
const copy = async (object: R2Object) => { const copy = async (object: R2Object) => {
let target = destination + "/" + object.key.slice(prefix.length); let target = destination + '/' + object.key.slice(prefix.length);
target = target.endsWith("/") ? target.slice(0, -1) : target; target = target.endsWith('/') ? target.slice(0, -1) : target;
let src = await bucket.get(object.key); let src = await bucket.get(object.key);
if (src !== null) { if (src !== null) {
await bucket.put(target, src.body, { await bucket.put(target, src.body, {
@ -611,9 +621,9 @@ async function dispatch_handler(request: Request, bucket: R2Bucket): Promise<Res
return new Response('Method Not Allowed', { return new Response('Method Not Allowed', {
status: 405, status: 405,
headers: { headers: {
'Allow': SUPPORT_METHODS.join(', '), Allow: SUPPORT_METHODS.join(', '),
'DAV': DAV_CLASS, DAV: DAV_CLASS,
} },
}); });
} }
} }
@ -625,9 +635,10 @@ export default {
if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) { if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) {
return new Response('Unauthorized', { return new Response('Unauthorized', {
status: 401, headers: { status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="webdav"', 'WWW-Authenticate': 'Basic realm="webdav"',
} },
}); });
} }
@ -636,15 +647,17 @@ export default {
// Set CORS headers // Set CORS headers
response.headers.set('Access-Control-Allow-Origin', request.headers.get('Origin') ?? '*'); response.headers.set('Access-Control-Allow-Origin', request.headers.get('Origin') ?? '*');
response.headers.set('Access-Control-Allow-Methods', SUPPORT_METHODS.join(', ')); response.headers.set('Access-Control-Allow-Methods', SUPPORT_METHODS.join(', '));
response.headers.set('Access-Control-Allow-Headers', response.headers.set(
["authorization", "content-type", "depth", "overwrite", "destination", "range"].join(', ') 'Access-Control-Allow-Headers',
['authorization', 'content-type', 'depth', 'overwrite', 'destination', 'range'].join(', '),
); );
response.headers.set('Access-Control-Expose-Headers', response.headers.set(
["content-type", "content-length", "dav", "etag", "last-modified", "location", "date", "content-range"].join(', ') 'Access-Control-Expose-Headers',
['content-type', 'content-length', 'dav', 'etag', 'last-modified', 'location', 'date', 'content-range'].join(', '),
); );
response.headers.set('Access-Control-Allow-Credentials', 'false'); response.headers.set('Access-Control-Allow-Credentials', 'false');
response.headers.set('Access-Control-Max-Age', '86400'); response.headers.set('Access-Control-Max-Age', '86400');
return response return response;
}, },
}; };