Spaces:
Sleeping
Sleeping
/*! | |
* content-type | |
* Copyright(c) 2015 Douglas Christopher Wilson | |
* MIT Licensed | |
*/ | |
/** | |
* RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 | |
* | |
* parameter = token "=" ( token / quoted-string ) | |
* token = 1*tchar | |
* tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" | |
* / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" | |
* / DIGIT / ALPHA | |
* ; any VCHAR, except delimiters | |
* quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE | |
* qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text | |
* obs-text = %x80-FF | |
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) | |
*/ | |
var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g // eslint-disable-line no-control-regex | |
var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex | |
var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/ | |
/** | |
* RegExp to match quoted-pair in RFC 7230 sec 3.2.6 | |
* | |
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) | |
* obs-text = %x80-FF | |
*/ | |
var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex | |
/** | |
* RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6 | |
*/ | |
var QUOTE_REGEXP = /([\\"])/g | |
/** | |
* RegExp to match type in RFC 7231 sec 3.1.1.1 | |
* | |
* media-type = type "/" subtype | |
* type = token | |
* subtype = token | |
*/ | |
var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/ | |
/** | |
* Module exports. | |
* @public | |
*/ | |
exports.format = format | |
exports.parse = parse | |
/** | |
* Format object to media type. | |
* | |
* @param {object} obj | |
* @return {string} | |
* @public | |
*/ | |
function format (obj) { | |
if (!obj || typeof obj !== 'object') { | |
throw new TypeError('argument obj is required') | |
} | |
var parameters = obj.parameters | |
var type = obj.type | |
if (!type || !TYPE_REGEXP.test(type)) { | |
throw new TypeError('invalid type') | |
} | |
var string = type | |
// append parameters | |
if (parameters && typeof parameters === 'object') { | |
var param | |
var params = Object.keys(parameters).sort() | |
for (var i = 0; i < params.length; i++) { | |
param = params[i] | |
if (!TOKEN_REGEXP.test(param)) { | |
throw new TypeError('invalid parameter name') | |
} | |
string += '; ' + param + '=' + qstring(parameters[param]) | |
} | |
} | |
return string | |
} | |
/** | |
* Parse media type to object. | |
* | |
* @param {string|object} string | |
* @return {Object} | |
* @public | |
*/ | |
function parse (string) { | |
if (!string) { | |
throw new TypeError('argument string is required') | |
} | |
// support req/res-like objects as argument | |
var header = typeof string === 'object' | |
? getcontenttype(string) | |
: string | |
if (typeof header !== 'string') { | |
throw new TypeError('argument string is required to be a string') | |
} | |
var index = header.indexOf(';') | |
var type = index !== -1 | |
? header.slice(0, index).trim() | |
: header.trim() | |
if (!TYPE_REGEXP.test(type)) { | |
throw new TypeError('invalid media type') | |
} | |
var obj = new ContentType(type.toLowerCase()) | |
// parse parameters | |
if (index !== -1) { | |
var key | |
var match | |
var value | |
PARAM_REGEXP.lastIndex = index | |
while ((match = PARAM_REGEXP.exec(header))) { | |
if (match.index !== index) { | |
throw new TypeError('invalid parameter format') | |
} | |
index += match[0].length | |
key = match[1].toLowerCase() | |
value = match[2] | |
if (value.charCodeAt(0) === 0x22 /* " */) { | |
// remove quotes | |
value = value.slice(1, -1) | |
// remove escapes | |
if (value.indexOf('\\') !== -1) { | |
value = value.replace(QESC_REGEXP, '$1') | |
} | |
} | |
obj.parameters[key] = value | |
} | |
if (index !== header.length) { | |
throw new TypeError('invalid parameter format') | |
} | |
} | |
return obj | |
} | |
/** | |
* Get content-type from req/res objects. | |
* | |
* @param {object} | |
* @return {Object} | |
* @private | |
*/ | |
function getcontenttype (obj) { | |
var header | |
if (typeof obj.getHeader === 'function') { | |
// res-like | |
header = obj.getHeader('content-type') | |
} else if (typeof obj.headers === 'object') { | |
// req-like | |
header = obj.headers && obj.headers['content-type'] | |
} | |
if (typeof header !== 'string') { | |
throw new TypeError('content-type header is missing from object') | |
} | |
return header | |
} | |
/** | |
* Quote a string if necessary. | |
* | |
* @param {string} val | |
* @return {string} | |
* @private | |
*/ | |
function qstring (val) { | |
var str = String(val) | |
// no need to quote tokens | |
if (TOKEN_REGEXP.test(str)) { | |
return str | |
} | |
if (str.length > 0 && !TEXT_REGEXP.test(str)) { | |
throw new TypeError('invalid parameter value') | |
} | |
return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"' | |
} | |
/** | |
* Class to represent a content type. | |
* @private | |
*/ | |
function ContentType (type) { | |
this.parameters = Object.create(null) | |
this.type = type | |
} | |