diff --git a/bewcloud.config.sample.ts b/bewcloud.config.sample.ts index 67e44b6..074d77a 100644 --- a/bewcloud.config.sample.ts +++ b/bewcloud.config.sample.ts @@ -20,9 +20,11 @@ const config: PartialDeep = { // rootPath: 'data-files', // allowPublicSharing: false, // If true, public file sharing will be allowed (still requires a user to enable sharing for a given file or directory) // allowDirectoryDownloads: false, // If true, directories can be downloaded as zip files + // maxUploadSizeInMegabytes: 100, // The maximum upload size in megabytes. Overrides the core.maxRequestSizeInMegabytes setting on /dav and /api/files/upload endpoints. // }, // core: { // enabledApps: ['dashboard', 'files', 'news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], // The apps to show, in order of appearance in the header. The first app will be the default one shown after logging in. At least one is required. + // maxRequestSizeInMegabytes: 12, // The maximum request size in megabytes. // }, // visuals: { // title: 'My own cloud', diff --git a/docker-compose.yml b/docker-compose.yml index 534f446..5b05e4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: website: - image: ghcr.io/bewcloud/bewcloud:v4.1.0 + image: ghcr.io/bewcloud/bewcloud:v4.1.1 # NOTE: uncomment below (and comment above) only if you pulled the repo and want to build the image locally # build: # context: . diff --git a/lib/config.ts b/lib/config.ts index dd76a6c..d5bda6b 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -25,9 +25,11 @@ export class AppConfig { rootPath: 'data-files', allowPublicSharing: false, allowDirectoryDownloads: false, + maxUploadSizeInMegabytes: 100, }, core: { enabledApps: ['dashboard', 'files', 'news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], + maxRequestSizeInMegabytes: 12, }, visuals: { title: '', diff --git a/lib/page.ts b/lib/page.ts index 31a5396..6feb789 100644 --- a/lib/page.ts +++ b/lib/page.ts @@ -17,15 +17,7 @@ export interface Page { patch?: RequestHandler; delete?: RequestHandler; options?: RequestHandler; - copy?: RequestHandler; - move?: RequestHandler; - mkcol?: RequestHandler; - mkcalendar?: RequestHandler; - lock?: RequestHandler; - unlock?: RequestHandler; - propfind?: RequestHandler; - proppatch?: RequestHandler; - report?: RequestHandler; + catchAll?: RequestHandler; } type AccessMode = 'public' | 'user'; @@ -61,15 +53,7 @@ export default function page( patch, delete: deleteAction, options, - copy, - move, - mkcol, - mkcalendar, - lock, - unlock, - propfind, - proppatch, - report, + catchAll, accessMode, }: Params, ): Page { @@ -80,14 +64,6 @@ export default function page( patch: patch ? permissioned(patch, accessMode) : undefined, delete: deleteAction ? permissioned(deleteAction, accessMode) : undefined, options: options ? permissioned(options, accessMode) : undefined, - copy: copy ? permissioned(copy, accessMode) : undefined, - move: move ? permissioned(move, accessMode) : undefined, - mkcol: mkcol ? permissioned(mkcol, accessMode) : undefined, - mkcalendar: mkcalendar ? permissioned(mkcalendar, accessMode) : undefined, - lock: lock ? permissioned(lock, accessMode) : undefined, - unlock: unlock ? permissioned(unlock, accessMode) : undefined, - propfind: propfind ? permissioned(propfind, accessMode) : undefined, - proppatch: proppatch ? permissioned(proppatch, accessMode) : undefined, - report: report ? permissioned(report, accessMode) : undefined, + catchAll: catchAll ? permissioned(catchAll, accessMode) : undefined, }; } diff --git a/lib/types.ts b/lib/types.ts index e05306c..e907213 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -175,10 +175,14 @@ export interface Config { allowPublicSharing: boolean; /** If true, directories can be downloaded as zip files */ allowDirectoryDownloads: boolean; + /** The maximum upload size in megabytes. Overrides the core.maxRequestSizeInMegabytes setting on /dav and /api/files/upload endpoints. */ + maxUploadSizeInMegabytes: number; }; core: { /** The apps to show, in order of appearance in the header. The first app will be the default one shown after logging in. At least one is required. */ enabledApps: OptionalApp[]; + /** The maximum request size in megabytes. */ + maxRequestSizeInMegabytes: number; }; visuals: { /** An override title of the application. Empty shows the default title. */ diff --git a/main.ts b/main.ts index 61bb2f3..2006627 100644 --- a/main.ts +++ b/main.ts @@ -1,9 +1,13 @@ import routes, { Route } from './routes.ts'; import { startCrons } from './crons/index.ts'; import { Page } from './lib/page.ts'; +import { AppConfig } from './lib/config.ts'; -const MAX_REQUEST_SIZE_IN_MEGABYTES = 12; +const config = await AppConfig.getConfig(); +const MAX_REQUEST_SIZE_IN_MEGABYTES = config.core.maxRequestSizeInMegabytes; +const MAX_UPLOAD_SIZE_IN_MEGABYTES = config.files.maxUploadSizeInMegabytes; const MAX_REQUEST_SIZE_IN_BYTES = MAX_REQUEST_SIZE_IN_MEGABYTES * 1024 * 1024; +const MAX_UPLOAD_SIZE_IN_BYTES = MAX_UPLOAD_SIZE_IN_MEGABYTES * 1024 * 1024; function applyCorsHeadersToResponse(origin: string, response: Response) { const headers = response.headers; @@ -53,12 +57,15 @@ function handleLogging(request: Request, response: Response) { async function handler(request: Request) { const contentLength = request.headers.get('content-length'); + const path = new URL(request.url).pathname; - if (contentLength && parseInt(contentLength, 10) > MAX_REQUEST_SIZE_IN_BYTES) { + const isUploadRequest = path.startsWith('/api/files/upload') || path.startsWith('/dav'); + const maxSizeInBytes = isUploadRequest ? MAX_UPLOAD_SIZE_IN_BYTES : MAX_REQUEST_SIZE_IN_BYTES; + + if (contentLength && parseInt(contentLength, 10) > maxSizeInBytes) { return new Response('Payload too large', { status: 413 }); } - const path = new URL(request.url).pathname; const origin = request.headers.get('Origin') || '*'; // CORS headers for non-DAV routes diff --git a/pages/caldav.ts b/pages/caldav.ts index 1151a30..ceae223 100644 --- a/pages/caldav.ts +++ b/pages/caldav.ts @@ -75,14 +75,6 @@ export default page({ put: get, delete: get, options: get, - copy: get, - move: get, - mkcol: get, - mkcalendar: get, - lock: get, - unlock: get, - propfind: get, - proppatch: get, - report: get, + catchAll: get, accessMode: 'public', }); diff --git a/pages/carddav.ts b/pages/carddav.ts index 1f1b2ea..12b6d34 100644 --- a/pages/carddav.ts +++ b/pages/carddav.ts @@ -75,13 +75,6 @@ export default page({ put: get, delete: get, options: get, - copy: get, - move: get, - mkcol: get, - lock: get, - unlock: get, - propfind: get, - proppatch: get, - report: get, + catchAll: get, accessMode: 'public', }); diff --git a/pages/dav.ts b/pages/dav.ts index c1a5f80..eec82d8 100644 --- a/pages/dav.ts +++ b/pages/dav.ts @@ -257,12 +257,6 @@ export default page({ delete: handler, put: handler, options: handler, - copy: handler, - move: handler, - mkcol: handler, - lock: handler, - unlock: handler, - propfind: handler, - report: handler, + catchAll: handler, accessMode: 'public', }); diff --git a/routes.ts b/routes.ts index c885413..19bcfec 100644 --- a/routes.ts +++ b/routes.ts @@ -33,14 +33,7 @@ function createPageRouteHandler(id: string, pathname: string) { patch, delete: deleteAction, options, - copy, - move, - mkcol, - lock, - unlock, - propfind, - proppatch, - report, + catchAll, } = page; const { user, session, tokenData } = (await getDataFromRequest(request)) || {}; @@ -148,95 +141,17 @@ function createPageRouteHandler(id: string, pathname: string) { }); } break; - case 'COPY': - if (copy) { - return await copy({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'MOVE': - if (move) { - return await move({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'MKCOL': - if (mkcol) { - return await mkcol({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'LOCK': - if (lock) { - return await lock({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'UNLOCK': - if (unlock) { - return await unlock({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'PROPFIND': - if (propfind) { - return await propfind({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'PROPPATCH': - if (proppatch) { - return await proppatch({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; - case 'REPORT': - if (report) { - return await report({ - request, - match, - user, - session: { userSession: session, tokenData }, - isRunningLocally, - }); - } - break; default: + if (catchAll) { + return await catchAll({ + request, + match, + user, + session: { userSession: session, tokenData }, + isRunningLocally, + }); + } + return new Response('Not Implemented', { status: 501 }); }