| /*! | 
|  * 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 | 
| } |