From a601e5343005ae6311c5a520491d2f8a88c8ff91 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Wed, 7 Jun 2023 23:40:49 -0700 Subject: [PATCH] fix: FTP access to deep subdirectories and pb_migrations --- .../src/services/FtpService/FtpService.ts | 3 + .../daemon/src/services/FtpService/PhFs.ts | 89 ++++++++++--------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/packages/daemon/src/services/FtpService/FtpService.ts b/packages/daemon/src/services/FtpService/FtpService.ts index 74f67a08..808f6f7a 100644 --- a/packages/daemon/src/services/FtpService/FtpService.ts +++ b/packages/daemon/src/services/FtpService/FtpService.ts @@ -17,6 +17,7 @@ export type FtpConfig = {} export enum FolderNames { PbData = 'pb_data', PbStatic = 'pb_static', + PbMigrations = 'pb_migrations', PbWorker = 'worker', PbBackup = 'backup', } @@ -26,6 +27,7 @@ export const README_CONTENTS: { [_ in FolderNames]: string } = { [FolderNames.PbData]: `This directory contains your PocketBase data. For more information, see https://pockethost.io/docs/data`, [FolderNames.PbStatic]: `This directory contains static files such as your web frontend. PocketHost will serve these when your instance URL receives a request. For more information, see https://pockethost.io/docs/static `, [FolderNames.PbWorker]: `This directory contains your Deno worker. For more information, see https://pockethost.io/docs/workers`, + [FolderNames.PbMigrations]: `This directory contains your migrations. For more information, see https://pockethost.io/docs/migrations`, } export const README_NAME = 'readme.md' @@ -34,6 +36,7 @@ export const FOLDER_NAMES: FolderNames[] = [ FolderNames.PbData, FolderNames.PbStatic, FolderNames.PbWorker, + FolderNames.PbMigrations, ] export function isFolder(name: string): name is FolderNames { diff --git a/packages/daemon/src/services/FtpService/PhFs.ts b/packages/daemon/src/services/FtpService/PhFs.ts index 898d2ca2..4d2d3381 100644 --- a/packages/daemon/src/services/FtpService/PhFs.ts +++ b/packages/daemon/src/services/FtpService/PhFs.ts @@ -47,8 +47,8 @@ export class PhFs extends FileSystem { throw new Error(`Expected path`) } const _path = path.startsWith('/') ? path : join(this.cwd, path) - const [empty, subdomain, folderName] = _path.split('/') - this.log.dbg({ _path, subdomain, folderName }) + const [empty, subdomain, folderName, ...restOfPath] = _path.split('/') + this.log.dbg({ _path, subdomain, folderName, restOfPath }) if (subdomain === '') { const instances = await this.client.getInstances() @@ -62,49 +62,48 @@ export class PhFs extends FileSystem { } }) } - if (subdomain) { - const [instance, user] = await this.client.getInstanceBySubdomain( - subdomain - ) - if (!instance) { - throw new Error(`Expected instance here`) - } - if (!folderName) { - return FOLDER_NAMES.map((name) => ({ - isDirectory: () => true, - mode: 0o755, - size: 0, - mtime: Date.parse(instance.updated), - name: name, - })) - } - if (isFolder(folderName)) { - const dir = join(DAEMON_PB_DATA_DIR, instance.id, folderName) - this.log.dbg({ dir, exists: existsSync(dir) }) - return [ - { - isDirectory: () => false, - mode: 0o444, - size: README_CONTENTS[folderName].length, - mtime: Date.parse(instance.updated), - name: README_NAME, - }, - ...(existsSync(dir) - ? await super.list( - join(DAEMON_PB_DATA_DIR, instance.id, folderName) - ) - : []), - ] - } + if (!subdomain) { + throw new Error(`Subdomain expected in ${_path}`) } - throw new Error(`Error parsing ${_path}`) + const [instance, user] = await this.client.getInstanceBySubdomain(subdomain) + if (!instance) { + throw new Error(`Expected instance here`) + } + if (!folderName) { + return FOLDER_NAMES.map((name) => ({ + isDirectory: () => true, + mode: 0o755, + size: 0, + mtime: Date.parse(instance.updated), + name: name, + })) + } + if (!isFolder(folderName)) { + throw new Error(`Top level folder name ${folderName} not allowed.`) + } + const dir = join(DAEMON_PB_DATA_DIR, instance.id, folderName, ...restOfPath) + this.log.dbg({ dir, exists: existsSync(dir) }) + return [ + ...(restOfPath.length === 0 + ? [ + { + isDirectory: () => false, + mode: 0o444, + size: README_CONTENTS[folderName].length, + mtime: Date.parse(instance.updated), + name: README_NAME, + }, + ] + : []), + ...(existsSync(dir) ? await super.list(dir) : []), + ] } async get(fileName: string): Promise { - const _path = fileName.startsWith('/') ? fileName : join(this.cwd, fileName) - const [empty, subdomain, folderName, ...fNames] = _path.split('/') + const path = fileName.startsWith('/') ? fileName : join(this.cwd, fileName) + const [empty, subdomain, folderName, ...fNames] = path.split('/') const fName = fNames.join('/') - this.log.dbg(`get`, { _path, subdomain, folderName, fName, fileName }) + this.log.dbg(`get`, { _path: path, subdomain, folderName, fName, fileName }) if (!subdomain) { return { @@ -140,7 +139,15 @@ export class PhFs extends FileSystem { name: folderName, } } - return super.get(_path) + const physicalPath = join( + DAEMON_PB_DATA_DIR, + instance.id, + folderName, + fName + ) + this.log.dbg({ physicalPath, exists: existsSync(physicalPath) }) + + return super.get(physicalPath) } async write(