CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
1/*!2 * content-disposition3 * Copyright(c) 2014-2017 Douglas Christopher Wilson4 * MIT Licensed5 */6 7'use strict'8 9/**10 * Module exports.11 * @public12 */13 14module.exports = contentDisposition15module.exports.parse = parse16 17/**18 * TextDecoder instance for UTF-8 decoding when decodeURIComponent fails due to invalid byte sequences.19 * @type {TextDecoder}20 * @private21 */22const utf8Decoder = new TextDecoder('utf-8')23 24/**25 * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")26 * @private27 */28 29var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex30 31/**32 * RegExp to match non-latin1 characters.33 * @private34 */35 36var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g37 38/**39 * RegExp to match quoted-pair in RFC 261640 *41 * quoted-pair = "\" CHAR42 * CHAR        = <any US-ASCII character (octets 0 - 127)>43 * @private44 */45 46var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex47 48/**49 * RegExp to match chars that must be quoted-pair in RFC 261650 * @private51 */52 53var QUOTE_REGEXP = /([\\"])/g54 55/**56 * RegExp for various RFC 2616 grammar57 *58 * parameter     = token "=" ( token | quoted-string )59 * token         = 1*<any CHAR except CTLs or separators>60 * separators    = "(" | ")" | "<" | ">" | "@"61 *               | "," | ";" | ":" | "\" | <">62 *               | "/" | "[" | "]" | "?" | "="63 *               | "{" | "}" | SP | HT64 * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )65 * qdtext        = <any TEXT except <">>66 * quoted-pair   = "\" CHAR67 * CHAR          = <any US-ASCII character (octets 0 - 127)>68 * TEXT          = <any OCTET except CTLs, but including LWS>69 * LWS           = [CRLF] 1*( SP | HT )70 * CRLF          = CR LF71 * CR            = <US-ASCII CR, carriage return (13)>72 * LF            = <US-ASCII LF, linefeed (10)>73 * SP            = <US-ASCII SP, space (32)>74 * HT            = <US-ASCII HT, horizontal-tab (9)>75 * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>76 * OCTET         = <any 8-bit sequence of data>77 * @private78 */79 80var 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-regex81var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/82var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/83 84/**85 * RegExp for various RFC 5987 grammar86 *87 * ext-value     = charset  "'" [ language ] "'" value-chars88 * charset       = "UTF-8" / "ISO-8859-1" / mime-charset89 * mime-charset  = 1*mime-charsetc90 * mime-charsetc = ALPHA / DIGIT91 *               / "!" / "#" / "$" / "%" / "&"92 *               / "+" / "-" / "^" / "_" / "`"93 *               / "{" / "}" / "~"94 * language      = ( 2*3ALPHA [ extlang ] )95 *               / 4ALPHA96 *               / 5*8ALPHA97 * extlang       = *3( "-" 3ALPHA )98 * value-chars   = *( pct-encoded / attr-char )99 * pct-encoded   = "%" HEXDIG HEXDIG100 * attr-char     = ALPHA / DIGIT101 *               / "!" / "#" / "$" / "&" / "+" / "-" / "."102 *               / "^" / "_" / "`" / "|" / "~"103 * @private104 */105 106var 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!#$&+.^_`|~-])+)$/107 108/**109 * RegExp for various RFC 6266 grammar110 *111 * disposition-type = "inline" | "attachment" | disp-ext-type112 * disp-ext-type    = token113 * disposition-parm = filename-parm | disp-ext-parm114 * filename-parm    = "filename" "=" value115 *                  | "filename*" "=" ext-value116 * disp-ext-parm    = token "=" value117 *                  | ext-token "=" ext-value118 * ext-token        = <the characters in token, followed by "*">119 * @private120 */121 122var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex123 124/**125 * Create an attachment Content-Disposition header.126 *127 * @param {string} [filename]128 * @param {object} [options]129 * @param {string} [options.type=attachment]130 * @param {string|boolean} [options.fallback=true]131 * @return {string}132 * @public133 */134 135function contentDisposition (filename, options) {136  var opts = options || {}137 138  // get type139  var type = opts.type || 'attachment'140 141  // get parameters142  var params = createparams(filename, opts.fallback)143 144  // format into string145  return format(new ContentDisposition(type, params))146}147 148/**149 * Create parameters object from filename and fallback.150 *151 * @param {string} [filename]152 * @param {string|boolean} [fallback=true]153 * @return {object}154 * @private155 */156 157function createparams (filename, fallback) {158  if (filename === undefined) {159    return160  }161 162  var params = {}163 164  if (typeof filename !== 'string') {165    throw new TypeError('filename must be a string')166  }167 168  // fallback defaults to true169  if (fallback === undefined) {170    fallback = true171  }172 173  if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {174    throw new TypeError('fallback must be a string or boolean')175  }176 177  if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {178    throw new TypeError('fallback must be ISO-8859-1 string')179  }180 181  // restrict to file base name182  var name = basename(filename)183 184  // determine if name is suitable for quoted string185  var isQuotedString = TEXT_REGEXP.test(name)186 187  // generate fallback name188  var fallbackName = typeof fallback !== 'string'189    ? fallback && getlatin1(name)190    : basename(fallback)191  var hasFallback = typeof fallbackName === 'string' && fallbackName !== name192 193  // set extended filename parameter194  if (hasFallback || !isQuotedString || hasHexEscape(name)) {195    params['filename*'] = name196  }197 198  // set filename parameter199  if (isQuotedString || hasFallback) {200    params.filename = hasFallback201      ? fallbackName202      : name203  }204 205  return params206}207 208/**209 * Format object to Content-Disposition header.210 *211 * @param {object} obj212 * @param {string} obj.type213 * @param {object} [obj.parameters]214 * @return {string}215 * @private216 */217 218function format (obj) {219  var parameters = obj.parameters220  var type = obj.type221 222  if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {223    throw new TypeError('invalid type')224  }225 226  // start with normalized type227  var string = String(type).toLowerCase()228 229  // append parameters230  if (parameters && typeof parameters === 'object') {231    var param232    var params = Object.keys(parameters).sort()233 234    for (var i = 0; i < params.length; i++) {235      param = params[i]236 237      var val = param.slice(-1) === '*'238        ? ustring(parameters[param])239        : qstring(parameters[param])240 241      string += '; ' + param + '=' + val242    }243  }244 245  return string246}247 248/**249 * Decode a RFC 5987 field value (gracefully).250 *251 * @param {string} str252 * @return {string}253 * @private254 */255 256function decodefield (str) {257  const match = EXT_VALUE_REGEXP.exec(str)258 259  if (!match) {260    throw new TypeError('invalid extended field value')261  }262 263  const charset = match[1].toLowerCase()264  const encoded = match[2]265 266  switch (charset) {267    case 'iso-8859-1':268    {269      const binary = decodeHexEscapes(encoded)270      return getlatin1(binary)271    }272    case 'utf-8':273    case 'utf8':274    {275      try {276        return decodeURIComponent(encoded)277      } catch {278        // Failed to decode with decodeURIComponent, fallback to lenient decoding which replaces invalid UTF-8 byte sequences with the Unicode replacement character279        // TODO: Consider removing in the next major version to be more strict about invalid percent-encodings280        const binary = decodeHexEscapes(encoded)281 282        const bytes = new Uint8Array(binary.length)283        for (let idx = 0; idx < binary.length; idx++) {284          bytes[idx] = binary.charCodeAt(idx)285        }286 287        return utf8Decoder.decode(bytes)288      }289    }290  }291  throw new TypeError('unsupported charset in extended field')292}293 294/**295 * Get ISO-8859-1 version of string.296 *297 * @param {string} val298 * @return {string}299 * @private300 */301 302function getlatin1 (val) {303  // simple Unicode -> ISO-8859-1 transformation304  return String(val).replace(NON_LATIN1_REGEXP, '?')305}306 307/**308 * Parse Content-Disposition header string.309 *310 * @param {string} string311 * @return {object}312 * @public313 */314 315function parse (string) {316  if (!string || typeof string !== 'string') {317    throw new TypeError('argument string is required')318  }319 320  var match = DISPOSITION_TYPE_REGEXP.exec(string)321 322  if (!match) {323    throw new TypeError('invalid type format')324  }325 326  // normalize type327  var index = match[0].length328  var type = match[1].toLowerCase()329 330  var key331  var names = []332  var params = {}333  var value334 335  // calculate index to start at336  index = PARAM_REGEXP.lastIndex = match[0].slice(-1) === ';'337    ? index - 1338    : index339 340  // match parameters341  while ((match = PARAM_REGEXP.exec(string))) {342    if (match.index !== index) {343      throw new TypeError('invalid parameter format')344    }345 346    index += match[0].length347    key = match[1].toLowerCase()348    value = match[2]349 350    if (names.indexOf(key) !== -1) {351      throw new TypeError('invalid duplicate parameter')352    }353 354    names.push(key)355 356    if (key.indexOf('*') + 1 === key.length) {357      // decode extended value358      key = key.slice(0, -1)359      value = decodefield(value)360 361      // overwrite existing value362      params[key] = value363      continue364    }365 366    if (typeof params[key] === 'string') {367      continue368    }369 370    if (value[0] === '"') {371      // remove quotes and escapes372      value = value373        .slice(1, -1)374        .replace(QESC_REGEXP, '$1')375    }376 377    params[key] = value378  }379 380  if (index !== -1 && index !== string.length) {381    throw new TypeError('invalid parameter format')382  }383 384  return new ContentDisposition(type, params)385}386 387/**388 * Percent encode a single character.389 *390 * @param {string} char391 * @return {string}392 * @private393 */394 395function pencode (char) {396  return '%' + String(char)397    .charCodeAt(0)398    .toString(16)399    .toUpperCase()400}401 402/**403 * Quote a string for HTTP.404 *405 * @param {string} val406 * @return {string}407 * @private408 */409 410function qstring (val) {411  var str = String(val)412 413  return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'414}415 416/**417 * Encode a Unicode string for HTTP (RFC 5987).418 *419 * @param {string} val420 * @return {string}421 * @private422 */423 424function ustring (val) {425  var str = String(val)426 427  // percent encode as UTF-8428  var encoded = encodeURIComponent(str)429    .replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)430 431  return 'UTF-8\'\'' + encoded432}433 434/**435 * Class for parsed Content-Disposition header for v8 optimization436 *437 * @public438 * @param {string} type439 * @param {object} parameters440 * @constructor441 */442 443function ContentDisposition (type, parameters) {444  this.type = type445  this.parameters = parameters446}447 448/**449 * Return the last portion of a path450 *451 * @param {string} path452 * @returns {string}453 */454function basename (path) {455  const normalized = path.replaceAll('\\', '/')456 457  let end = normalized.length458  while (end > 0 && normalized[end - 1] === '/') {459    end--460  }461 462  if (end === 0) {463    return ''464  }465 466  let start = end - 1467  while (start >= 0 && normalized[start] !== '/') {468    start--469  }470 471  return normalized.slice(start + 1, end)472}473 474/**475 * Check if a character is a hex digit [0-9A-Fa-f]476 *477 * @param {string} char478 * @return {boolean}479 * @private480 */481function isHexDigit (char) {482  const code = char.charCodeAt(0)483  return (484    (code >= 48 && code <= 57) || // 0-9485    (code >= 65 && code <= 70) || // A-F486    (code >= 97 && code <= 102) // a-f487  )488}489 490/**491 * Check if a string contains percent encoding escapes.492 *493 * @param {string} str494 * @return {boolean}495 * @private496 */497function hasHexEscape (str) {498  const maxIndex = str.length - 3499  let lastIndex = -1500 501  while ((lastIndex = str.indexOf('%', lastIndex + 1)) !== -1 && lastIndex <= maxIndex) {502    if (isHexDigit(str[lastIndex + 1]) && isHexDigit(str[lastIndex + 2])) {503      return true504    }505  }506 507  return false508}509 510/**511 * Decode hex escapes in a string (e.g., %20 -> space)512 *513 * @param {string} str514 * @return {string}515 * @private516 */517function decodeHexEscapes (str) {518  const firstEscape = str.indexOf('%')519  if (firstEscape === -1) return str520 521  let result = str.slice(0, firstEscape)522  for (let idx = firstEscape; idx < str.length; idx++) {523    if (524      str[idx] === '%' &&525      idx + 2 < str.length &&526      isHexDigit(str[idx + 1]) &&527      isHexDigit(str[idx + 2])528    ) {529      result += String.fromCharCode(Number.parseInt(str[idx + 1] + str[idx + 2], 16))530      idx += 2531    } else {532      result += str[idx]533    }534  }535  return result536}537