|
1 | 1 | import { PassThroughParser, DateParser, MappedParser } from "../../parsers";
|
| 2 | +import { ValidationError, CloudEvent } from "../.."; |
| 3 | +import { Headers } from "../"; |
| 4 | +import { Version } from "../../event/cloudevent"; |
2 | 5 | import CONSTANTS from "../../constants";
|
3 | 6 |
|
| 7 | +export const allowedContentTypes = [CONSTANTS.DEFAULT_CONTENT_TYPE, CONSTANTS.MIME_JSON, CONSTANTS.MIME_OCTET_STREAM]; |
| 8 | +export const requiredHeaders = [ |
| 9 | + CONSTANTS.CE_HEADERS.ID, |
| 10 | + CONSTANTS.CE_HEADERS.SOURCE, |
| 11 | + CONSTANTS.CE_HEADERS.TYPE, |
| 12 | + CONSTANTS.CE_HEADERS.SPEC_VERSION, |
| 13 | +]; |
| 14 | + |
| 15 | +/** |
| 16 | + * Validates cloud event headers and their values |
| 17 | + * @param {Headers} headers event transport headers for validation |
| 18 | + * @throws {ValidationError} if the headers are invalid |
| 19 | + * @return {boolean} true if headers are valid |
| 20 | + */ |
| 21 | +export function validate(headers: Headers): Headers { |
| 22 | + const sanitizedHeaders = sanitize(headers); |
| 23 | + |
| 24 | + // if content-type exists, be sure it's an allowed type |
| 25 | + const contentTypeHeader = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]; |
| 26 | + const noContentType = !allowedContentTypes.includes(contentTypeHeader); |
| 27 | + if (contentTypeHeader && noContentType) { |
| 28 | + throw new ValidationError("invalid content type", [sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]]); |
| 29 | + } |
| 30 | + |
| 31 | + requiredHeaders |
| 32 | + .filter((required: string) => !sanitizedHeaders[required]) |
| 33 | + .forEach((required: string) => { |
| 34 | + throw new ValidationError(`header '${required}' not found`); |
| 35 | + }); |
| 36 | + |
| 37 | + if (!sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]) { |
| 38 | + sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE] = CONSTANTS.MIME_JSON; |
| 39 | + } |
| 40 | + |
| 41 | + return sanitizedHeaders; |
| 42 | +} |
| 43 | + |
| 44 | +/** |
| 45 | + * Returns the HTTP headers that will be sent for this event when the HTTP transmission |
| 46 | + * mode is "binary". Events sent over HTTP in structured mode only have a single CE header |
| 47 | + * and that is "ce-id", corresponding to the event ID. |
| 48 | + * @param {CloudEvent} event a CloudEvent |
| 49 | + * @returns {Object} the headers that will be sent for the event |
| 50 | + */ |
| 51 | +export function headersFor(event: CloudEvent): Headers { |
| 52 | + const headers: Headers = {}; |
| 53 | + let headerMap: Readonly<{ [key: string]: MappedParser }>; |
| 54 | + if (event.specversion === Version.V1) { |
| 55 | + headerMap = v1headerMap; |
| 56 | + } else { |
| 57 | + headerMap = v03headerMap; |
| 58 | + } |
| 59 | + |
| 60 | + // iterate over the event properties - generate a header for each |
| 61 | + Object.getOwnPropertyNames(event).forEach((property) => { |
| 62 | + const value = event[property]; |
| 63 | + if (value) { |
| 64 | + const map: MappedParser | undefined = headerMap[property] as MappedParser; |
| 65 | + if (map) { |
| 66 | + headers[map.name] = map.parser.parse(value as string) as string; |
| 67 | + } else if (property !== CONSTANTS.DATA_ATTRIBUTE && property !== `${CONSTANTS.DATA_ATTRIBUTE}_base64`) { |
| 68 | + headers[`${CONSTANTS.EXTENSIONS_PREFIX}${property}`] = value as string; |
| 69 | + } |
| 70 | + } |
| 71 | + }); |
| 72 | + // Treat time specially, since it's handled with getters and setters in CloudEvent |
| 73 | + if (event.time) { |
| 74 | + headers[CONSTANTS.CE_HEADERS.TIME] = event.time as string; |
| 75 | + } |
| 76 | + return headers; |
| 77 | +} |
| 78 | + |
| 79 | +/** |
| 80 | + * Sanitizes incoming headers by lowercasing them and potentially removing |
| 81 | + * encoding from the content-type header. |
| 82 | + * @param {Headers} headers HTTP headers as key/value pairs |
| 83 | + * @returns {Headers} the sanitized headers |
| 84 | + */ |
| 85 | +export function sanitize(headers: Headers): Headers { |
| 86 | + const sanitized: Headers = {}; |
| 87 | + |
| 88 | + Array.from(Object.keys(headers)) |
| 89 | + .filter((header) => Object.hasOwnProperty.call(headers, header)) |
| 90 | + .forEach((header) => (sanitized[header.toLowerCase()] = headers[header])); |
| 91 | + |
| 92 | + return sanitized; |
| 93 | +} |
| 94 | + |
4 | 95 | function parser(name: string, parser = new PassThroughParser()): MappedParser {
|
5 | 96 | return { name: name, parser: parser };
|
6 | 97 | }
|
|
0 commit comments