123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- /*!
- * content-type
- * Copyright(c) 2015 Douglas Christopher Wilson
- * MIT Licensed
- */
- 'use strict'
- /**
- * 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
- var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/
- 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
- /**
- * 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.substr(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[0] === '"') {
- // remove quotes and escapes
- value = value
- .substr(1, value.length - 2)
- .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
- }
|