diff --git a/src/transport/http/binary_receiver.ts b/src/transport/http/binary_receiver.ts index 3c6fb2c3..9be2def0 100644 --- a/src/transport/http/binary_receiver.ts +++ b/src/transport/http/binary_receiver.ts @@ -77,6 +77,15 @@ export class BinaryHTTPReceiver { eventObj[header.substring(CONSTANTS.EXTENSIONS_PREFIX.length)] = headers[header]; } } + // At this point, if the datacontenttype is application/json and the datacontentencoding is base64 + // then the data has already been decoded as a string, then parsed as JSON. We don't need to have + // the datacontentencoding property set - in fact, it's incorrect to do so. + if ( + eventObj.datacontenttype === CONSTANTS.MIME_JSON && + eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64 + ) { + delete eventObj.datacontentencoding; + } const cloudevent = new CloudEvent({ ...eventObj, data: parsedPayload } as CloudEventV1 | CloudEventV03); this.version === Version.V1 ? validateV1(cloudevent) : validateV03(cloudevent); diff --git a/src/transport/http/structured_receiver.ts b/src/transport/http/structured_receiver.ts index 383c5a75..2450371e 100644 --- a/src/transport/http/structured_receiver.ts +++ b/src/transport/http/structured_receiver.ts @@ -1,6 +1,6 @@ import { CloudEvent, Version } from "../.."; import { Headers, sanitize } from "./headers"; -import { Parser, JSONParser, MappedParser } from "../../parsers"; +import { Parser, JSONParser, MappedParser, Base64Parser } from "../../parsers"; import { parserByContentType } from "../../parsers"; import { v1structuredParsers, v03structuredParsers } from "./versions"; import { isString, isBase64, ValidationError, isStringOrObjectOrThrow } from "../../event/validation"; @@ -75,6 +75,10 @@ export class StructuredHTTPReceiver { if (eventObj.data && eventObj.datacontentencoding) { if (eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64 && !isBase64(eventObj.data)) { throw new ValidationError("invalid payload"); + } else if (eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64) { + const dataParser = new Base64Parser(); + eventObj.data = JSON.parse(dataParser.parse(eventObj.data as string)); + delete eventObj.datacontentencoding; } } diff --git a/test/integration/receiver_binary_03_tests.ts b/test/integration/receiver_binary_03_tests.ts index 9ac4c6d0..dfdfd736 100644 --- a/test/integration/receiver_binary_03_tests.ts +++ b/test/integration/receiver_binary_03_tests.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { CloudEvent, ValidationError, Version } from "../../src"; import { BinaryHTTPReceiver } from "../../src/transport/http/binary_receiver"; import CONSTANTS from "../../src/constants"; +import { asBase64 } from "../../src/event/validation"; const receiver = new BinaryHTTPReceiver(Version.V03); @@ -154,6 +155,26 @@ describe("HTTP Transport Binding Binary Receiver for CloudEvents v0.3", () => { // act and assert expect(receiver.parse.bind(receiver, payload, attributes)).to.not.throw(); }); + + it("Succeeds when content-type is application/json and datacontentencoding is base64", () => { + const expected = { + whose: "ours", + }; + const bindata = Uint32Array.from(JSON.stringify(expected) as string, (c) => c.codePointAt(0) as number); + const payload = asBase64(bindata); + + const attributes = { + [CONSTANTS.CE_HEADERS.TYPE]: "test", + [CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V03, + [CONSTANTS.CE_HEADERS.SOURCE]: "/test-source", + [CONSTANTS.CE_HEADERS.ID]: "123456", + [CONSTANTS.CE_HEADERS.TIME]: "2019-06-16T11:42:00Z", + [CONSTANTS.HEADER_CONTENT_TYPE]: "application/json", + [CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING]: "base64", + }; + const event = receiver.parse(payload, attributes); + expect(event.data).to.deep.equal(expected); + }); }); describe("Parse", () => { diff --git a/test/integration/receiver_structured_0_3_test.ts b/test/integration/receiver_structured_0_3_test.ts index f83e11f9..d47220bf 100644 --- a/test/integration/receiver_structured_0_3_test.ts +++ b/test/integration/receiver_structured_0_3_test.ts @@ -3,6 +3,8 @@ import { expect } from "chai"; import { CloudEvent, ValidationError, Version } from "../../src"; import { StructuredHTTPReceiver } from "../../src/transport/http/structured_receiver"; +import { asBase64 } from "../../src/event/validation"; +import CONSTANTS from "../../src/constants"; const receiver = new StructuredHTTPReceiver(Version.V03); const type = "com.github.pull.create"; @@ -85,6 +87,26 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => { expect(receiver.parse.bind(receiver, event, attributes)).to.throw(ValidationError, "invalid payload"); }); + it("Succeeds when content-type is application/cloudevents+json and datacontentencoding is base64", () => { + const expected = { + whose: "ours", + }; + const bindata = Uint32Array.from(JSON.stringify(expected) as string, (c) => c.codePointAt(0) as number); + const payload = { + data: asBase64(bindata), + specversion: Version.V03, + source, + type, + datacontentencoding: CONSTANTS.ENCODING_BASE64, + }; + const attributes = { + "Content-Type": "application/cloudevents+json", + }; + + const event = receiver.parse(payload, attributes); + expect(event.data).to.deep.equal(expected); + }); + it("No error when all required stuff are in place", () => { // setup const payload = {