Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/transport/http/binary_receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion src/transport/http/structured_receiver.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
}
}

Expand Down
21 changes: 21 additions & 0 deletions test/integration/receiver_binary_03_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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", () => {
Expand Down
22 changes: 22 additions & 0 deletions test/integration/receiver_structured_0_3_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 = {
Expand Down