diff --git a/apps/backend-e2e/files/map_nozip.bsp b/apps/backend-e2e/files/map_nozip.bsp new file mode 100644 index 000000000..87f3f2c8e Binary files /dev/null and b/apps/backend-e2e/files/map_nozip.bsp differ diff --git a/apps/backend-e2e/src/maps.e2e-spec.ts b/apps/backend-e2e/src/maps.e2e-spec.ts index 21d0fec30..02bc080f8 100644 --- a/apps/backend-e2e/src/maps.e2e-spec.ts +++ b/apps/backend-e2e/src/maps.e2e-spec.ts @@ -506,6 +506,7 @@ describe('Maps', () => { u2, u3, bspBuffer, + nozipBspBuffer, bspHash, vmfBuffer, vmfHash; @@ -519,6 +520,8 @@ describe('Maps', () => { bspBuffer = readFileSync(path.join(FILES_PATH, 'map.bsp')); bspHash = createSha1Hash(bspBuffer); + nozipBspBuffer = readFileSync(path.join(FILES_PATH, 'map_nozip.bsp')); + vmfBuffer = readFileSync(path.join(FILES_PATH, 'map.vmf')); vmfHash = createSha1Hash(vmfBuffer); @@ -976,6 +979,36 @@ describe('Maps', () => { }); }); + it('should 400 if a BSP file has invalid header', async () => { + await req.postAttach({ + url: 'maps', + status: 400, + data: createMapObject, + files: [ + { + file: Buffer.alloc(100), + field: 'bsp', + fileName: 'surf_map.bsp' + }, + { file: vmfBuffer, field: 'vmfs', fileName: 'surf_map.vmf' } + ], + token + }); + }); + + it('should 400 if a BSP file was not compressed', async () => { + await req.postAttach({ + url: 'maps', + status: 400, + data: createMapObject, + files: [ + { file: nozipBspBuffer, field: 'bsp', fileName: 'surf_map.bsp' }, + { file: vmfBuffer, field: 'vmfs', fileName: 'surf_map.vmf' } + ], + token + }); + }); + it('should succeed if VMF file is missing', async () => req.postAttach({ url: 'maps', diff --git a/apps/backend/src/app/modules/maps/maps.service.ts b/apps/backend/src/app/modules/maps/maps.service.ts index 5c149655d..8573380df 100644 --- a/apps/backend/src/app/modules/maps/maps.service.ts +++ b/apps/backend/src/app/modules/maps/maps.service.ts @@ -88,6 +88,7 @@ import { LeaderboardHandler, LeaderboardProps } from './leaderboard-handler.util'; +import { BspHeader, BspReadError } from '@momentum/formats/bsp'; @Injectable() export class MapsService { @@ -538,6 +539,9 @@ export class MapsService { vmfFiles?: File[] ): Promise { await this.checkCreateDto(userID, dto); + + await this.checkMapCompression(bspFile); + this.checkMapFiles(dto.fileName, bspFile, vmfFiles); this.checkMapFileNames(dto.name, dto.fileName); @@ -634,6 +638,8 @@ export class MapsService { throw new ForbiddenException('Map does not allow editing'); } + await this.checkMapCompression(bspFile); + this.checkMapFiles(map.fileName, bspFile, vmfFiles); const hasVmf = vmfFiles?.length > 0; @@ -1674,6 +1680,25 @@ export class MapsService { } } + /** + * Check if provided bsp file was compressed with bspzip + * @throws BadRequestException + */ + async checkMapCompression(bspFile: File) { + const header = await BspHeader.fromBlob(new Blob([bspFile.buffer])).catch( + (error) => { + throw new BadRequestException( + error instanceof BspReadError + ? error.message + : 'Unknown error reading BSP file' + ); + } + ); + + if (!header.isCompressed()) + throw new BadRequestException('BSP is not compressed'); + } + //#endregion }