diff --git a/src/event/spec.ts b/src/event/spec.ts index 25def7df..bfab4f3d 100644 --- a/src/event/spec.ts +++ b/src/event/spec.ts @@ -18,10 +18,11 @@ export function validateCloudEvent(event: CloudEventV1): boolean { } else { return false; } - // attribute names must all be lowercase + // attribute names must all be [a-z|0-9] + const validation = /^[a-z0-9]+$/; for (const key in event) { - if (key !== key.toLowerCase()) { - throw new ValidationError(`invalid attribute name: ${key}`); + if (validation.test(key) === false && key !== "data_base64") { + throw new ValidationError(`invalid attribute name: "${key}"`); } } return true; diff --git a/test/integration/kafka_tests.ts b/test/integration/kafka_tests.ts index 8870d479..ac0e0c47 100644 --- a/test/integration/kafka_tests.ts +++ b/test/integration/kafka_tests.ts @@ -131,7 +131,7 @@ describe("Kafka transport", () => { expect(event.LUNCH).to.equal("tacos"); expect(function () { event.validate(); - }).to.throw("invalid attribute name: LUNCH"); + }).to.throw("invalid attribute name: \"LUNCH\""); }); it("Can detect CloudEvent binary Messages with weird versions", () => { diff --git a/test/integration/message_test.ts b/test/integration/message_test.ts index b1afad4b..46ab21f0 100644 --- a/test/integration/message_test.ts +++ b/test/integration/message_test.ts @@ -41,7 +41,21 @@ const imageData = new Uint32Array(fs.readFileSync(path.join(process.cwd(), "test const image_base64 = asBase64(imageData); describe("HTTP transport", () => { - + it("validates extension attribute names for incoming messages", () => { + // create a new Message + const msg: Message = { + headers: { + "ce-id": "213", + "ce-source": "test", + "ce-type": "test", + "ce-bad-extension": "value" + }, + body: undefined + }; + const evt = HTTP.toEvent(msg) as CloudEvent; + expect(() => evt.validate()).to.throw(TypeError); + }); + it("Includes extensions in binary mode when type is 'boolean' with a false value", () => { const evt = new CloudEvent({ source: "test", type: "test", extboolean: false }); expect(evt.hasOwnProperty("extboolean")).to.equal(true); @@ -129,7 +143,7 @@ describe("HTTP transport", () => { expect(event.LUNCH).to.equal("tacos"); expect(function () { event.validate(); - }).to.throw("invalid attribute name: LUNCH"); + }).to.throw("invalid attribute name: \"LUNCH\""); }); it("Can detect CloudEvent binary Messages with weird versions", () => { diff --git a/test/integration/mqtt_tests.ts b/test/integration/mqtt_tests.ts index 411a9cd7..69b77e52 100644 --- a/test/integration/mqtt_tests.ts +++ b/test/integration/mqtt_tests.ts @@ -134,7 +134,7 @@ describe("MQTT transport", () => { expect(event.LUNCH).to.equal("tacos"); expect(function () { event.validate(); - }).to.throw("invalid attribute name: LUNCH"); + }).to.throw("invalid attribute name: \"LUNCH\""); }); it("Can detect CloudEvent binary Messages with weird versions", () => { diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index 7bd22483..9759d8fc 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -99,6 +99,16 @@ describe("CloudEvents Spec v1.0", () => { it("should be ok when the type is an string converted from an object", () => { expect(cloudevent.cloneWith({ objectextension: JSON.stringify({ some: "object" }) }).validate()).to.equal(true); }); + + it("should only allow a-z|0-9 in the attribute names", () => { + const testCases = [ + "an extension", "an_extension", "an-extension", "an.extension", "an+extension" + ]; + testCases.forEach((testCase) => { + const evt = cloudevent.cloneWith({ [testCase]: "a value"}, false); + expect(() => evt.validate()).to.throw(ValidationError); + }); + }); }); describe("The Constraints check", () => {