Support downloading directories as a zip archive (#106)
* Add directory download as zip feature Implements the ability for users to download directories as zip files if enabled in config. Adds a new API route for directory zipping, updates UI components to show a download button for directories, and introduces related config and type changes. Also includes a new download icon. * Windows path bugfix * Include empty directories in zip archive * Address feedback - `isDirectoryDownloadsAllowed` -> `areDirectoryDownloadsAllowed` - send `parentPath` & `name` to API instead of resolving `fullPath` on client - call `ensureUserPathIsValidAndSecurelyAccessible` before zipping - set config `allowDirectoryDownloads` default to `false` - add `zip` to Dockerfile and replace in-house zip algorithm - replace `download.svg` with heroicon's `arrow-down-tray` - `replace` with glob -> `replaceAll` with string * Cleanup apt-get command * Remove unused zip archive and directory functions
This commit is contained in:
@@ -22,6 +22,7 @@ export class AppConfig {
|
||||
files: {
|
||||
rootPath: 'data-files',
|
||||
allowPublicSharing: false,
|
||||
allowDirectoryDownloads: true,
|
||||
},
|
||||
core: {
|
||||
enabledApps: ['news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'],
|
||||
@@ -179,6 +180,12 @@ export class AppConfig {
|
||||
return this.config.files.allowPublicSharing;
|
||||
}
|
||||
|
||||
static async areDirectoryDownloadsAllowed(): Promise<boolean> {
|
||||
await this.loadConfig();
|
||||
|
||||
return this.config.files.allowDirectoryDownloads;
|
||||
}
|
||||
|
||||
static async getFilesRootPath(): Promise<string> {
|
||||
await this.loadConfig();
|
||||
|
||||
|
||||
@@ -598,7 +598,11 @@ export async function ensureUserPathIsValidAndSecurelyAccessible(userId: string,
|
||||
|
||||
const resolvedFullPath = `${resolve(fullPath)}/`;
|
||||
|
||||
if (!resolvedFullPath.startsWith(userRootPath)) {
|
||||
// Normalize path separators for consistent comparison on Windows
|
||||
const normalizedUserRootPath = userRootPath.replaceAll('\\', '/');
|
||||
const normalizedResolvedFullPath = resolvedFullPath.replaceAll('\\', '/');
|
||||
|
||||
if (!normalizedResolvedFullPath.startsWith(normalizedUserRootPath)) {
|
||||
throw new Error('Invalid file path');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +184,8 @@ export interface Config {
|
||||
rootPath: string;
|
||||
/** If true, public file sharing will be allowed (still requires a user to enable sharing for a given file or directory) */
|
||||
allowPublicSharing: boolean;
|
||||
/** If true, directories can be downloaded as zip files */
|
||||
allowDirectoryDownloads: boolean;
|
||||
};
|
||||
core: {
|
||||
/** dashboard and files cannot be disabled */
|
||||
|
||||
Reference in New Issue
Block a user