fa3bc7415209dbb6855a4a32d794f899b195b319
[sdnc/oam.git] / dgbuilder / dgeflows / node_modules / express / node_modules / content-disposition / index.js
1 /*!
2  * content-disposition
3  * Copyright(c) 2014 Douglas Christopher Wilson
4  * MIT Licensed
5  */
6
7 /**
8  * Module exports.
9  */
10
11 module.exports = contentDisposition
12 module.exports.parse = parse
13
14 /**
15  * Module dependencies.
16  */
17
18 var basename = require('path').basename
19
20 /**
21  * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
22  */
23
24 var encodeUriAttrCharRegExp = /[\x00-\x20"'\(\)*,\/:;<=>?@\[\\\]\{\}\x7f]/g
25
26 /**
27  * RegExp to match percent encoding escape.
28  */
29
30 var hexEscapeRegExp = /%[0-9A-Fa-f]{2}/
31 var hexEscapeReplaceRegExp = /%([0-9A-Fa-f]{2})/g
32
33 /**
34  * RegExp to match non-latin1 characters.
35  */
36
37 var nonLatin1RegExp = /[^\x20-\x7e\xa0-\xff]/g
38
39 /**
40  * RegExp to match quoted-pair in RFC 2616
41  *
42  * quoted-pair = "\" CHAR
43  * CHAR        = <any US-ASCII character (octets 0 - 127)>
44  */
45
46 var qescRegExp = /\\([\u0000-\u007f])/g;
47
48 /**
49  * RegExp to match chars that must be quoted-pair in RFC 2616
50  */
51
52 var quoteRegExp = /([\\"])/g
53
54 /**
55  * RegExp for various RFC 2616 grammar
56  *
57  * parameter     = token "=" ( token | quoted-string )
58  * token         = 1*<any CHAR except CTLs or separators>
59  * separators    = "(" | ")" | "<" | ">" | "@"
60  *               | "," | ";" | ":" | "\" | <">
61  *               | "/" | "[" | "]" | "?" | "="
62  *               | "{" | "}" | SP | HT
63  * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
64  * qdtext        = <any TEXT except <">>
65  * quoted-pair   = "\" CHAR
66  * CHAR          = <any US-ASCII character (octets 0 - 127)>
67  * TEXT          = <any OCTET except CTLs, but including LWS>
68  * LWS           = [CRLF] 1*( SP | HT )
69  * CRLF          = CR LF
70  * CR            = <US-ASCII CR, carriage return (13)>
71  * LF            = <US-ASCII LF, linefeed (10)>
72  * SP            = <US-ASCII SP, space (32)>
73  * HT            = <US-ASCII HT, horizontal-tab (9)>
74  * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
75  * OCTET         = <any 8-bit sequence of data>
76  */
77
78 var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g
79 var textRegExp = /^[\x20-\x7e\x80-\xff]+$/
80 var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/
81
82 /**
83  * RegExp for various RFC 5987 grammar
84  *
85  * ext-value     = charset  "'" [ language ] "'" value-chars
86  * charset       = "UTF-8" / "ISO-8859-1" / mime-charset
87  * mime-charset  = 1*mime-charsetc
88  * mime-charsetc = ALPHA / DIGIT
89  *               / "!" / "#" / "$" / "%" / "&"
90  *               / "+" / "-" / "^" / "_" / "`"
91  *               / "{" / "}" / "~"
92  * language      = ( 2*3ALPHA [ extlang ] )
93  *               / 4ALPHA
94  *               / 5*8ALPHA
95  * extlang       = *3( "-" 3ALPHA )
96  * value-chars   = *( pct-encoded / attr-char )
97  * pct-encoded   = "%" HEXDIG HEXDIG
98  * attr-char     = ALPHA / DIGIT
99  *               / "!" / "#" / "$" / "&" / "+" / "-" / "."
100  *               / "^" / "_" / "`" / "|" / "~"
101  */
102
103 var extValueRegExp = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+\-\.^_`|~])+)$/
104
105 /**
106  * RegExp for various RFC 6266 grammar
107  *
108  * disposition-type = "inline" | "attachment" | disp-ext-type
109  * disp-ext-type    = token
110  * disposition-parm = filename-parm | disp-ext-parm
111  * filename-parm    = "filename" "=" value
112  *                  | "filename*" "=" ext-value
113  * disp-ext-parm    = token "=" value
114  *                  | ext-token "=" ext-value
115  * ext-token        = <the characters in token, followed by "*">
116  */
117
118 var dispositionTypeRegExp = /^([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *(?:$|;)/
119
120 /**
121  * Create an attachment Content-Disposition header.
122  *
123  * @param {string} [filename]
124  * @param {object} [options]
125  * @param {string} [options.type=attachment]
126  * @param {string|boolean} [options.fallback=true]
127  * @return {string}
128  * @api public
129  */
130
131 function contentDisposition(filename, options) {
132   var opts = options || {}
133
134   // get type
135   var type = opts.type || 'attachment'
136
137   // get parameters
138   var params = createparams(filename, opts.fallback)
139
140   // format into string
141   return format(new ContentDisposition(type, params))
142 }
143
144 /**
145  * Create parameters object from filename and fallback.
146  *
147  * @param {string} [filename]
148  * @param {string|boolean} [fallback=true]
149  * @return {object}
150  * @api private
151  */
152
153 function createparams(filename, fallback) {
154   if (filename === undefined) {
155     return
156   }
157
158   var params = {}
159
160   if (typeof filename !== 'string') {
161     throw new TypeError('filename must be a string')
162   }
163
164   // fallback defaults to true
165   if (fallback === undefined) {
166     fallback = true
167   }
168
169   if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
170     throw new TypeError('fallback must be a string or boolean')
171   }
172
173   if (typeof fallback === 'string' && nonLatin1RegExp.test(fallback)) {
174     throw new TypeError('fallback must be ISO-8859-1 string')
175   }
176
177   // restrict to file base name
178   var name = basename(filename)
179
180   // determine if name is suitable for quoted string
181   var isQuotedString = textRegExp.test(name)
182
183   // generate fallback name
184   var fallbackName = typeof fallback !== 'string'
185     ? fallback && getlatin1(name)
186     : basename(fallback)
187   var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
188
189   // set extended filename parameter
190   if (hasFallback || !isQuotedString || hexEscapeRegExp.test(name)) {
191     params['filename*'] = name
192   }
193
194   // set filename parameter
195   if (isQuotedString || hasFallback) {
196     params.filename = hasFallback
197       ? fallbackName
198       : name
199   }
200
201   return params
202 }
203
204 /**
205  * Format object to Content-Disposition header.
206  *
207  * @param {object} obj
208  * @param {string} obj.type
209  * @param {object} [obj.parameters]
210  * @return {string}
211  * @api private
212  */
213
214 function format(obj) {
215   var parameters = obj.parameters
216   var type = obj.type
217
218   if (!type || typeof type !== 'string' || !tokenRegExp.test(type)) {
219     throw new TypeError('invalid type')
220   }
221
222   // start with normalized type
223   var string = String(type).toLowerCase()
224
225   // append parameters
226   if (parameters && typeof parameters === 'object') {
227     var param
228     var params = Object.keys(parameters).sort()
229
230     for (var i = 0; i < params.length; i++) {
231       param = params[i]
232
233       var val = param.substr(-1) === '*'
234         ? ustring(parameters[param])
235         : qstring(parameters[param])
236
237       string += '; ' + param + '=' + val
238     }
239   }
240
241   return string
242 }
243
244 /**
245  * Decode a RFC 6987 field value (gracefully).
246  *
247  * @param {string} str
248  * @return {string}
249  * @api private
250  */
251
252 function decodefield(str) {
253   var match = extValueRegExp.exec(str)
254
255   if (!match) {
256     throw new TypeError('invalid extended field value')
257   }
258
259   var charset = match[1].toLowerCase()
260   var encoded = match[2]
261   var value
262
263   // to binary string
264   var binary = encoded.replace(hexEscapeReplaceRegExp, pdecode)
265
266   switch (charset) {
267     case 'iso-8859-1':
268       value = getlatin1(binary)
269       break
270     case 'utf-8':
271       value = new Buffer(binary, 'binary').toString('utf8')
272       break
273     default:
274       throw new TypeError('unsupported charset in extended field')
275   }
276
277   return value
278 }
279
280 /**
281  * Get ISO-8859-1 version of string.
282  *
283  * @param {string} val
284  * @return {string}
285  * @api private
286  */
287
288 function getlatin1(val) {
289   // simple Unicode -> ISO-8859-1 transformation
290   return String(val).replace(nonLatin1RegExp, '?')
291 }
292
293 /**
294  * Parse Content-Disposition header string.
295  *
296  * @param {string} string
297  * @return {object}
298  * @api private
299  */
300
301 function parse(string) {
302   if (!string || typeof string !== 'string') {
303     throw new TypeError('argument string is required')
304   }
305
306   var match = dispositionTypeRegExp.exec(string)
307
308   if (!match) {
309     throw new TypeError('invalid type format')
310   }
311
312   // normalize type
313   var index = match[0].length
314   var type = match[1].toLowerCase()
315
316   var key
317   var names = []
318   var params = {}
319   var value
320
321   // calculate index to start at
322   index = paramRegExp.lastIndex = match[0].substr(-1) === ';'
323     ? index - 1
324     : index
325
326   // match parameters
327   while (match = paramRegExp.exec(string)) {
328     if (match.index !== index) {
329       throw new TypeError('invalid parameter format')
330     }
331
332     index += match[0].length
333     key = match[1].toLowerCase()
334     value = match[2]
335
336     if (names.indexOf(key) !== -1) {
337       throw new TypeError('invalid duplicate parameter')
338     }
339
340     names.push(key)
341
342     if (key.indexOf('*') + 1 === key.length) {
343       // decode extended value
344       key = key.slice(0, -1)
345       value = decodefield(value)
346
347       // overwrite existing value
348       params[key] = value
349       continue
350     }
351
352     if (typeof params[key] === 'string') {
353       continue
354     }
355
356     if (value[0] === '"') {
357       // remove quotes and escapes
358       value = value
359         .substr(1, value.length - 2)
360         .replace(qescRegExp, '$1')
361     }
362
363     params[key] = value
364   }
365
366   if (index !== -1 && index !== string.length) {
367     throw new TypeError('invalid parameter format')
368   }
369
370   return new ContentDisposition(type, params)
371 }
372
373 /**
374  * Percent decode a single character.
375  *
376  * @param {string} str
377  * @param {string} hex
378  * @return {string}
379  * @api private
380  */
381
382 function pdecode(str, hex) {
383   return String.fromCharCode(parseInt(hex, 16))
384 }
385
386 /**
387  * Percent encode a single character.
388  *
389  * @param {string} char
390  * @return {string}
391  * @api private
392  */
393
394 function pencode(char) {
395   var hex = String(char)
396     .charCodeAt(0)
397     .toString(16)
398     .toUpperCase()
399   return hex.length === 1
400     ? '%0' + hex
401     : '%' + hex
402 }
403
404 /**
405  * Quote a string for HTTP.
406  *
407  * @param {string} val
408  * @return {string}
409  * @api private
410  */
411
412 function qstring(val) {
413   var str = String(val)
414
415   return '"' + str.replace(quoteRegExp, '\\$1') + '"'
416 }
417
418 /**
419  * Encode a Unicode string for HTTP (RFC 5987).
420  *
421  * @param {string} val
422  * @return {string}
423  * @api private
424  */
425
426 function ustring(val) {
427   var str = String(val)
428
429   // percent encode as UTF-8
430   var encoded = encodeURIComponent(str)
431     .replace(encodeUriAttrCharRegExp, pencode)
432
433   return 'UTF-8\'\'' + encoded
434 }
435
436 /**
437  * Class for parsed Content-Disposition header for v8 optimization
438  */
439
440 function ContentDisposition(type, parameters) {
441   this.type = type
442   this.parameters = parameters
443 }