UNPKG

@advanced-rest-client/headers-parser-mixin

Version:

Headers parser Polymer Mixin to be implemented to elements that needs to parse headers data

304 lines (291 loc) 7.57 kB
/** * @typedef {Object} Header * @property {string} name * @property {string|string[]} value */ /* eslint-disable no-continue */ /* eslint-disable no-plusplus */ /* eslint-disable no-param-reassign */ /** * Filter array of headers and return not duplicated array of the same headers. * Duplicated headers should be appended to already found one using coma separator. * * @param {Array<Header>} input Headers list to filter. * @return {Array<Header>} An array of filtered headers. */ export function filter(input) { const _tmp = {}; input.forEach(header => { if (header.name in _tmp) { if (header.value) { _tmp[header.name] += `, ${header.value}`; } } else { _tmp[header.name] = header.value; } }); const result = []; Object.keys(_tmp).forEach(key => { result[result.length] = { name: key, value: _tmp[key], }; }); return result; } /** * Parse headers string to array of objects. * See `#toJSON` for more info. * * @param {String} headerString Headers string to process. * @return {Array<Header>} List of parsed headers */ function stringToJSON(headerString) { const result = []; if (!headerString) { return result; } if (typeof headerString !== 'string') { throw new Error('The headerString argument must be a String.'); } if (headerString.trim() === '') { return result; } const headers = headerString.split(/\n(?=[^ \t]+)/gim); for (let i = 0, len = headers.length; i < len; i++) { const line = headers[i].trim(); if (line === '') { continue; } const sepPosition = line.indexOf(':'); if (sepPosition === -1) { result[result.length] = { name: line, value: '', }; continue; } const name = line.substr(0, sepPosition); const value = line.substr(sepPosition + 1).trim(); const obj = { name, value, }; result.push(obj); } return result; } /** * Parse Headers object to array of objects. * See `#toJSON` for more info. * * @param {Headers|Object} input * @return {Array<Header>} */ function hedersToJSON(input) { const result = []; if (!input) { return result; } const headers = new Headers(input); const _tmp = {}; headers.forEach((value, name) => { if (_tmp[name]) { _tmp[name] += `, ${value}`; } else { _tmp[name] = value; } }); return Object.keys(_tmp).map(key => { let value = _tmp[key]; if (value && value.indexOf(',') !== -1) { value = value .split(',') .map(part => part.trim()) .join(', '); } return { name: key, value, }; }); } /** * Transforms a header model item to a string. * Array values are supported. * * @param {Header} header Object with name and value. * @return {string} Generated headers line */ export function itemToString(header) { const key = header.name; let value; if (Array.isArray(header.value)) { value = header.value.join(','); } else { value = header.value; } let result = ''; if (key && key.trim() !== '') { result += `${key}: `; if (value) { result += value; } } return result; } /** * Parse HTTP headers input from string to array of objects containing `name` and `value` * properties. * * @param {string|Headers|Header[]|Object} headers Raw HTTP headers input or Headers object * @return {Header[]} List of parsed headers */ export function toJSON(headers) { if (typeof headers === 'string') { return stringToJSON(headers); } return hedersToJSON(headers); } /** * Parse headers array to Raw HTTP headers string. * * @param {Array<Header>|String|Headers} input List of `Header`s * @return {string} A HTTP representation of the headers. */ export function toString(input) { if (typeof input === 'string') { return input; } let heeaders = input; if (!Array.isArray(heeaders)) { heeaders = toJSON(heeaders); } if (heeaders.length === 0) { return ''; } heeaders = filter(heeaders); return heeaders.map(header => itemToString(header)).join('\n'); } /** * finds and returns the value of the Content-Type value header. * * @param {Header[]|Headers|string} input Either HTTP headers string or list of headers. * @return {string|null} A content-type header value or null if not found */ export function contentType(input) { let headers = input; if (typeof headers !== 'string') { headers = toString(headers); } headers = headers.trim(); if (headers === '') { return null; } const re = /^content-type:\s?(.*)$/im; const match = headers.match(re); if (!match) { return null; } let ct = match[1].trim(); if (ct.indexOf('multipart') === -1) { const index = ct.indexOf('; '); if (index > 0) { ct = ct.substr(0, index); } } return ct; } /** * Replace value for given header in the headers list. * * @param {Headers|Header[]|string} input A headers to process. Can be string, * array of internall definition of headers or an instance of the Headers object. * @param {string} name Header name to be replaced. * @param {string} value Header value to be repleced. * @return {Headers|Header[]|string} Updated headers. */ export function replace(input, name, value) { let headers = /** @type Header[] */ (input); let origType = 'headers'; if (Array.isArray(headers)) { origType = 'array'; } else if (typeof headers === 'string') { origType = 'string'; } if (origType !== 'array') { headers = toJSON(headers); } const _name = name.toLowerCase(); let found = false; headers.forEach(header => { if (header.name.toLowerCase() === _name) { header.value = value; found = true; } }); if (!found) { headers.push({ name, value, }); } if (origType === 'array') { return headers; } if (origType === 'string') { return toString(headers); } const obj = {}; headers.forEach(header => { obj[header.name] = header.value; }); // @ts-ignore return new Headers(obj); } const ERROR_MESSAGES = { CONTENT_TYPE_MISSING: 'Content-Type header is not defined', HEADER_NAME_EMPTY: "Header name can't be empty", HEADER_NAME_WHITESPACES: 'Header name should not contain whitespaces', HEADER_VALUE_EMPTY: 'Header value should not be empty', }; /** * Get error message for given header string. * @param {Headers|Header[]|string} input A headers to check. * @param {boolean} [isPayload=false] Whether current request can have payload message. * @return {String|null} An error message or null if the headers are valid. */ export function getError(input, isPayload = false) { if (!input) { if (isPayload) { return ERROR_MESSAGES.CONTENT_TYPE_MISSING; } return null; } if (!(input instanceof Array)) { input = toJSON(input); } const msg = []; let hasContentType = false; for (let i = 0, len = input.length; i < len; i++) { const { name, value } = input[i]; if (name.toLowerCase() === 'content-type') { hasContentType = true; } if (!name || !name.trim()) { msg[msg.length] = ERROR_MESSAGES.HEADER_NAME_EMPTY; } else if (/\s/.test(name)) { msg[msg.length] = ERROR_MESSAGES.HEADER_NAME_WHITESPACES; } if (!value || !String(value).trim()) { msg[msg.length] = ERROR_MESSAGES.HEADER_VALUE_EMPTY; } } if (isPayload && !hasContentType) { msg[msg.length] = ERROR_MESSAGES.CONTENT_TYPE_MISSING; } if (msg.length > 0) { return msg.join('\n'); } return null; }