From 14468980f7a79da836bd3ee8304a8a5710a206c1 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Tue, 6 Oct 2020 08:20:54 -0400 Subject: [PATCH] fix: do not alter an event's data attribute (#344) * fix: do not alter an event's data attribute When setting an event's data attribute we were trying to be really clever and this is problematic. Instead, keep the data attribute unchanged. Per the 1.0 specification, the data attribute is still inspected to determine if it is binary, and if so, a data_base64 attribute is added with the contents of the data property encoded as base64. Fixes: https://github.com/cloudevents/sdk-javascript/issues/343 Signed-off-by: Lance Ball --- src/event/cloudevent.ts | 21 +---- src/message/http/index.ts | 49 ++++++------ src/message/index.ts | 2 +- src/transport/receiver.ts | 2 +- test/integration/ce.png | Bin 0 -> 40750 bytes test/integration/cloud_event_test.ts | 16 ++++ test/integration/emitter_factory_test.ts | 4 +- test/integration/message_test.ts | 98 +++++++++++++++++------ test/integration/spec_03_tests.ts | 6 -- test/integration/spec_1_tests.ts | 5 -- 10 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 test/integration/ce.png diff --git a/src/event/cloudevent.ts b/src/event/cloudevent.ts index c9ade6f..e6322f8 100644 --- a/src/event/cloudevent.ts +++ b/src/event/cloudevent.ts @@ -10,8 +10,6 @@ import { } from "./interfaces"; import { validateCloudEvent } from "./spec"; import { ValidationError, isBinary, asBase64, isValidType } from "./validation"; -import CONSTANTS from "../constants"; -import { isString } from "util"; /** * An enum representing the CloudEvent specification version @@ -92,7 +90,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 { this.schemaurl = properties.schemaurl as string; delete properties.schemaurl; - this._setData(properties.data); + this.data = properties.data; delete properties.data; // sanity checking @@ -125,25 +123,11 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 { } get data(): unknown { - if ( - this.datacontenttype === CONSTANTS.MIME_JSON && - !(this.datacontentencoding === CONSTANTS.ENCODING_BASE64) && - isString(this.#_data) - ) { - return JSON.parse(this.#_data as string); - } else if (isBinary(this.#_data)) { - return asBase64(this.#_data as Uint32Array); - } return this.#_data; } set data(value: unknown) { - this._setData(value); - } - - private _setData(value: unknown): void { if (isBinary(value)) { - this.#_data = value; this.data_base64 = asBase64(value as Uint32Array); } this.#_data = value; @@ -158,7 +142,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 { toJSON(): Record { const event = { ...this }; event.time = new Date(this.time as string).toISOString(); - event.data = this.data; + event.data = !isBinary(this.data) ? this.data : undefined; return event; } @@ -175,7 +159,6 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 { try { return validateCloudEvent(this); } catch (e) { - console.error(e.errors); if (e instanceof ValidationError) { throw e; } else { diff --git a/src/message/http/index.ts b/src/message/http/index.ts index 97c005b..bd90eb5 100644 --- a/src/message/http/index.ts +++ b/src/message/http/index.ts @@ -2,16 +2,17 @@ import { CloudEvent, CloudEventV03, CloudEventV1, CONSTANTS, Mode, Version } fro import { Message, Headers } from ".."; import { headersFor, sanitize, v03structuredParsers, v1binaryParsers, v1structuredParsers } from "./headers"; -import { asData, isBase64, isString, isStringOrObjectOrThrow, ValidationError } from "../../event/validation"; -import { Base64Parser, JSONParser, MappedParser, Parser, parserByContentType } from "../../parsers"; +import { isStringOrObjectOrThrow, ValidationError } from "../../event/validation"; +import { JSONParser, MappedParser, Parser, parserByContentType } from "../../parsers"; // implements Serializer export function binary(event: CloudEvent): Message { const contentType: Headers = { [CONSTANTS.HEADER_CONTENT_TYPE]: CONSTANTS.DEFAULT_CONTENT_TYPE }; const headers: Headers = { ...contentType, ...headersFor(event) }; - let body = asData(event.data, event.datacontenttype as string); - if (typeof body === "object") { - body = JSON.stringify(body); + let body = event.data; + if (typeof event.data === "object" && !(event.data instanceof Uint32Array)) { + // we'll stringify objects, but not binary data + body = JSON.stringify(event.data); } return { headers, @@ -21,6 +22,10 @@ export function binary(event: CloudEvent): Message { // implements Serializer export function structured(event: CloudEvent): Message { + if (event.data_base64) { + // The event's data is binary - delete it + event = event.cloneWith({ data: undefined }); + } return { headers: { [CONSTANTS.HEADER_CONTENT_TYPE]: CONSTANTS.DEFAULT_CE_CONTENT_TYPE, @@ -89,7 +94,7 @@ function getMode(headers: Headers): Mode { * @param {Record} body the HTTP request body * @returns {Version} the CloudEvent specification version */ -function getVersion(mode: Mode, headers: Headers, body: string | Record) { +function getVersion(mode: Mode, headers: Headers, body: string | Record | unknown) { if (mode === Mode.BINARY) { // Check the headers for the version const versionHeader = headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]; @@ -129,8 +134,6 @@ function parseBinary(message: Message, version: Version): CloudEvent { throw new ValidationError(`invalid spec version ${headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]}`); } - body = isString(body) && isBase64(body) ? Buffer.from(body as string, "base64").toString() : body; - // Clone and low case all headers names const sanitizedHeaders = sanitize(headers); @@ -145,22 +148,18 @@ function parseBinary(message: Message, version: Version): CloudEvent { } } - let parsedPayload; - - if (body) { - const parser = parserByContentType[eventObj.datacontenttype as string]; - if (!parser) { - throw new ValidationError(`no parser found for content type ${eventObj.datacontenttype}`); - } - parsedPayload = parser.parse(body); - } - // Every unprocessed header can be an extension for (const header in sanitizedHeaders) { if (header.startsWith(CONSTANTS.EXTENSIONS_PREFIX)) { eventObj[header.substring(CONSTANTS.EXTENSIONS_PREFIX.length)] = headers[header]; } } + + const parser = parserByContentType[eventObj.datacontenttype as string]; + if (parser && body) { + body = parser.parse(body as string); + } + // 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. @@ -168,7 +167,7 @@ function parseBinary(message: Message, version: Version): CloudEvent { delete eventObj.datacontentencoding; } - return new CloudEvent({ ...eventObj, data: parsedPayload } as CloudEventV1 | CloudEventV03, false); + return new CloudEvent({ ...eventObj, data: body } as CloudEventV1 | CloudEventV03, false); } /** @@ -201,7 +200,7 @@ function parseStructured(message: Message, version: Version): CloudEvent { const contentType = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]; const parser: Parser = contentType ? parserByContentType[contentType] : new JSONParser(); if (!parser) throw new ValidationError(`invalid content type ${sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]}`); - const incoming = { ...(parser.parse(payload) as Record) }; + const incoming = { ...(parser.parse(payload as string) as Record) }; const eventObj: { [key: string]: unknown } = {}; const parserMap: Record = version === Version.V1 ? v1structuredParsers : v03structuredParsers; @@ -220,10 +219,12 @@ function parseStructured(message: Message, version: Version): CloudEvent { eventObj[key] = incoming[key]; } - // ensure data content is correctly decoded - if (eventObj.data_base64) { - const parser = new Base64Parser(); - eventObj.data = JSON.parse(parser.parse(eventObj.data_base64 as string)); + // data_base64 is a property that only exists on V1 events. For V03 events, + // there will be a .datacontentencoding property, and the .data property + // itself will be encoded as base64 + if (eventObj.data_base64 || eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64) { + const data = eventObj.data_base64 || eventObj.data; + eventObj.data = new Uint32Array(Buffer.from(data as string, "base64")); delete eventObj.data_base64; delete eventObj.datacontentencoding; } diff --git a/src/message/index.ts b/src/message/index.ts index 14ed270..6a1343c 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -28,7 +28,7 @@ export interface Headers { */ export interface Message { headers: Headers; - body: string; + body: string | unknown; } /** diff --git a/src/transport/receiver.ts b/src/transport/receiver.ts index f15e8f4..3d7b575 100644 --- a/src/transport/receiver.ts +++ b/src/transport/receiver.ts @@ -17,7 +17,7 @@ export const Receiver = { */ accept(headers: Headers, body: string | Record | undefined | null): CloudEvent { const cleanHeaders: Headers = sanitize(headers); - const cleanBody = body ? (typeof body === "object" ? JSON.stringify(body) : body) : ""; + const cleanBody = body ? (typeof body === "object" ? JSON.stringify(body) : body) : undefined; const message: Message = { headers: cleanHeaders, body: cleanBody, diff --git a/test/integration/ce.png b/test/integration/ce.png new file mode 100644 index 0000000000000000000000000000000000000000..67b50a84c576eef40ded5eb4eb46f0f98d574e83 GIT binary patch literal 40750 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 CqZX3@ literal 0 HcmV?d00001 diff --git a/test/integration/cloud_event_test.ts b/test/integration/cloud_event_test.ts index 2b948f2..86f0c4c 100644 --- a/test/integration/cloud_event_test.ts +++ b/test/integration/cloud_event_test.ts @@ -1,6 +1,10 @@ +import path from "path"; +import fs from "fs"; + import { expect } from "chai"; import { CloudEvent, ValidationError, Version } from "../../src"; import { CloudEventV03, CloudEventV1 } from "../../src/event/interfaces"; +import { asBase64 } from "../../src/event/validation"; const type = "org.cncf.cloudevents.example"; const source = "http://unit.test"; @@ -14,6 +18,9 @@ const fixture: CloudEventV1 = { data: `"some data"`, }; +const imageData = new Uint32Array(fs.readFileSync(path.join(process.cwd(), "test", "integration", "ce.png"))); +const image_base64 = asBase64(imageData); + describe("A CloudEvent", () => { it("Can be constructed with a typed Message", () => { const ce = new CloudEvent(fixture); @@ -151,6 +158,15 @@ describe("A 1.0 CloudEvent", () => { expect(ce.data).to.be.true; }); + it("can be constructed with binary data", () => { + const ce = new CloudEvent({ + ...fixture, + data: imageData, + }); + expect(ce.data).to.equal(imageData); + expect(ce.data_base64).to.equal(image_base64); + }); + it("can be constructed with extensions", () => { const extensions = { extensionkey: "extension-value", diff --git a/test/integration/emitter_factory_test.ts b/test/integration/emitter_factory_test.ts index 4f6cba3..5e2a103 100644 --- a/test/integration/emitter_factory_test.ts +++ b/test/integration/emitter_factory_test.ts @@ -50,12 +50,12 @@ function superagentEmitter(message: Message, options?: Options): Promise { return Promise.resolve( - got.post(sink, { headers: message.headers, body: message.body, ...((options as unknown) as Options) }), + got.post(sink, { headers: message.headers, body: message.body as string, ...((options as unknown) as Options) }), ); } diff --git a/test/integration/message_test.ts b/test/integration/message_test.ts index 6495a80..7e1caf5 100644 --- a/test/integration/message_test.ts +++ b/test/integration/message_test.ts @@ -1,3 +1,6 @@ +import path from "path"; +import fs from "fs"; + import { expect } from "chai"; import { CloudEvent, CONSTANTS, Version } from "../../src"; import { asBase64 } from "../../src/event/validation"; @@ -16,7 +19,6 @@ const data = { // Attributes for v03 events const schemaurl = "https://cloudevents.io/schema.json"; -const datacontentencoding = "base64"; const ext1Name = "extension1"; const ext1Value = "foobar"; @@ -27,6 +29,11 @@ const ext2Value = "acme"; const dataBinary = Uint32Array.from(JSON.stringify(data), (c) => c.codePointAt(0) as number); const data_base64 = asBase64(dataBinary); +// Since the above is a special case (string as binary), let's test +// with a real binary file one is likely to encounter in the wild +const imageData = new Uint32Array(fs.readFileSync(path.join(process.cwd(), "test", "integration", "ce.png"))); +const image_base64 = asBase64(imageData); + describe("HTTP transport", () => { it("Can detect invalid CloudEvent Messages", () => { // Create a message that is not an actual event @@ -45,6 +52,7 @@ describe("HTTP transport", () => { new CloudEvent({ source: "/message-test", type: "example", + data, }), ); expect(HTTP.isEvent(message)).to.be.true; @@ -102,7 +110,7 @@ describe("HTTP transport", () => { it("Binary Messages can be created from a CloudEvent", () => { const message: Message = HTTP.binary(fixture); - expect(JSON.parse(message.body)).to.deep.equal(data); + expect(message.body).to.equal(JSON.stringify(data)); // validate all headers expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1); @@ -120,7 +128,7 @@ describe("HTTP transport", () => { const message: Message = HTTP.structured(fixture); expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); // Parse the message body as JSON, then validate the attributes - const body = JSON.parse(message.body); + const body = JSON.parse(message.body as string); expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); @@ -144,20 +152,47 @@ describe("HTTP transport", () => { expect(event).to.deep.equal(fixture); }); - it("Supports Base-64 encoded data in structured messages", () => { - const event = fixture.cloneWith({ data: dataBinary }); - expect(event.data_base64).to.equal(data_base64); + it("Converts binary data to base64 when serializing structured messages", () => { + const event = fixture.cloneWith({ data: imageData, datacontenttype: "image/png" }); + expect(event.data).to.equal(imageData); const message = HTTP.structured(event); - const eventDeserialized = HTTP.toEvent(message); - expect(eventDeserialized.data).to.deep.equal({ foo: "bar" }); + const messageBody = JSON.parse(message.body as string); + expect(messageBody.data_base64).to.equal(image_base64); }); - it("Supports Base-64 encoded data in binary messages", () => { - const event = fixture.cloneWith({ data: dataBinary }); - expect(event.data_base64).to.equal(data_base64); - const message = HTTP.binary(event); + it("Converts base64 encoded data to binary when deserializing structured messages", () => { + const message = HTTP.structured(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); const eventDeserialized = HTTP.toEvent(message); - expect(eventDeserialized.data).to.deep.equal({ foo: "bar" }); + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Does not parse binary data from structured messages with content type application/json", () => { + const message = HTTP.structured(fixture.cloneWith({ data: dataBinary })); + const eventDeserialized = HTTP.toEvent(message); + expect(eventDeserialized.data).to.deep.equal(dataBinary); + expect(eventDeserialized.data_base64).to.equal(data_base64); + }); + + it("Converts base64 encoded data to binary when deserializing binary messages", () => { + const message = HTTP.binary(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); + const eventDeserialized = HTTP.toEvent(message); + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Keeps binary data binary when serializing binary messages", () => { + const event = fixture.cloneWith({ data: dataBinary }); + expect(event.data).to.equal(dataBinary); + const message = HTTP.binary(event); + expect(message.body).to.equal(dataBinary); + }); + + it("Does not parse binary data from binary messages with content type application/json", () => { + const message = HTTP.binary(fixture.cloneWith({ data: dataBinary })); + const eventDeserialized = HTTP.toEvent(message); + expect(eventDeserialized.data).to.deep.equal(dataBinary); + expect(eventDeserialized.data_base64).to.equal(data_base64); }); }); @@ -196,7 +231,7 @@ describe("HTTP transport", () => { const message: Message = HTTP.structured(fixture); expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); // Parse the message body as JSON, then validate the attributes - const body = JSON.parse(message.body); + const body = JSON.parse(message.body as string); expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); @@ -220,20 +255,35 @@ describe("HTTP transport", () => { expect(event).to.deep.equal(fixture); }); - it("Supports Base-64 encoded data in structured messages", () => { - const event = fixture.cloneWith({ data: dataBinary, datacontentencoding }); - expect(event.data_base64).to.equal(data_base64); + it("Converts binary data to base64 when serializing structured messages", () => { + const event = fixture.cloneWith({ data: imageData, datacontenttype: "image/png" }); + expect(event.data).to.equal(imageData); const message = HTTP.structured(event); - const eventDeserialized = HTTP.toEvent(message); - expect(eventDeserialized.data).to.deep.equal({ foo: "bar" }); + const messageBody = JSON.parse(message.body as string); + expect(messageBody.data_base64).to.equal(image_base64); }); - it("Supports Base-64 encoded data in binary messages", () => { - const event = fixture.cloneWith({ data: dataBinary, datacontentencoding }); - expect(event.data_base64).to.equal(data_base64); - const message = HTTP.binary(event); + it("Converts base64 encoded data to binary when deserializing structured messages", () => { + // Creating an event with binary data automatically produces base64 encoded data + // which is then set as the 'data' attribute on the message body + const message = HTTP.structured(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); const eventDeserialized = HTTP.toEvent(message); - expect(eventDeserialized.data).to.deep.equal({ foo: "bar" }); + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Converts base64 encoded data to binary when deserializing binary messages", () => { + const message = HTTP.binary(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); + const eventDeserialized = HTTP.toEvent(message); + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Keeps binary data binary when serializing binary messages", () => { + const event = fixture.cloneWith({ data: dataBinary }); + expect(event.data).to.equal(dataBinary); + const message = HTTP.binary(event); + expect(message.body).to.equal(dataBinary); }); }); }); diff --git a/test/integration/spec_03_tests.ts b/test/integration/spec_03_tests.ts index 84252a4..27cb3d8 100644 --- a/test/integration/spec_03_tests.ts +++ b/test/integration/spec_03_tests.ts @@ -168,12 +168,6 @@ describe("CloudEvents Spec v0.3", () => { expect(typeof cloudevent.data).to.equal("string"); }); - - it("should convert data with stringified json to a json object", () => { - cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON }); - cloudevent.data = JSON.stringify(data); - expect(cloudevent.data).to.deep.equal(data); - }); }); describe("'subject'", () => { diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index 9329dbd..6da9774 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -168,11 +168,6 @@ describe("CloudEvents Spec v1.0", () => { cloudevent = cloudevent.cloneWith({ datacontenttype: dct }); }); - it("should convert data with stringified json to a json object", () => { - cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON, data: JSON.stringify(data) }); - expect(cloudevent.data).to.deep.equal(data); - }); - it("should be ok when type is 'Uint32Array' for 'Binary'", () => { const dataString = ")(*~^my data for ce#@#$%";