fix: throw on validation if extensions are improperly named (#420)

Also fixes the case where UPPERCASED extension names were silently changed
to lowercase and then set as undefined. Even though uppercased extension
names are invalid, we should still accept them in incoming messsages and
only throw when validating the event.

Fixes: https://github.com/cloudevents/sdk-javascript/issues/380

Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
Lance Ball 2021-05-24 14:53:07 -04:00 committed by GitHub
parent 80d987c1f6
commit 7f6b658858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 6 deletions

View File

@ -28,14 +28,21 @@ export function validateCloudEvent(event: CloudEventV03 | CloudEventV1): boolean
if (!isValidAgainstSchemaV1(event)) {
throw new ValidationError("invalid payload", isValidAgainstSchemaV1.errors);
}
return true;
} else if (event.specversion === Version.V03) {
if (!isValidAgainstSchemaV03(event)) {
throw new ValidationError("invalid payload", isValidAgainstSchemaV03.errors);
}
return checkDataContentEncoding(event);
checkDataContentEncoding(event);
} else {
return false;
}
return false;
// attribute names must all be lowercase
for (const key in event) {
if (key !== key.toLowerCase()) {
throw new ValidationError(`invalid attribute name: ${key}`);
}
}
return true;
}
function checkDataContentEncoding(event: CloudEventV03): boolean {

View File

@ -19,7 +19,7 @@ export class ValidationError extends TypeError {
// @ts-ignore
errors?.reduce(
(accum: string, err: Record<string, string>) =>
(accum as string).concat(`
accum.concat(`
${err instanceof Object ? JSON.stringify(err) : err}`),
message,
)

View File

@ -151,7 +151,7 @@ function getVersion(mode: Mode, headers: Headers, body: string | Record<string,
* @throws {ValidationError} of the event does not conform to the spec
*/
function parseBinary(message: Message, version: Version): CloudEvent {
const headers = message.headers;
const headers = { ...message.headers };
let body = message.body;
if (!headers) throw new ValidationError("headers is null or undefined");
@ -167,11 +167,12 @@ function parseBinary(message: Message, version: Version): CloudEvent {
const mappedParser: MappedParser = parserMap[header];
eventObj[mappedParser.name] = mappedParser.parser.parse(sanitizedHeaders[header]);
delete sanitizedHeaders[header];
delete headers[header];
}
}
// Every unprocessed header can be an extension
for (const header in sanitizedHeaders) {
for (const header in headers) {
if (header.startsWith(CONSTANTS.EXTENSIONS_PREFIX)) {
eventObj[header.substring(CONSTANTS.EXTENSIONS_PREFIX.length)] = headers[header];
}

View File

@ -64,6 +64,26 @@ describe("HTTP transport", () => {
expect(HTTP.isEvent(message)).to.be.true;
});
it("Respects extension attribute casing (even if against spec)", () => {
// Now create a message that is an event
const message = {
body: `{ "greeting": "hello" }`,
headers: {
[CONSTANTS.CE_HEADERS.ID]: "1234",
[CONSTANTS.CE_HEADERS.SOURCE]: "test",
[CONSTANTS.CE_HEADERS.TYPE]: "test.event",
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
"ce-LUNCH": "tacos",
},
};
expect(HTTP.isEvent(message)).to.be.true;
const event: CloudEvent = HTTP.toEvent(message);
expect(event.LUNCH).to.equal("tacos");
expect(function () {
event.validate();
}).to.throw("invalid attribute name: LUNCH");
});
it("Can detect CloudEvent binary Messages with weird versions", () => {
// Now create a message that is an event
const message = {