@tinyhttp/content-disposition
Advanced tools
Comparing version 2.0.6 to 2.0.7
@@ -1,2 +0,1 @@ | ||
// eslint-disable-next-line no-control-regex | ||
const ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g; | ||
@@ -6,147 +5,124 @@ const HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/; | ||
const NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g; | ||
// eslint-disable-next-line no-control-regex | ||
const QESC_REGEXP = /\\([\u0000-\u007f])/g; | ||
const QUOTE_REGEXP = /([\\"])/g; | ||
const 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 | ||
const 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; | ||
const TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/; | ||
const TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/; | ||
const 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!#$&+.^_`|~-])+)$/; | ||
// eslint-disable-next-line no-control-regex | ||
const DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/; | ||
const getlatin1 = (val) => { | ||
// simple Unicode -> ISO-8859-1 transformation | ||
return String(val).replace(NON_LATIN1_REGEXP, '?'); | ||
return String(val).replace(NON_LATIN1_REGEXP, "?"); | ||
}; | ||
class ContentDisposition { | ||
constructor(type, parameters) { | ||
this.type = type; | ||
this.parameters = parameters; | ||
} | ||
constructor(type, parameters) { | ||
this.type = type; | ||
this.parameters = parameters; | ||
} | ||
} | ||
const qstring = (val) => '"' + String(val).replace(QUOTE_REGEXP, '\\$1') + '"'; | ||
const pencode = (char) => '%' + String(char).charCodeAt(0).toString(16).toUpperCase(); | ||
const qstring = (val) => '"' + String(val).replace(QUOTE_REGEXP, "\\$1") + '"'; | ||
const pencode = (char) => "%" + String(char).charCodeAt(0).toString(16).toUpperCase(); | ||
function ustring(val) { | ||
const str = String(val); | ||
// percent encode as UTF-8 | ||
const encoded = encodeURIComponent(str).replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode); | ||
return "UTF-8''" + encoded; | ||
const str = String(val); | ||
const encoded = encodeURIComponent(str).replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode); | ||
return "UTF-8''" + encoded; | ||
} | ||
const basename = (str) => str.slice(str.lastIndexOf('/') + 1); | ||
function format({ parameters, type }) { | ||
if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) | ||
throw new TypeError('invalid type'); | ||
// start with normalized type | ||
let string = String(type).toLowerCase(); | ||
// append parameters | ||
if (parameters && typeof parameters === 'object') { | ||
const params = Object.keys(parameters).sort(); | ||
for (const param of params) { | ||
const val = param.substr(-1) === '*' ? ustring(parameters[param]) : qstring(parameters[param]); | ||
string += '; ' + param + '=' + val; | ||
} | ||
const basename = (str) => str.slice(str.lastIndexOf("/") + 1); | ||
function format({ | ||
parameters, | ||
type | ||
}) { | ||
if (!type || typeof type !== "string" || !TOKEN_REGEXP.test(type)) | ||
throw new TypeError("invalid type"); | ||
let string = String(type).toLowerCase(); | ||
if (parameters && typeof parameters === "object") { | ||
const params = Object.keys(parameters).sort(); | ||
for (const param of params) { | ||
const val = param.substr(-1) === "*" ? ustring(parameters[param]) : qstring(parameters[param]); | ||
string += "; " + param + "=" + val; | ||
} | ||
return string; | ||
} | ||
return string; | ||
} | ||
function createParams(filename, fallback) { | ||
if (filename === undefined) | ||
return; | ||
const params = {}; | ||
// fallback defaults to true | ||
if (fallback === undefined) | ||
fallback = true; | ||
if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) | ||
throw new TypeError('fallback must be ISO-8859-1 string'); | ||
// restrict to file base name | ||
const name = basename(filename); | ||
// determine if name is suitable for quoted string | ||
const isQuotedString = TEXT_REGEXP.test(name); | ||
// generate fallback name | ||
const fallbackName = typeof fallback !== 'string' ? fallback && getlatin1(name) : basename(fallback); | ||
const hasFallback = typeof fallbackName === 'string' && fallbackName !== name; | ||
// set extended filename parameter | ||
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) | ||
params['filename*'] = name; | ||
// set filename parameter | ||
if (isQuotedString || hasFallback) | ||
params.filename = hasFallback ? fallbackName : name; | ||
return params; | ||
if (filename === void 0) | ||
return; | ||
const params = {}; | ||
if (fallback === void 0) | ||
fallback = true; | ||
if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) | ||
throw new TypeError("fallback must be ISO-8859-1 string"); | ||
const name = basename(filename); | ||
const isQuotedString = TEXT_REGEXP.test(name); | ||
const fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename(fallback); | ||
const hasFallback = typeof fallbackName === "string" && fallbackName !== name; | ||
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) | ||
params["filename*"] = name; | ||
if (isQuotedString || hasFallback) | ||
params.filename = hasFallback ? fallbackName : name; | ||
return params; | ||
} | ||
const pdecode = (_str, hex) => String.fromCharCode(parseInt(hex, 16)); | ||
/** | ||
* Create an attachment Content-Disposition header. | ||
* | ||
* @param filename file name | ||
* @param options | ||
*/ | ||
function contentDisposition(filename, options = {}) { | ||
// format into string | ||
return format(new ContentDisposition(options.type || 'attachment', createParams(filename, options.fallback))); | ||
return format(new ContentDisposition(options.type || "attachment", createParams(filename, options.fallback))); | ||
} | ||
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]; | ||
let value; | ||
// to binary string | ||
const binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode); | ||
switch (charset) { | ||
case 'iso-8859-1': | ||
value = getlatin1(binary); | ||
break; | ||
case 'utf-8': | ||
value = Buffer.from(binary, 'binary').toString('utf8'); | ||
break; | ||
default: | ||
throw new TypeError('unsupported charset in extended field'); | ||
} | ||
return value; | ||
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]; | ||
let value; | ||
const binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode); | ||
switch (charset) { | ||
case "iso-8859-1": | ||
value = getlatin1(binary); | ||
break; | ||
case "utf-8": | ||
value = Buffer.from(binary, "binary").toString("utf8"); | ||
break; | ||
default: | ||
throw new TypeError("unsupported charset in extended field"); | ||
} | ||
return value; | ||
} | ||
/** | ||
* Parse Content-Disposition header string. | ||
* @param string string | ||
*/ | ||
function parse(string) { | ||
let match = DISPOSITION_TYPE_REGEXP.exec(string); | ||
if (!match) | ||
throw new TypeError('invalid type format'); | ||
// normalize type | ||
let index = match[0].length; | ||
const type = match[1].toLowerCase(); | ||
let key; | ||
const names = []; | ||
const params = {}; | ||
let value; | ||
// calculate index to start at | ||
index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === ';' ? index - 1 : index; | ||
// match parameters | ||
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) { | ||
// decode extended value | ||
key = key.slice(0, -1); | ||
value = decodefield(value); | ||
// overwrite existing value | ||
params[key] = value; | ||
continue; | ||
} | ||
if (typeof params[key] === 'string') | ||
continue; | ||
if (value[0] === '"') | ||
value = value.substr(1, value.length - 2).replace(QESC_REGEXP, '$1'); | ||
params[key] = value; | ||
let match = DISPOSITION_TYPE_REGEXP.exec(string); | ||
if (!match) | ||
throw new TypeError("invalid type format"); | ||
let index = match[0].length; | ||
const type = match[1].toLowerCase(); | ||
let key; | ||
const names = []; | ||
const params = {}; | ||
let value; | ||
index = PARAM_REGEXP.lastIndex = match[0].substr(-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 (index !== -1 && index !== string.length) | ||
throw new TypeError('invalid parameter format'); | ||
return new ContentDisposition(type, params); | ||
if (typeof params[key] === "string") | ||
continue; | ||
if (value[0] === '"') | ||
value = value.substr(1, value.length - 2).replace(QESC_REGEXP, "$1"); | ||
params[key] = value; | ||
} | ||
if (index !== -1 && index !== string.length) | ||
throw new TypeError("invalid parameter format"); | ||
return new ContentDisposition(type, params); | ||
} | ||
export { ContentDisposition, contentDisposition, parse }; | ||
export { | ||
ContentDisposition, | ||
contentDisposition, | ||
parse | ||
}; |
{ | ||
"name": "@tinyhttp/content-disposition", | ||
"description": "content-disposition rewrite in TypeScript", | ||
"version": "2.0.6", | ||
"version": "2.0.7", | ||
"homepage": "https://tinyhttp.v1rtl.site", | ||
@@ -24,5 +24,8 @@ "funding": { | ||
], | ||
"dependencies": {}, | ||
"scripts": { | ||
"build": "rollup -c ../../build/defaultConfig.js" | ||
"dev": "vite", | ||
"build": "vite build", | ||
"postbuild": "tsc --emitDeclarationOnly" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10877
147