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 | } |