'use strict'; function parseContentType(str) { if (str.length === 0) return; const params = Object.create(null); let i = 0; // Parse type for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { if (code !== 47/* '/' */ || i === 0) return; break; } } // Check for type without subtype if (i === str.length) return; const type = str.slice(0, i).toLowerCase(); // Parse subtype const subtypeStart = ++i; for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { // Make sure we have a subtype if (i === subtypeStart) return; if (parseContentTypeParams(str, i, params) === undefined) return; break; } } // Make sure we have a subtype if (i === subtypeStart) return; const subtype = str.slice(subtypeStart, i).toLowerCase(); return { type, subtype, params }; } function parseContentTypeParams(str, i, params) { while (i < str.length) { // Consume whitespace for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code !== 32/* ' ' */ && code !== 9/* '\t' */) break; } // Ended on whitespace if (i === str.length) break; // Check for malformed parameter if (str.charCodeAt(i++) !== 59/* ';' */) return; // Consume whitespace for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code !== 32/* ' ' */ && code !== 9/* '\t' */) break; } // Ended on whitespace (malformed) if (i === str.length) return; let name; const nameStart = i; // Parse parameter name for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { if (code !== 61/* '=' */) return; break; } } // No value (malformed) if (i === str.length) return; name = str.slice(nameStart, i); ++i; // Skip over '=' // No value (malformed) if (i === str.length) return; let value = ''; let valueStart; if (str.charCodeAt(i) === 34/* '"' */) { valueStart = ++i; let escaping = false; // Parse quoted value for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code === 92/* '\\' */) { if (escaping) { valueStart = i; escaping = false; } else { value += str.slice(valueStart, i); escaping = true; } continue; } if (code === 34/* '"' */) { if (escaping) { valueStart = i; escaping = false; continue; } value += str.slice(valueStart, i); break; } if (escaping) { valueStart = i - 1; escaping = false; } // Invalid unescaped quoted character (malformed) if (QDTEXT[code] !== 1) return; } // No end quote (malformed) if (i === str.length) return; ++i; // Skip over double quote } else { valueStart = i; // Parse unquoted value for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { // No value (malformed) if (i === valueStart) return; break; } } value = str.slice(valueStart, i); } name = name.toLowerCase(); if (params[name] === undefined) params[name] = value; } return params; } function parseDisposition(str, defDecoder) { if (str.length === 0) return; const params = Object.create(null); let i = 0; for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { if (parseDispositionParams(str, i, params, defDecoder) === undefined) return; break; } } const type = str.slice(0, i).toLowerCase(); return { type, params }; } function parseDispositionParams(str, i, params, defDecoder) { while (i < str.length) { // Consume whitespace for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code !== 32/* ' ' */ && code !== 9/* '\t' */) break; } // Ended on whitespace if (i === str.length) break; // Check for malformed parameter if (str.charCodeAt(i++) !== 59/* ';' */) return; // Consume whitespace for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code !== 32/* ' ' */ && code !== 9/* '\t' */) break; } // Ended on whitespace (malformed) if (i === str.length) return; let name; const nameStart = i; // Parse parameter name for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { if (code === 61/* '=' */) break; return; } } // No value (malformed) if (i === str.length) return; let value = ''; let valueStart; let charset; //~ let lang; name = str.slice(nameStart, i); if (name.charCodeAt(name.length - 1) === 42/* '*' */) { // Extended value const charsetStart = ++i; // Parse charset name for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (CHARSET[code] !== 1) { if (code !== 39/* '\'' */) return; break; } } // Incomplete charset (malformed) if (i === str.length) return; charset = str.slice(charsetStart, i); ++i; // Skip over the '\'' //~ const langStart = ++i; // Parse language name for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code === 39/* '\'' */) break; } // Incomplete language (malformed) if (i === str.length) return; //~ lang = str.slice(langStart, i); ++i; // Skip over the '\'' // No value (malformed) if (i === str.length) return; valueStart = i; let encode = 0; // Parse value for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (EXTENDED_VALUE[code] !== 1) { if (code === 37/* '%' */) { let hexUpper; let hexLower; if (i + 2 < str.length && (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1 && (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) { const byteVal = (hexUpper << 4) + hexLower; value += str.slice(valueStart, i); value += String.fromCharCode(byteVal); i += 2; valueStart = i + 1; if (byteVal >= 128) encode = 2; else if (encode === 0) encode = 1; continue; } // '%' disallowed in non-percent encoded contexts (malformed) return; } break; } } value += str.slice(valueStart, i); value = convertToUTF8(value, charset, encode); if (value === undefined) return; } else { // Non-extended value ++i; // Skip over '=' // No value (malformed) if (i === str.length) return; if (str.charCodeAt(i) === 34/* '"' */) { valueStart = ++i; let escaping = false; // Parse quoted value for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (code === 92/* '\\' */) { if (escaping) { valueStart = i; escaping = false; } else { value += str.slice(valueStart, i); escaping = true; } continue; } if (code === 34/* '"' */) { if (escaping) { valueStart = i; escaping = false; continue; } value += str.slice(valueStart, i); break; } if (escaping) { valueStart = i - 1; escaping = false; } // Invalid unescaped quoted character (malformed) if (QDTEXT[code] !== 1) return; } // No end quote (malformed) if (i === str.length) return; ++i; // Skip over double quote } else { valueStart = i; // Parse unquoted value for (; i < str.length; ++i) { const code = str.charCodeAt(i); if (TOKEN[code] !== 1) { // No value (malformed) if (i === valueStart) return; break; } } value = str.slice(valueStart, i); } value = defDecoder(value, 2); if (value === undefined) return; } name = name.toLowerCase(); if (params[name] === undefined) params[name] = value; } return params; } function getDecoder(charset) { let lc; while (true) { switch (charset) { case 'utf-8': case 'utf8': return decoders.utf8; case 'latin1': case 'ascii': // TODO: Make these a separate, strict decoder? case 'us-ascii': case 'iso-8859-1': case 'iso8859-1': case 'iso88591': case 'iso_8859-1': case 'windows-1252': case 'iso_8859-1:1987': case 'cp1252': case 'x-cp1252': return decoders.latin1; case 'utf16le': case 'utf-16le': case 'ucs2': case 'ucs-2': return decoders.utf16le; case 'base64': return decoders.base64; default: if (lc === undefined) { lc = true; charset = charset.toLowerCase(); continue; } return decoders.other.bind(charset); } } } const decoders = { utf8: (data, hint) => { if (data.length === 0) return ''; if (typeof data === 'string') { // If `data` never had any percent-encoded bytes or never had any that // were outside of the ASCII range, then we can safely just return the // input since UTF-8 is ASCII compatible if (hint < 2) return data; data = Buffer.from(data, 'latin1'); } return data.utf8Slice(0, data.length); }, latin1: (data, hint) => { if (data.length === 0) return ''; if (typeof data === 'string') return data; return data.latin1Slice(0, data.length); }, utf16le: (data, hint) => { if (data.length === 0) return ''; if (typeof data === 'string') data = Buffer.from(data, 'latin1'); return data.ucs2Slice(0, data.length); }, base64: (data, hint) => { if (data.length === 0) return ''; if (typeof data === 'string') data = Buffer.from(data, 'latin1'); return data.base64Slice(0, data.length); }, other: (data, hint) => { if (data.length === 0) return ''; if (typeof data === 'string') data = Buffer.from(data, 'latin1'); try { const decoder = new TextDecoder(this); return decoder.decode(data); } catch {} }, }; function convertToUTF8(data, charset, hint) { const decode = getDecoder(charset); if (decode) return decode(data, hint); } function basename(path) { if (typeof path !== 'string') return ''; for (let i = path.length - 1; i >= 0; --i) { switch (path.charCodeAt(i)) { case 0x2F: // '/' case 0x5C: // '\' path = path.slice(i + 1); return (path === '..' || path === '.' ? '' : path); } } return (path === '..' || path === '.' ? '' : path); } const TOKEN = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; const QDTEXT = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]; const CHARSET = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; const EXTENDED_VALUE = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; /* eslint-disable no-multi-spaces */ const HEX_VALUES = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ]; /* eslint-enable no-multi-spaces */ module.exports = { basename, convertToUTF8, getDecoder, parseContentType, parseDisposition, };