diff --git a/packages/core/src/utils/envelope.ts b/packages/core/src/utils/envelope.ts index 4eacc1919410..b0c5b511f698 100644 --- a/packages/core/src/utils/envelope.ts +++ b/packages/core/src/utils/envelope.ts @@ -168,7 +168,7 @@ export function parseEnvelope(env: string | Uint8Array): Envelope { const itemHeader = readJson(); const binaryLength = typeof itemHeader.length === 'number' ? itemHeader.length : undefined; - items.push([itemHeader, binaryLength ? readBinary(binaryLength) : readJson()]); + items.push([itemHeader, binaryLength !== undefined ? readBinary(binaryLength) : readJson()]); } return [envelopeHeader, items]; diff --git a/packages/core/test/lib/utils/envelope.test.ts b/packages/core/test/lib/utils/envelope.test.ts index d6002ce1dfa3..ae068e20c7fe 100644 --- a/packages/core/test/lib/utils/envelope.test.ts +++ b/packages/core/test/lib/utils/envelope.test.ts @@ -4,6 +4,7 @@ import type { EventEnvelope } from '../../../src/types/envelope'; import type { Event } from '../../../src/types/event'; import { addItemToEnvelope, + createAttachmentEnvelopeItem, createEnvelope, forEachEnvelopeItem, parseEnvelope, @@ -90,6 +91,22 @@ describe('envelope', () => { after?.(); }); + it('parses an envelope with an empty attachment', () => { + const env = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + createAttachmentEnvelopeItem({ filename: 'empty.txt', data: '' }), + createAttachmentEnvelopeItem({ filename: 'foo.txt', data: 'foo' }), + ]); + + const [, parsedItems] = parseEnvelope(serializeEnvelope(env)); + + expect(parsedItems).toEqual([ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + [{ type: 'attachment', filename: 'empty.txt', length: 0 }, new Uint8Array(0)], + [{ type: 'attachment', filename: 'foo.txt', length: 3 }, new TextEncoder().encode('foo')], + ]); + }); + it("doesn't throw when being passed a an envelope that contains a circular item payload", () => { const chicken: { egg?: any } = {}; const egg = { chicken } as unknown as Event; diff --git a/packages/core/test/lib/utils/tunnel.test.ts b/packages/core/test/lib/utils/tunnel.test.ts index 2b499ce754ae..786465cb6e80 100644 --- a/packages/core/test/lib/utils/tunnel.test.ts +++ b/packages/core/test/lib/utils/tunnel.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { getEnvelopeEndpointWithUrlEncodedAuth } from '../../../src/api'; import { makeDsn } from '../../../src/utils/dsn'; -import { createEnvelope, serializeEnvelope } from '../../../src/utils/envelope'; +import { createAttachmentEnvelopeItem, createEnvelope, serializeEnvelope } from '../../../src/utils/envelope'; import { handleTunnelRequest } from '../../../src/utils/tunnel'; const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337'; @@ -43,6 +43,24 @@ describe('handleTunnelRequest', () => { expect(result).toBe(upstreamResponse); }); + it('forwards an envelope that contains an empty attachment', async () => { + const upstreamResponse = new Response('ok', { status: 200 }); + fetchMock.mockResolvedValueOnce(upstreamResponse); + + const envelope = createEnvelope({ dsn: TEST_DSN }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + createAttachmentEnvelopeItem({ filename: 'empty.txt', data: '' }), + ]); + + const result = await handleTunnelRequest({ + request: new Request('http://localhost/tunnel', { method: 'POST', body: serializeEnvelope(envelope) }), + allowedDsns: [TEST_DSN], + }); + + expect(fetchMock).toHaveBeenCalledOnce(); + expect(result).toBe(upstreamResponse); + }); + it('returns 500 when allowedDsns is empty', async () => { const result = await handleTunnelRequest({ request: makeEnvelopeRequest({ dsn: TEST_DSN }),