fix: ensure that data encoded as base64 is parsed as an object (#285)

The 0.3 specification states that `datacontentencoding` may be set to base64.
If an incoming event arrives over HTTP with this value set, and the content type
is either application/json or application/cloudevents+json, then ensure that
the data is decoded and parsed.

Fixes: https://github.com/cloudevents/sdk-javascript/issues/284
See: https://github.com/cloudevents/sdk-javascript/pull/282


Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
Lance Ball 2020-07-30 08:39:47 -04:00 committed by GitHub
parent e219a30708
commit ed9ea956d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 1 deletions

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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", () => {

View File

@ -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 = {