| |
| |
| |
| |
|
|
|
|
| 'use strict'
|
|
|
| |
| |
| |
|
|
|
|
| module.exports = contentDisposition
|
| module.exports.parse = parse
|
|
|
| |
| |
| |
| |
|
|
| const utf8Decoder = new TextDecoder('utf-8')
|
|
|
| |
| |
| |
|
|
|
|
| var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g
|
|
|
| |
| |
| |
|
|
|
|
| var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| var QESC_REGEXP = /\\([\u0000-\u007f])/g
|
|
|
| |
| |
| |
|
|
|
|
| var QUOTE_REGEXP = /([\\"])/g
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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
|
| var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
|
| var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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!#$&+.^_`|~-])+)$/
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| function contentDisposition (filename, options) {
|
| var opts = options || {}
|
|
|
|
|
| var type = opts.type || 'attachment'
|
|
|
|
|
| var params = createparams(filename, opts.fallback)
|
|
|
|
|
| return format(new ContentDisposition(type, params))
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| function createparams (filename, fallback) {
|
| if (filename === undefined) {
|
| return
|
| }
|
|
|
| var params = {}
|
|
|
| if (typeof filename !== 'string') {
|
| throw new TypeError('filename must be a string')
|
| }
|
|
|
|
|
| if (fallback === undefined) {
|
| fallback = true
|
| }
|
|
|
| if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
|
| throw new TypeError('fallback must be a string or boolean')
|
| }
|
|
|
| if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
|
| throw new TypeError('fallback must be ISO-8859-1 string')
|
| }
|
|
|
|
|
| var name = basename(filename)
|
|
|
|
|
| var isQuotedString = TEXT_REGEXP.test(name)
|
|
|
|
|
| var fallbackName = typeof fallback !== 'string'
|
| ? fallback && getlatin1(name)
|
| : basename(fallback)
|
| var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
|
|
|
|
|
| if (hasFallback || !isQuotedString || hasHexEscape(name)) {
|
| params['filename*'] = name
|
| }
|
|
|
|
|
| if (isQuotedString || hasFallback) {
|
| params.filename = hasFallback
|
| ? fallbackName
|
| : name
|
| }
|
|
|
| return params
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| function format (obj) {
|
| var parameters = obj.parameters
|
| var type = obj.type
|
|
|
| if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
|
| throw new TypeError('invalid type')
|
| }
|
|
|
|
|
| var string = String(type).toLowerCase()
|
|
|
|
|
| if (parameters && typeof parameters === 'object') {
|
| var param
|
| var params = Object.keys(parameters).sort()
|
|
|
| for (var i = 0; i < params.length; i++) {
|
| param = params[i]
|
|
|
| var val = param.slice(-1) === '*'
|
| ? ustring(parameters[param])
|
| : qstring(parameters[param])
|
|
|
| string += '; ' + param + '=' + val
|
| }
|
| }
|
|
|
| return string
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function decodefield (str) {
|
| const match = EXT_VALUE_REGEXP.exec(str)
|
|
|
| if (!match) {
|
| throw new TypeError('invalid extended field value')
|
| }
|
|
|
| const charset = match[1].toLowerCase()
|
| const encoded = match[2]
|
|
|
| switch (charset) {
|
| case 'iso-8859-1':
|
| {
|
| const binary = decodeHexEscapes(encoded)
|
| return getlatin1(binary)
|
| }
|
| case 'utf-8':
|
| case 'utf8':
|
| {
|
| try {
|
| return decodeURIComponent(encoded)
|
| } catch {
|
|
|
|
|
| const binary = decodeHexEscapes(encoded)
|
|
|
| const bytes = new Uint8Array(binary.length)
|
| for (let idx = 0; idx < binary.length; idx++) {
|
| bytes[idx] = binary.charCodeAt(idx)
|
| }
|
|
|
| return utf8Decoder.decode(bytes)
|
| }
|
| }
|
| }
|
| throw new TypeError('unsupported charset in extended field')
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function getlatin1 (val) {
|
|
|
| return String(val).replace(NON_LATIN1_REGEXP, '?')
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function parse (string) {
|
| if (!string || typeof string !== 'string') {
|
| throw new TypeError('argument string is required')
|
| }
|
|
|
| var match = DISPOSITION_TYPE_REGEXP.exec(string)
|
|
|
| if (!match) {
|
| throw new TypeError('invalid type format')
|
| }
|
|
|
|
|
| var index = match[0].length
|
| var type = match[1].toLowerCase()
|
|
|
| var key
|
| var names = []
|
| var params = {}
|
| var value
|
|
|
|
|
| index = PARAM_REGEXP.lastIndex = match[0].slice(-1) === ';'
|
| ? index - 1
|
| : index
|
|
|
|
|
| while ((match = PARAM_REGEXP.exec(string))) {
|
| if (match.index !== index) {
|
| throw new TypeError('invalid parameter format')
|
| }
|
|
|
| index += match[0].length
|
| key = match[1].toLowerCase()
|
| value = match[2]
|
|
|
| if (names.indexOf(key) !== -1) {
|
| throw new TypeError('invalid duplicate parameter')
|
| }
|
|
|
| names.push(key)
|
|
|
| if (key.indexOf('*') + 1 === key.length) {
|
|
|
| key = key.slice(0, -1)
|
| value = decodefield(value)
|
|
|
|
|
| params[key] = value
|
| continue
|
| }
|
|
|
| if (typeof params[key] === 'string') {
|
| continue
|
| }
|
|
|
| if (value[0] === '"') {
|
|
|
| value = value
|
| .slice(1, -1)
|
| .replace(QESC_REGEXP, '$1')
|
| }
|
|
|
| params[key] = value
|
| }
|
|
|
| if (index !== -1 && index !== string.length) {
|
| throw new TypeError('invalid parameter format')
|
| }
|
|
|
| return new ContentDisposition(type, params)
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function pencode (char) {
|
| return '%' + String(char)
|
| .charCodeAt(0)
|
| .toString(16)
|
| .toUpperCase()
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function qstring (val) {
|
| var str = String(val)
|
|
|
| return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
|
|
| function ustring (val) {
|
| var str = String(val)
|
|
|
|
|
| var encoded = encodeURIComponent(str)
|
| .replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)
|
|
|
| return 'UTF-8\'\'' + encoded
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| function ContentDisposition (type, parameters) {
|
| this.type = type
|
| this.parameters = parameters
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| function basename (path) {
|
| const normalized = path.replaceAll('\\', '/')
|
|
|
| let end = normalized.length
|
| while (end > 0 && normalized[end - 1] === '/') {
|
| end--
|
| }
|
|
|
| if (end === 0) {
|
| return ''
|
| }
|
|
|
| let start = end - 1
|
| while (start >= 0 && normalized[start] !== '/') {
|
| start--
|
| }
|
|
|
| return normalized.slice(start + 1, end)
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| function isHexDigit (char) {
|
| const code = char.charCodeAt(0)
|
| return (
|
| (code >= 48 && code <= 57) ||
|
| (code >= 65 && code <= 70) ||
|
| (code >= 97 && code <= 102)
|
| )
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| function hasHexEscape (str) {
|
| const maxIndex = str.length - 3
|
| let lastIndex = -1
|
|
|
| while ((lastIndex = str.indexOf('%', lastIndex + 1)) !== -1 && lastIndex <= maxIndex) {
|
| if (isHexDigit(str[lastIndex + 1]) && isHexDigit(str[lastIndex + 2])) {
|
| return true
|
| }
|
| }
|
|
|
| return false
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| function decodeHexEscapes (str) {
|
| const firstEscape = str.indexOf('%')
|
| if (firstEscape === -1) return str
|
|
|
| let result = str.slice(0, firstEscape)
|
| for (let idx = firstEscape; idx < str.length; idx++) {
|
| if (
|
| str[idx] === '%' &&
|
| idx + 2 < str.length &&
|
| isHexDigit(str[idx + 1]) &&
|
| isHexDigit(str[idx + 2])
|
| ) {
|
| result += String.fromCharCode(Number.parseInt(str[idx + 1] + str[idx + 2], 16))
|
| idx += 2
|
| } else {
|
| result += str[idx]
|
| }
|
| }
|
| return result
|
| }
|
|
|