Spaces:
Sleeping
Sleeping
/*! | |
* media-typer | |
* Copyright(c) 2014 Douglas Christopher Wilson | |
* MIT Licensed | |
*/ | |
/** | |
* RegExp to match *( ";" parameter ) in RFC 2616 sec 3.7 | |
* | |
* parameter = token "=" ( token | quoted-string ) | |
* token = 1*<any CHAR except CTLs or separators> | |
* separators = "(" | ")" | "<" | ">" | "@" | |
* | "," | ";" | ":" | "\" | <"> | |
* | "/" | "[" | "]" | "?" | "=" | |
* | "{" | "}" | SP | HT | |
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) | |
* qdtext = <any TEXT except <">> | |
* quoted-pair = "\" CHAR | |
* CHAR = <any US-ASCII character (octets 0 - 127)> | |
* TEXT = <any OCTET except CTLs, but including LWS> | |
* LWS = [CRLF] 1*( SP | HT ) | |
* CRLF = CR LF | |
* CR = <US-ASCII CR, carriage return (13)> | |
* LF = <US-ASCII LF, linefeed (10)> | |
* SP = <US-ASCII SP, space (32)> | |
* SHT = <US-ASCII HT, horizontal-tab (9)> | |
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> | |
* OCTET = <any 8-bit sequence of data> | |
*/ | |
var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u0020-\u007e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g; | |
var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/ | |
var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/ | |
/** | |
* RegExp to match quoted-pair in RFC 2616 | |
* | |
* quoted-pair = "\" CHAR | |
* CHAR = <any US-ASCII character (octets 0 - 127)> | |
*/ | |
var qescRegExp = /\\([\u0000-\u007f])/g; | |
/** | |
* RegExp to match chars that must be quoted-pair in RFC 2616 | |
*/ | |
var quoteRegExp = /([\\"])/g; | |
/** | |
* RegExp to match type in RFC 6838 | |
* | |
* type-name = restricted-name | |
* subtype-name = restricted-name | |
* restricted-name = restricted-name-first *126restricted-name-chars | |
* restricted-name-first = ALPHA / DIGIT | |
* restricted-name-chars = ALPHA / DIGIT / "!" / "#" / | |
* "$" / "&" / "-" / "^" / "_" | |
* restricted-name-chars =/ "." ; Characters before first dot always | |
* ; specify a facet name | |
* restricted-name-chars =/ "+" ; Characters after last plus always | |
* ; specify a structured syntax suffix | |
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z | |
* DIGIT = %x30-39 ; 0-9 | |
*/ | |
var subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/ | |
var typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/ | |
var typeRegExp = /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/; | |
/** | |
* Module exports. | |
*/ | |
exports.format = format | |
exports.parse = parse | |
/** | |
* Format object to media type. | |
* | |
* @param {object} obj | |
* @return {string} | |
* @api public | |
*/ | |
function format(obj) { | |
if (!obj || typeof obj !== 'object') { | |
throw new TypeError('argument obj is required') | |
} | |
var parameters = obj.parameters | |
var subtype = obj.subtype | |
var suffix = obj.suffix | |
var type = obj.type | |
if (!type || !typeNameRegExp.test(type)) { | |
throw new TypeError('invalid type') | |
} | |
if (!subtype || !subtypeNameRegExp.test(subtype)) { | |
throw new TypeError('invalid subtype') | |
} | |
// format as type/subtype | |
var string = type + '/' + subtype | |
// append +suffix | |
if (suffix) { | |
if (!typeNameRegExp.test(suffix)) { | |
throw new TypeError('invalid suffix') | |
} | |
string += '+' + suffix | |
} | |
// 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 (!tokenRegExp.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} | |
* @api public | |
*/ | |
function parse(string) { | |
if (!string) { | |
throw new TypeError('argument string is required') | |
} | |
// support req/res-like objects as argument | |
if (typeof string === 'object') { | |
string = getcontenttype(string) | |
} | |
if (typeof string !== 'string') { | |
throw new TypeError('argument string is required to be a string') | |
} | |
var index = string.indexOf(';') | |
var type = index !== -1 | |
? string.substr(0, index) | |
: string | |
var key | |
var match | |
var obj = splitType(type) | |
var params = {} | |
var value | |
paramRegExp.lastIndex = index | |
while (match = paramRegExp.exec(string)) { | |
if (match.index !== index) { | |
throw new TypeError('invalid parameter format') | |
} | |
index += match[0].length | |
key = match[1].toLowerCase() | |
value = match[2] | |
if (value[0] === '"') { | |
// remove quotes and escapes | |
value = value | |
.substr(1, value.length - 2) | |
.replace(qescRegExp, '$1') | |
} | |
params[key] = value | |
} | |
if (index !== -1 && index !== string.length) { | |
throw new TypeError('invalid parameter format') | |
} | |
obj.parameters = params | |
return obj | |
} | |
/** | |
* Get content-type from req/res objects. | |
* | |
* @param {object} | |
* @return {Object} | |
* @api private | |
*/ | |
function getcontenttype(obj) { | |
if (typeof obj.getHeader === 'function') { | |
// res-like | |
return obj.getHeader('content-type') | |
} | |
if (typeof obj.headers === 'object') { | |
// req-like | |
return obj.headers && obj.headers['content-type'] | |
} | |
} | |
/** | |
* Quote a string if necessary. | |
* | |
* @param {string} val | |
* @return {string} | |
* @api private | |
*/ | |
function qstring(val) { | |
var str = String(val) | |
// no need to quote tokens | |
if (tokenRegExp.test(str)) { | |
return str | |
} | |
if (str.length > 0 && !textRegExp.test(str)) { | |
throw new TypeError('invalid parameter value') | |
} | |
return '"' + str.replace(quoteRegExp, '\\$1') + '"' | |
} | |
/** | |
* Simply "type/subtype+siffx" into parts. | |
* | |
* @param {string} string | |
* @return {Object} | |
* @api private | |
*/ | |
function splitType(string) { | |
var match = typeRegExp.exec(string.toLowerCase()) | |
if (!match) { | |
throw new TypeError('invalid media type') | |
} | |
var type = match[1] | |
var subtype = match[2] | |
var suffix | |
// suffix after last + | |
var index = subtype.lastIndexOf('+') | |
if (index !== -1) { | |
suffix = subtype.substr(index + 1) | |
subtype = subtype.substr(0, index) | |
} | |
var obj = { | |
type: type, | |
subtype: subtype, | |
suffix: suffix | |
} | |
return obj | |
} | |