second
[josuexyz/.git] / node_modules / media-typer / index.js
1 /*!
2  * media-typer
3  * Copyright(c) 2014 Douglas Christopher Wilson
4  * MIT Licensed
5  */
6
7 /**
8  * RegExp to match *( ";" parameter ) in RFC 2616 sec 3.7
9  *
10  * parameter     = token "=" ( token | quoted-string )
11  * token         = 1*<any CHAR except CTLs or separators>
12  * separators    = "(" | ")" | "<" | ">" | "@"
13  *               | "," | ";" | ":" | "\" | <">
14  *               | "/" | "[" | "]" | "?" | "="
15  *               | "{" | "}" | SP | HT
16  * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
17  * qdtext        = <any TEXT except <">>
18  * quoted-pair   = "\" CHAR
19  * CHAR          = <any US-ASCII character (octets 0 - 127)>
20  * TEXT          = <any OCTET except CTLs, but including LWS>
21  * LWS           = [CRLF] 1*( SP | HT )
22  * CRLF          = CR LF
23  * CR            = <US-ASCII CR, carriage return (13)>
24  * LF            = <US-ASCII LF, linefeed (10)>
25  * SP            = <US-ASCII SP, space (32)>
26  * SHT           = <US-ASCII HT, horizontal-tab (9)>
27  * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
28  * OCTET         = <any 8-bit sequence of data>
29  */
30 var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u0020-\u007e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g;
31 var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/
32 var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/
33
34 /**
35  * RegExp to match quoted-pair in RFC 2616
36  *
37  * quoted-pair = "\" CHAR
38  * CHAR        = <any US-ASCII character (octets 0 - 127)>
39  */
40 var qescRegExp = /\\([\u0000-\u007f])/g;
41
42 /**
43  * RegExp to match chars that must be quoted-pair in RFC 2616
44  */
45 var quoteRegExp = /([\\"])/g;
46
47 /**
48  * RegExp to match type in RFC 6838
49  *
50  * type-name = restricted-name
51  * subtype-name = restricted-name
52  * restricted-name = restricted-name-first *126restricted-name-chars
53  * restricted-name-first  = ALPHA / DIGIT
54  * restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
55  *                          "$" / "&" / "-" / "^" / "_"
56  * restricted-name-chars =/ "." ; Characters before first dot always
57  *                              ; specify a facet name
58  * restricted-name-chars =/ "+" ; Characters after last plus always
59  *                              ; specify a structured syntax suffix
60  * ALPHA =  %x41-5A / %x61-7A   ; A-Z / a-z
61  * DIGIT =  %x30-39             ; 0-9
62  */
63 var subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/
64 var typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/
65 var typeRegExp = /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/;
66
67 /**
68  * Module exports.
69  */
70
71 exports.format = format
72 exports.parse = parse
73
74 /**
75  * Format object to media type.
76  *
77  * @param {object} obj
78  * @return {string}
79  * @api public
80  */
81
82 function format(obj) {
83   if (!obj || typeof obj !== 'object') {
84     throw new TypeError('argument obj is required')
85   }
86
87   var parameters = obj.parameters
88   var subtype = obj.subtype
89   var suffix = obj.suffix
90   var type = obj.type
91
92   if (!type || !typeNameRegExp.test(type)) {
93     throw new TypeError('invalid type')
94   }
95
96   if (!subtype || !subtypeNameRegExp.test(subtype)) {
97     throw new TypeError('invalid subtype')
98   }
99
100   // format as type/subtype
101   var string = type + '/' + subtype
102
103   // append +suffix
104   if (suffix) {
105     if (!typeNameRegExp.test(suffix)) {
106       throw new TypeError('invalid suffix')
107     }
108
109     string += '+' + suffix
110   }
111
112   // append parameters
113   if (parameters && typeof parameters === 'object') {
114     var param
115     var params = Object.keys(parameters).sort()
116
117     for (var i = 0; i < params.length; i++) {
118       param = params[i]
119
120       if (!tokenRegExp.test(param)) {
121         throw new TypeError('invalid parameter name')
122       }
123
124       string += '; ' + param + '=' + qstring(parameters[param])
125     }
126   }
127
128   return string
129 }
130
131 /**
132  * Parse media type to object.
133  *
134  * @param {string|object} string
135  * @return {Object}
136  * @api public
137  */
138
139 function parse(string) {
140   if (!string) {
141     throw new TypeError('argument string is required')
142   }
143
144   // support req/res-like objects as argument
145   if (typeof string === 'object') {
146     string = getcontenttype(string)
147   }
148
149   if (typeof string !== 'string') {
150     throw new TypeError('argument string is required to be a string')
151   }
152
153   var index = string.indexOf(';')
154   var type = index !== -1
155     ? string.substr(0, index)
156     : string
157
158   var key
159   var match
160   var obj = splitType(type)
161   var params = {}
162   var value
163
164   paramRegExp.lastIndex = index
165
166   while (match = paramRegExp.exec(string)) {
167     if (match.index !== index) {
168       throw new TypeError('invalid parameter format')
169     }
170
171     index += match[0].length
172     key = match[1].toLowerCase()
173     value = match[2]
174
175     if (value[0] === '"') {
176       // remove quotes and escapes
177       value = value
178         .substr(1, value.length - 2)
179         .replace(qescRegExp, '$1')
180     }
181
182     params[key] = value
183   }
184
185   if (index !== -1 && index !== string.length) {
186     throw new TypeError('invalid parameter format')
187   }
188
189   obj.parameters = params
190
191   return obj
192 }
193
194 /**
195  * Get content-type from req/res objects.
196  *
197  * @param {object}
198  * @return {Object}
199  * @api private
200  */
201
202 function getcontenttype(obj) {
203   if (typeof obj.getHeader === 'function') {
204     // res-like
205     return obj.getHeader('content-type')
206   }
207
208   if (typeof obj.headers === 'object') {
209     // req-like
210     return obj.headers && obj.headers['content-type']
211   }
212 }
213
214 /**
215  * Quote a string if necessary.
216  *
217  * @param {string} val
218  * @return {string}
219  * @api private
220  */
221
222 function qstring(val) {
223   var str = String(val)
224
225   // no need to quote tokens
226   if (tokenRegExp.test(str)) {
227     return str
228   }
229
230   if (str.length > 0 && !textRegExp.test(str)) {
231     throw new TypeError('invalid parameter value')
232   }
233
234   return '"' + str.replace(quoteRegExp, '\\$1') + '"'
235 }
236
237 /**
238  * Simply "type/subtype+siffx" into parts.
239  *
240  * @param {string} string
241  * @return {Object}
242  * @api private
243  */
244
245 function splitType(string) {
246   var match = typeRegExp.exec(string.toLowerCase())
247
248   if (!match) {
249     throw new TypeError('invalid media type')
250   }
251
252   var type = match[1]
253   var subtype = match[2]
254   var suffix
255
256   // suffix after last +
257   var index = subtype.lastIndexOf('+')
258   if (index !== -1) {
259     suffix = subtype.substr(index + 1)
260     subtype = subtype.substr(0, index)
261   }
262
263   var obj = {
264     type: type,
265     subtype: subtype,
266     suffix: suffix
267   }
268
269   return obj
270 }