@tinyhttp/content-disposition
Advanced tools
Comparing version 2.2.1 to 2.2.2
export declare class ContentDisposition { | ||
type: string; | ||
parameters: Record<string, unknown>; | ||
constructor(type: string, parameters: Record<string, unknown>); | ||
constructor(type: string, parameters: Record<string, string | undefined | boolean>); | ||
} | ||
@@ -6,0 +6,0 @@ /** |
@@ -0,1 +1,2 @@ | ||
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation> | ||
const ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g; | ||
@@ -5,132 +6,158 @@ const HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/; | ||
const NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g; | ||
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation> | ||
const QESC_REGEXP = /\\([\u0000-\u007f])/g; | ||
const QUOTE_REGEXP = /([\\"])/g; | ||
const PARAM_REGEXP = ( | ||
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation> | ||
/;[\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 PARAM_REGEXP = | ||
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation> | ||
/;[\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!#$&+.^_`|~-])+)$/; | ||
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation> | ||
const DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/; | ||
const getlatin1 = (val) => { | ||
return String(val).replace(NON_LATIN1_REGEXP, "?"); | ||
// simple Unicode -> ISO-8859-1 transformation | ||
return String(val).replace(NON_LATIN1_REGEXP, '?'); | ||
}; | ||
class ContentDisposition { | ||
constructor(type, parameters) { | ||
this.type = type; | ||
this.parameters = parameters; | ||
} | ||
export class ContentDisposition { | ||
constructor(type, parameters) { | ||
this.type = type; | ||
this.parameters = parameters; | ||
} | ||
} | ||
const qstring = (val) => `"${String(val).replace(QUOTE_REGEXP, "\\$1")}"`; | ||
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); | ||
const encoded = encodeURIComponent(str).replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode); | ||
return `UTF-8''${encoded}`; | ||
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 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.slice(-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'); | ||
} | ||
} | ||
return string; | ||
// 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.slice(-1) === '*' ? ustring(parameters[param]) : qstring(parameters[param]); | ||
string += `; ${param}=${val}`; | ||
} | ||
} | ||
return string; | ||
} | ||
function createParams(filename, fallback) { | ||
if (filename === void 0) return; | ||
const params = {}; | ||
if (!fallback) 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; | ||
if (filename === undefined) | ||
return {}; | ||
const params = {}; | ||
// fallback defaults to true | ||
if (!fallback) | ||
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; | ||
} | ||
const pdecode = (_str, hex) => String.fromCharCode(Number.parseInt(hex, 16)); | ||
function contentDisposition(filename, options = {}) { | ||
return format(new ContentDisposition(options.type || "attachment", createParams(filename, options.fallback))); | ||
/** | ||
* Create an attachment Content-Disposition header. | ||
* | ||
* @param filename file name | ||
* @param options | ||
*/ | ||
export function contentDisposition(filename, options = {}) { | ||
// format into string | ||
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; | ||
switch (charset) { | ||
case "iso-8859-1": | ||
value = getlatin1(encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)); | ||
break; | ||
case "utf-8": | ||
try { | ||
value = decodeURIComponent(encoded); | ||
} catch { | ||
throw new TypeError("invalid encoded utf-8"); | ||
} | ||
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; | ||
switch (charset) { | ||
case 'iso-8859-1': | ||
value = getlatin1(encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)); | ||
break; | ||
case 'utf-8': | ||
try { | ||
value = decodeURIComponent(encoded); | ||
} | ||
catch { | ||
throw new TypeError('invalid encoded utf-8'); | ||
} | ||
break; | ||
default: | ||
throw new TypeError('unsupported charset in extended field'); | ||
} | ||
return value; | ||
} | ||
function parse(header) { | ||
let match = DISPOSITION_TYPE_REGEXP.exec(header); | ||
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].slice(-1) === ";" ? index - 1 : index; | ||
while (match = PARAM_REGEXP.exec(header)) { | ||
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"); | ||
/** | ||
* Parse Content-Disposition header string. | ||
* @param header string | ||
*/ | ||
export function parse(header) { | ||
let match = DISPOSITION_TYPE_REGEXP.exec(header); | ||
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].slice(-1) === ';' ? index - 1 : index; | ||
// match parameters | ||
while ((match = PARAM_REGEXP.exec(header))) { | ||
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.slice(1, value.length - 1).replace(QESC_REGEXP, '$1'); | ||
} | ||
params[key] = value; | ||
} | ||
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 !== header.length) { | ||
throw new TypeError('invalid parameter format'); | ||
} | ||
if (typeof params[key] === "string") continue; | ||
if (value[0] === '"') { | ||
value = value.slice(1, value.length - 1).replace(QESC_REGEXP, "$1"); | ||
} | ||
params[key] = value; | ||
} | ||
if (index !== -1 && index !== header.length) { | ||
throw new TypeError("invalid parameter format"); | ||
} | ||
return new ContentDisposition(type, params); | ||
return new ContentDisposition(type, params); | ||
} | ||
export { | ||
ContentDisposition, | ||
contentDisposition, | ||
parse | ||
}; | ||
//# sourceMappingURL=index.js.map | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@tinyhttp/content-disposition", | ||
"description": "content-disposition rewrite in TypeScript", | ||
"version": "2.2.1", | ||
"version": "2.2.2", | ||
"license": "MIT", | ||
@@ -25,7 +25,5 @@ "homepage": "https://tinyhttp.v1rtl.site", | ||
], | ||
"dependencies": {}, | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build" | ||
"build": "tsc" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
181
18673