fix(server): sanitize EXIF template variables and tighten storage path check (#32566)
(cherry picked from commit 6141f8bdbf6d21a14558d9f7d7325918023d1037)
This commit is contained in:
@@ -237,6 +237,45 @@ describe(StorageTemplateService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should sanitize the make, model and lensModel variables', async () => {
|
||||
const user = UserFactory.create();
|
||||
const asset = AssetFactory.from()
|
||||
.owner(user)
|
||||
.exif({ make: '../../sibling', model: 'a/b', lensModel: 'XF27mm F2.8' })
|
||||
.build();
|
||||
const config = structuredClone(defaults);
|
||||
config.storageTemplate.template = '{{make}}/{{model}}/{{lensModel}}/{{filename}}';
|
||||
sut.onConfigInit({ newConfig: config });
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
|
||||
|
||||
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success);
|
||||
|
||||
expect(mocks.move.create).toHaveBeenCalledWith({
|
||||
entityId: asset.id,
|
||||
newPath: `/data/library/${user.id}/....sibling/ab/XF27mm F2.8/${asset.originalFileName}`,
|
||||
oldPath: asset.originalPath,
|
||||
pathType: AssetPathType.Original,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not move an asset to a sibling of the library folder', async () => {
|
||||
const user = UserFactory.create();
|
||||
const asset = AssetFactory.from().owner(user).exif().build();
|
||||
const config = structuredClone(defaults);
|
||||
config.storageTemplate.template = '../{{filename}}';
|
||||
sut.onConfigInit({ newConfig: config });
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
|
||||
|
||||
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success);
|
||||
|
||||
expect(mocks.move.create).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use handlebar else condition for album', async () => {
|
||||
const user = UserFactory.create();
|
||||
const asset = AssetFactory.from().owner(user).exif().build();
|
||||
|
||||
@@ -344,8 +344,14 @@ export class StorageTemplateService extends BaseService {
|
||||
const fullPath = path.normalize(path.join(rootPath, storagePath));
|
||||
let destination = `${fullPath}.${extension}`;
|
||||
|
||||
if (!fullPath.startsWith(rootPath)) {
|
||||
this.logger.warn(`Skipped attempt to access an invalid path: ${fullPath}. Path should start with ${rootPath}`);
|
||||
const relativePath = path.relative(rootPath, fullPath);
|
||||
if (
|
||||
!relativePath ||
|
||||
relativePath === '..' ||
|
||||
relativePath.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relativePath)
|
||||
) {
|
||||
this.logger.warn(`Skipped attempt to access an invalid path: ${fullPath}. Path should be inside ${rootPath}`);
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -414,9 +420,9 @@ export class StorageTemplateService extends BaseService {
|
||||
assetIdShort: asset.id.slice(-12),
|
||||
//just throw into the root if it doesn't belong to an album
|
||||
album: (albumName && sanitize(albumName.replaceAll(/\.+/g, ''))) || '',
|
||||
make: make ?? '',
|
||||
model: model ?? '',
|
||||
lensModel: lensModel ?? '',
|
||||
make: sanitize(make ?? ''),
|
||||
model: sanitize(model ?? ''),
|
||||
lensModel: sanitize(lensModel ?? ''),
|
||||
};
|
||||
|
||||
const dt = DateTime.fromJSDate(asset.fileCreatedAt);
|
||||
|
||||
Reference in New Issue
Block a user