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