content-disposition
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -0,1 +1,6 @@ | ||
0.5.0 / 2014-10-11 | ||
================== | ||
* Add `parse` function | ||
0.4.0 / 2014-09-21 | ||
@@ -2,0 +7,0 @@ ================== |
333
index.js
@@ -12,2 +12,3 @@ /*! | ||
module.exports = contentDisposition | ||
module.exports.parse = parse | ||
@@ -30,11 +31,21 @@ /** | ||
var hexEscapeRegExp = /%[0-9A-F]{2}/i | ||
var hexEscapeRegExp = /%[0-9A-Fa-f]{2}/ | ||
var hexEscapeReplaceRegExp = /%([0-9A-Fa-f]{2})/g | ||
/** | ||
* RegExp to match non-RFC 2616 text characters. | ||
* RegExp to match non-latin1 characters. | ||
*/ | ||
var nonTextRegExp = /[^\x20-\x7e\x80-\xff]/g | ||
var nonLatin1RegExp = /[^\x20-\x7e\xa0-\xff]/g | ||
/** | ||
* RegExp to match quoted-pair in RFC 2616 | ||
* | ||
* quoted-pair = "\" CHAR | ||
* CHAR = <any US-ASCII character (octets 0 - 127)> | ||
*/ | ||
var qescRegExp = /\\([\u0000-\u007f])/g; | ||
/** | ||
* RegExp to match chars that must be quoted-pair in RFC 2616 | ||
@@ -48,18 +59,66 @@ */ | ||
* | ||
* token = 1*<any CHAR except CTLs or separators> | ||
* separators = "(" | ")" | "<" | ">" | "@" | ||
* | "," | ";" | ":" | "\" | <"> | ||
* | "/" | "[" | "]" | "?" | "=" | ||
* | "{" | "}" | SP | HT | ||
* CHAR = <any US-ASCII character (octets 0 - 127)> | ||
* TEXT = <any OCTET except CTLs, but including LWS> | ||
* SP = <US-ASCII SP, space (32)> | ||
* HT = <US-ASCII HT, horizontal-tab (9)> | ||
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> | ||
* parameter = token "=" ( token | quoted-string ) | ||
* token = 1*<any CHAR except CTLs or separators> | ||
* separators = "(" | ")" | "<" | ">" | "@" | ||
* | "," | ";" | ":" | "\" | <"> | ||
* | "/" | "[" | "]" | "?" | "=" | ||
* | "{" | "}" | SP | HT | ||
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) | ||
* qdtext = <any TEXT except <">> | ||
* quoted-pair = "\" CHAR | ||
* CHAR = <any US-ASCII character (octets 0 - 127)> | ||
* TEXT = <any OCTET except CTLs, but including LWS> | ||
* LWS = [CRLF] 1*( SP | HT ) | ||
* CRLF = CR LF | ||
* CR = <US-ASCII CR, carriage return (13)> | ||
* LF = <US-ASCII LF, linefeed (10)> | ||
* SP = <US-ASCII SP, space (32)> | ||
* HT = <US-ASCII HT, horizontal-tab (9)> | ||
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> | ||
* OCTET = <any 8-bit sequence of data> | ||
*/ | ||
var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/ | ||
var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g | ||
var textRegExp = /^[\x20-\x7e\x80-\xff]+$/ | ||
var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/ | ||
/** | ||
* RegExp for various RFC 5987 grammar | ||
* | ||
* ext-value = charset "'" [ language ] "'" value-chars | ||
* charset = "UTF-8" / "ISO-8859-1" / mime-charset | ||
* mime-charset = 1*mime-charsetc | ||
* mime-charsetc = ALPHA / DIGIT | ||
* / "!" / "#" / "$" / "%" / "&" | ||
* / "+" / "-" / "^" / "_" / "`" | ||
* / "{" / "}" / "~" | ||
* language = ( 2*3ALPHA [ extlang ] ) | ||
* / 4ALPHA | ||
* / 5*8ALPHA | ||
* extlang = *3( "-" 3ALPHA ) | ||
* value-chars = *( pct-encoded / attr-char ) | ||
* pct-encoded = "%" HEXDIG HEXDIG | ||
* attr-char = ALPHA / DIGIT | ||
* / "!" / "#" / "$" / "&" / "+" / "-" / "." | ||
* / "^" / "_" / "`" / "|" / "~" | ||
*/ | ||
var extValueRegExp = /^([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!#$&+\-\.^_`|~])+)$/ | ||
/** | ||
* RegExp for various RFC 6266 grammar | ||
* | ||
* disposition-type = "inline" | "attachment" | disp-ext-type | ||
* disp-ext-type = token | ||
* disposition-parm = filename-parm | disp-ext-parm | ||
* filename-parm = "filename" "=" value | ||
* | "filename*" "=" ext-value | ||
* disp-ext-parm = token "=" value | ||
* | ext-token "=" ext-value | ||
* ext-token = <the characters in token, followed by "*"> | ||
*/ | ||
var dispositionTypeRegExp = /^([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *(?:$|;)/ | ||
/** | ||
* Create an attachment Content-Disposition header. | ||
@@ -81,32 +140,40 @@ * | ||
if (typeof type !== 'string') { | ||
throw new TypeError('option type must be a string') | ||
} | ||
// get parameters | ||
var params = createparams(filename, opts.fallback) | ||
if (!tokenRegExp.test(type)) { | ||
throw new TypeError('option type must be a valid token') | ||
} | ||
// format into string | ||
return format(new ContentDisposition(type, params)) | ||
} | ||
// normalize type | ||
type = type.toLowerCase() | ||
/** | ||
* Create parameters object from filename and fallback. | ||
* | ||
* @param {string} [filename] | ||
* @param {string|boolean} [fallback=true] | ||
* @return {object} | ||
* @api private | ||
*/ | ||
function createparams(filename, fallback) { | ||
if (filename === undefined) { | ||
return type | ||
return | ||
} | ||
var params = {} | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('argument filename must be a string') | ||
throw new TypeError('filename must be a string') | ||
} | ||
// get fallback | ||
var fallback = opts.fallback !== undefined | ||
? opts.fallback | ||
: true | ||
// fallback defaults to true | ||
if (fallback === undefined) { | ||
fallback = true | ||
} | ||
if (typeof fallback !== 'string' && typeof fallback !== 'boolean') { | ||
throw new TypeError('option fallback must be a string or boolean') | ||
throw new TypeError('fallback must be a string or boolean') | ||
} | ||
if (typeof fallback === 'string' && nonTextRegExp.test(fallback)) { | ||
throw new TypeError('option fallback must be ISO-8859-1 string') | ||
if (typeof fallback === 'string' && nonLatin1RegExp.test(fallback)) { | ||
throw new TypeError('fallback must be ISO-8859-1 string') | ||
} | ||
@@ -117,2 +184,5 @@ | ||
// determine if name is suitable for quoted string | ||
var isQuotedString = textRegExp.test(name) | ||
// generate fallback name | ||
@@ -122,19 +192,96 @@ var fallbackName = typeof fallback !== 'string' | ||
: basename(fallback) | ||
var hasFallback = typeof fallbackName === 'string' && fallbackName !== name | ||
var isSimpleHeader = (typeof fallbackName !== 'string' || fallbackName === name) | ||
&& !hexEscapeRegExp.test(name) | ||
&& textRegExp.test(name) | ||
// set extended filename parameter | ||
if (hasFallback || !isQuotedString || hexEscapeRegExp.test(name)) { | ||
params['filename*'] = name | ||
} | ||
if (isSimpleHeader) { | ||
// simple header | ||
// file name is always quoted and not a token for RFC 2616 compatibility | ||
return type + '; filename=' + qstring(name) | ||
// set filename parameter | ||
if (isQuotedString || hasFallback) { | ||
params.filename = hasFallback | ||
? fallbackName | ||
: name | ||
} | ||
return type | ||
+ (fallbackName !== false ? '; filename=' + qstring(fallbackName) : '') | ||
+ '; filename*=' + ustring(name) | ||
return params | ||
} | ||
/** | ||
* Format object to Content-Disposition header. | ||
* | ||
* @param {object} obj | ||
* @param {string} obj.type | ||
* @param {object} [obj.parameters] | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function format(obj) { | ||
var parameters = obj.parameters | ||
var type = obj.type | ||
if (!type || typeof type !== 'string' || !tokenRegExp.test(type)) { | ||
throw new TypeError('invalid type') | ||
} | ||
// start with normalized type | ||
var string = String(type).toLowerCase() | ||
// append parameters | ||
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.substr(-1) === '*' | ||
? ustring(parameters[param]) | ||
: qstring(parameters[param]) | ||
string += '; ' + param + '=' + val | ||
} | ||
} | ||
return string | ||
} | ||
/** | ||
* Decode a RFC 6987 field value (gracefully). | ||
* | ||
* @param {string} str | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function decodefield(str) { | ||
var match = extValueRegExp.exec(str) | ||
if (!match) { | ||
throw new TypeError('invalid extended field value') | ||
} | ||
var charset = match[1].toLowerCase() | ||
var encoded = match[2] | ||
var value | ||
// to binary string | ||
var binary = encoded.replace(hexEscapeReplaceRegExp, pdecode) | ||
switch (charset) { | ||
case 'iso-8859-1': | ||
value = getlatin1(binary) | ||
break | ||
case 'utf-8': | ||
value = new Buffer(binary, 'binary').toString('utf8') | ||
break | ||
default: | ||
throw new TypeError('unsupported charset in extended field') | ||
} | ||
return value | ||
} | ||
/** | ||
* Get ISO-8859-1 version of string. | ||
@@ -149,6 +296,99 @@ * | ||
// simple Unicode -> ISO-8859-1 transformation | ||
return String(val).replace(nonTextRegExp, '?') | ||
return String(val).replace(nonLatin1RegExp, '?') | ||
} | ||
/** | ||
* Parse Content-Disposition header string. | ||
* | ||
* @param {string} string | ||
* @return {object} | ||
* @api private | ||
*/ | ||
function parse(string) { | ||
if (!string || typeof string !== 'string') { | ||
throw new TypeError('argument string is required') | ||
} | ||
var match = dispositionTypeRegExp.exec(string) | ||
if (!match) { | ||
throw new TypeError('invalid type format') | ||
} | ||
// normalize type | ||
var index = match[0].length | ||
var type = match[1].toLowerCase() | ||
var key | ||
var names = [] | ||
var params = {} | ||
var value | ||
// calculate index to start at | ||
index = paramRegExp.lastIndex = match[0].substr(-1) === ';' | ||
? index - 1 | ||
: index | ||
// match parameters | ||
while (match = paramRegExp.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] === '"') { | ||
// remove quotes and escapes | ||
value = value | ||
.substr(1, value.length - 2) | ||
.replace(qescRegExp, '$1') | ||
} | ||
params[key] = value | ||
} | ||
if (index !== -1 && index !== string.length) { | ||
throw new TypeError('invalid parameter format') | ||
} | ||
return new ContentDisposition(type, params) | ||
} | ||
/** | ||
* Percent decode a single character. | ||
* | ||
* @param {string} str | ||
* @param {string} hex | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function pdecode(str, hex) { | ||
return String.fromCharCode(parseInt(hex, 16)) | ||
} | ||
/** | ||
* Percent encode a single character. | ||
@@ -202,1 +442,10 @@ * | ||
} | ||
/** | ||
* Class for parsed Content-Disposition header for v8 optimization | ||
*/ | ||
function ContentDisposition(type, parameters) { | ||
this.type = type | ||
this.parameters = parameters | ||
} |
{ | ||
"name": "content-disposition", | ||
"description": "Create an attachment Content-Disposition header", | ||
"version": "0.4.0", | ||
"description": "Create and parse Content-Disposition header", | ||
"version": "0.5.0", | ||
"contributors": [ | ||
@@ -6,0 +6,0 @@ "Douglas Christopher Wilson <doug@somethingdoug.com>" |
@@ -9,3 +9,3 @@ # content-disposition | ||
Create an attachment Content-Disposition header | ||
Create and parse HTTP `Content-Disposition` header | ||
@@ -68,2 +68,19 @@ ## Installation | ||
### contentDisposition.parse(string) | ||
```js | ||
var disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt"'); | ||
``` | ||
Parse a `Content-Disposition` header string. This automatically handles extended | ||
("Unicode") parameters by decoding them and providing them under the standard | ||
parameter name. This will return an object with the following properties (examples | ||
are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`): | ||
- `type`: The disposition type (always lower case). Example: `'attachment'` | ||
- `parameters`: An object of the parameters in the disposition (name of parameter | ||
always lower case and extended versions replace non-extended versions). Example: | ||
`{filename: "€ rates.txt"}` | ||
## Examples | ||
@@ -70,0 +87,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
17993
356
142
1