diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index c3e43fd12..1c3142fb0 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -13,40 +13,50 @@ import { useTheme } from 'providers/Theme/index'; import { getEncoding, uuid } from 'utils/common/index'; import LargeResponseWarning from '../LargeResponseWarning'; +// Memory threshold to prevent crashes when decoding large buffers +const LARGE_BUFFER_THRESHOLD = 50 * 1024 * 1024; // 50 MB + +const applyJSONPathFilter = (data, filter) => { + try { + return JSONPath({ path: filter, json: data }); + } catch (e) { + console.warn('Could not apply JSONPath filter:', e.message); + return data; + } +}; + const formatResponse = (data, dataBuffer, encoding, mode, filter) => { if (data === undefined || !dataBuffer || !mode) { return ''; } - // TODO: We need a better way to get the raw response-data here instead - // of using this dataBuffer param. - // Also, we only need the raw response-data and content-type to show the preview. - const rawData = iconv.decode( - Buffer.from(dataBuffer, "base64"), - iconv.encodingExists(encoding) ? encoding : "utf-8" - ); + let bufferSize = 0; + try { + bufferSize = Buffer.from(dataBuffer, 'base64').length; + } catch (error) { + console.warn('Failed to calculate buffer size:', error); + } + + const isVeryLargeResponse = bufferSize > LARGE_BUFFER_THRESHOLD; if (mode.includes('json')) { try { - JSON.parse(rawData); + const prettyPrint = !isVeryLargeResponse; + const processedData = filter ? applyJSONPathFilter(data, filter) : data; + + return typeof processedData === 'string' + ? processedData + : safeStringifyJSON(processedData, prettyPrint); } catch (error) { - // If the response content-type is JSON and it fails parsing, its an invalid JSON. - // In that case, just show the response as it is in the preview. - return rawData; + return typeof data === 'string' ? data : String(data); } - - if (filter) { - try { - data = JSONPath({ path: filter, json: data }); - } catch (e) { - console.warn('Could not apply JSONPath filter:', e.message); - } - } - - return safeStringifyJSON(data, true); } if (mode.includes('xml')) { + if (isVeryLargeResponse) { + return typeof data === 'string' ? data : safeStringifyJSON(data, false); + } + let parsed = safeParseXML(data, { collapseContent: true }); if (typeof parsed === 'string') { return parsed; @@ -58,7 +68,7 @@ const formatResponse = (data, dataBuffer, encoding, mode, filter) => { return data; } - return safeStringifyJSON(data, true); + return safeStringifyJSON(data, !isVeryLargeResponse); }; const formatErrorMessage = (error) => { diff --git a/tests/response/large-response-crash-prevention.spec.ts b/tests/response/large-response-crash-prevention.spec.ts new file mode 100644 index 000000000..33e3915e0 --- /dev/null +++ b/tests/response/large-response-crash-prevention.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from '../../playwright'; + +test.describe('Large Response Crash Prevention', () => { + test('should show appropriate warning for responses over 10MB', async ({ page, createTmpDir }) => { + // Create collection + await page.getByLabel('Create Collection').click(); + await page.getByLabel('Name').fill('size-warning-test'); + await page.getByLabel('Name').press('Tab'); + await page.getByLabel('Location').fill(await createTmpDir('size-warning-test')); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + await page.getByText('size-warning-test').click(); + await page.getByLabel('Safe Mode').check(); + await page.getByRole('button', { name: 'Save' }).click(); + + // Create request + await page.locator('#create-new-tab').getByRole('img').click(); + await page.getByPlaceholder('Request Name').fill('size-check'); + await page.locator('#new-request-url .CodeMirror').click(); + await page.locator('textarea').fill('https://samples.json-format.com/employees/json/employees_50MB.json'); + await page.getByRole('button', { name: 'Create' }).click(); + + // Send request + const sendButton = page.locator('#send-request').getByRole('img').nth(2); + await sendButton.click(); + + // Verify warning appears + await expect(page.getByText('Large Response Warning')).toBeVisible({ timeout: 60000 }); + + // Verify warning content + await expect(page.getByText('Handling responses over')).toBeVisible(); + await expect(page.getByText('could degrade performance')).toBeVisible(); + + // Verify action button + await expect(page.getByRole('button', { name: 'View' })).toBeVisible(); + + console.log('Large response warning displayed correctly'); + }); +});