From 2f689ea9ca7e68472bf4a9f710b068c74a274986 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 30 Oct 2026 23:14:32 +0000 Subject: [PATCH] maps: stop reading a community picture at its size cap An answer without a content-length was buffered whole before its size was checked. The read now stops at the cap. --- server/src/nest/maps/trek-community.service.ts | 6 ++++-- .../tests/unit/nest/trek-community.service.test.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/server/src/nest/maps/trek-community.service.ts b/server/src/nest/maps/trek-community.service.ts index 36d30975e..837bb40f0 100644 --- a/server/src/nest/maps/trek-community.service.ts +++ b/server/src/nest/maps/trek-community.service.ts @@ -1,3 +1,4 @@ +import { readCapped } from '../../utils/cappedFetch'; import { trekCommunityByKeys, trekPlacesBaseUrl, trekPlacesById } from './trek-places.client'; import { identifiedFetch } from './trek-places.fetch'; import { Injectable } from '@nestjs/common'; @@ -81,8 +82,9 @@ export class TrekCommunityService { await res.body?.cancel(); return null; } - const bytes = Buffer.from(await res.arrayBuffer()); - return bytes.length && bytes.length <= IMAGE_MAX_BYTES ? bytes : null; + // A chunked answer declares no length, so the read itself stops at the cap. + const { bytes, truncated } = await readCapped(res, IMAGE_MAX_BYTES); + return bytes.length && !truncated ? bytes : null; } catch { return null; } finally { diff --git a/server/tests/unit/nest/trek-community.service.test.ts b/server/tests/unit/nest/trek-community.service.test.ts index 36f2214a3..13b1e6dcc 100644 --- a/server/tests/unit/nest/trek-community.service.test.ts +++ b/server/tests/unit/nest/trek-community.service.test.ts @@ -91,4 +91,18 @@ describe('TrekCommunityService.imageBytes', () => { fetchWithKey.mockRejectedValueOnce(new Error('timeout')); expect(await svc.imageBytes(A)).toBeNull(); }); + + it('TCOM-007: an answer that declares no length stops being read at the cap', async () => { + // An endless body in 1 MB chunks, without a content-length, as a chunked answer comes. + let pulled = 0; + const body = new ReadableStream({ + pull(controller) { + pulled += 1; + controller.enqueue(new Uint8Array(1_000_000)); + }, + }); + fetchWithKey.mockResolvedValueOnce(new Response(body, { status: 200 })); + expect(await new TrekCommunityService().imageBytes(A)).toBeNull(); + expect(pulled).toBeLessThanOrEqual(6); + }); });