[Pkg-javascript-commits] [node-content-disposition] 01/02: Imported Upstream version 0.5.0

Andrew Kelley andrewrk-guest at moszumanska.debian.org
Wed Oct 15 04:34:42 UTC 2014


This is an automated email from the git hooks/post-receive script.

andrewrk-guest pushed a commit to branch master
in repository node-content-disposition.

commit 4970c3cf8f2323ac3373f6e9192788dcf2560128
Author: Andrew Kelley <superjoe30 at gmail.com>
Date:   Wed Oct 15 04:30:14 2014 +0000

    Imported Upstream version 0.5.0
---
 .gitignore   |   3 +
 .travis.yml  |  15 +
 HISTORY.md   |  40 +++
 LICENSE      |  22 ++
 README.md    | 141 +++++++++
 index.js     | 443 +++++++++++++++++++++++++++
 package.json |  34 +++
 test/test.js | 953 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1651 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3cd27af
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+coverage/
+node_modules/
+npm-debug.log
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f034de0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: node_js
+node_js:
+  - "0.6"
+  - "0.8"
+  - "0.10"
+  - "0.11"
+matrix:
+  allow_failures:
+    - node_js: "0.11"
+  fast_finish: true
+script:
+  - "test $TRAVIS_NODE_VERSION != '0.6' || npm test"
+  - "test $TRAVIS_NODE_VERSION  = '0.6' || npm run-script test-travis"
+after_script:
+  - "test $TRAVIS_NODE_VERSION = '0.10' && npm install coveralls at 2 && cat ./coverage/lcov.info | coveralls"
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..1192551
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,40 @@
+0.5.0 / 2014-10-11
+==================
+
+  * Add `parse` function
+
+0.4.0 / 2014-09-21
+==================
+
+  * Expand non-Unicode `filename` to the full ISO-8859-1 charset
+
+0.3.0 / 2014-09-20
+==================
+
+  * Add `fallback` option
+  * Add `type` option
+
+0.2.0 / 2014-09-19
+==================
+
+  * Reduce ambiguity of file names with hex escape in buggy browsers
+
+0.1.2 / 2014-09-19
+==================
+
+  * Fix periodic invalid Unicode filename header
+
+0.1.1 / 2014-09-19
+==================
+
+  * Fix invalid characters appearing in `filename*` parameter
+
+0.1.0 / 2014-09-18
+==================
+
+  * Make the `filename` argument optional
+
+0.0.0 / 2014-09-18
+==================
+
+  * Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b7dce6c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2014 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d265431
--- /dev/null
+++ b/README.md
@@ -0,0 +1,141 @@
+# content-disposition
+
+[![NPM Version][npm-image]][npm-url]
+[![NPM Downloads][downloads-image]][downloads-url]
+[![Node.js Version][node-version-image]][node-version-url]
+[![Build Status][travis-image]][travis-url]
+[![Test Coverage][coveralls-image]][coveralls-url]
+
+Create and parse HTTP `Content-Disposition` header
+
+## Installation
+
+```sh
+$ npm install content-disposition
+```
+
+## API
+
+```js
+var contentDisposition = require('content-disposition')
+```
+
+### contentDisposition(filename, options)
+
+Create an attachment `Content-Disposition` header value using the given file name,
+if supplied. The `filename` is optional and if no file name is desired, but you
+want to specify `options`, set `filename` to `undefined`.
+
+```js
+res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
+```
+
+**note** HTTP headers are of the ISO-8859-1 character set. If you are writing this
+header through a means different from `setHeader` in Node.js, you'll want to specify
+the `'binary'` encoding in Node.js.
+
+#### Options
+
+`contentDisposition` accepts these properties in the options object.
+
+##### fallback
+
+If the `filename` option is outside ISO-8859-1, then the file name is actually
+stored in a supplemental field for clients that support Unicode file names and
+a ISO-8859-1 version of the file name is automatically generated.
+
+This specifies the ISO-8859-1 file name to override the automatic generation or
+disables the generation all together, defaults to `true`.
+
+  - A string will specify the ISO-8859-1 file name to use in place of automatic
+    generation.
+  - `false` will disable including a ISO-8859-1 file name and only include the
+    Unicode version (unless the file name is already ISO-8859-1).
+  - `true` will enable automatic generation if the file name is outside ISO-8859-1.
+
+If the `filename` option is ISO-8859-1 and this option is specified and has a
+different value, then the `filename` option is encoded in the extended field
+and this set as the fallback field, even though they are both ISO-8859-1.
+
+##### type
+
+Specifies the disposition type, defaults to `"attachment"`. This can also be
+`"inline"`, or any other value (all values except inline are treated like
+`attachment`, but can convey additional information if both parties agree to
+it). The type is normalized to lower-case.
+
+### 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
+
+### Send a file for download
+
+```js
+var contentDisposition = require('content-disposition')
+var destroy = require('destroy')
+var http = require('http')
+var onFinished = require('on-finished')
+
+var filePath = '/path/to/public/plans.pdf'
+
+http.createServer(function onRequest(req, res) {
+  // set headers
+  res.setHeader('Content-Type', 'application/pdf')
+  res.setHeader('Content-Disposition', contentDisposition(filePath))
+
+  // send file
+  var stream = fs.createReadStream(filePath)
+  stream.pipe(res)
+  onFinished(res, function (err) {
+    destroy(stream)
+  })
+})
+```
+
+## Testing
+
+```sh
+$ npm test
+```
+
+## References
+
+- [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1][rfc-2616]
+- [RFC 5987: Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters][rfc-5987]
+- [RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)][rfc-6266]
+- [Test Cases for HTTP Content-Disposition header field (RFC 6266) and the Encodings defined in RFCs 2047, 2231 and 5987][tc-2231]
+
+[rfc-2616]: https://tools.ietf.org/html/rfc2616
+[rfc-5987]: https://tools.ietf.org/html/rfc5987
+[rfc-6266]: https://tools.ietf.org/html/rfc6266
+[tc-2231]: http://greenbytes.de/tech/tc2231/
+
+## License
+
+[MIT](LICENSE)
+
+[npm-image]: https://img.shields.io/npm/v/content-disposition.svg?style=flat
+[npm-url]: https://npmjs.org/package/content-disposition
+[node-version-image]: https://img.shields.io/node/v/content-disposition.svg?style=flat
+[node-version-url]: http://nodejs.org/download/
+[travis-image]: https://img.shields.io/travis/jshttp/content-disposition.svg?style=flat
+[travis-url]: https://travis-ci.org/jshttp/content-disposition
+[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg?style=flat
+[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
+[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg?style=flat
+[downloads-url]: https://npmjs.org/package/content-disposition
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..fa3bc74
--- /dev/null
+++ b/index.js
@@ -0,0 +1,443 @@
+/*!
+ * content-disposition
+ * Copyright(c) 2014 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+/**
+ * Module exports.
+ */
+
+module.exports = contentDisposition
+module.exports.parse = parse
+
+/**
+ * Module dependencies.
+ */
+
+var basename = require('path').basename
+
+/**
+ * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
+ */
+
+var encodeUriAttrCharRegExp = /[\x00-\x20"'\(\)*,\/:;<=>?@\[\\\]\{\}\x7f]/g
+
+/**
+ * RegExp to match percent encoding escape.
+ */
+
+var hexEscapeRegExp = /%[0-9A-Fa-f]{2}/
+var hexEscapeReplaceRegExp = /%([0-9A-Fa-f]{2})/g
+
+/**
+ * RegExp to match non-latin1 characters.
+ */
+
+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
+ */
+
+var quoteRegExp = /([\\"])/g
+
+/**
+ * RegExp for various RFC 2616 grammar
+ *
+ * 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 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.
+ *
+ * @param {string} [filename]
+ * @param {object} [options]
+ * @param {string} [options.type=attachment]
+ * @param {string|boolean} [options.fallback=true]
+ * @return {string}
+ * @api public
+ */
+
+function contentDisposition(filename, options) {
+  var opts = options || {}
+
+  // get type
+  var type = opts.type || 'attachment'
+
+  // get parameters
+  var params = createparams(filename, opts.fallback)
+
+  // format into string
+  return format(new ContentDisposition(type, params))
+}
+
+/**
+ * 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
+  }
+
+  var params = {}
+
+  if (typeof filename !== 'string') {
+    throw new TypeError('filename must be a string')
+  }
+
+  // fallback defaults to true
+  if (fallback === undefined) {
+    fallback = true
+  }
+
+  if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
+    throw new TypeError('fallback must be a string or boolean')
+  }
+
+  if (typeof fallback === 'string' && nonLatin1RegExp.test(fallback)) {
+    throw new TypeError('fallback must be ISO-8859-1 string')
+  }
+
+  // restrict to file base name
+  var name = basename(filename)
+
+  // determine if name is suitable for quoted string
+  var isQuotedString = textRegExp.test(name)
+
+  // generate fallback name
+  var fallbackName = typeof fallback !== 'string'
+    ? fallback && getlatin1(name)
+    : basename(fallback)
+  var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
+
+  // set extended filename parameter
+  if (hasFallback || !isQuotedString || hexEscapeRegExp.test(name)) {
+    params['filename*'] = name
+  }
+
+  // set filename parameter
+  if (isQuotedString || hasFallback) {
+    params.filename = hasFallback
+      ? fallbackName
+      : 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.
+ *
+ * @param {string} val
+ * @return {string}
+ * @api private
+ */
+
+function getlatin1(val) {
+  // simple Unicode -> ISO-8859-1 transformation
+  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.
+ *
+ * @param {string} char
+ * @return {string}
+ * @api private
+ */
+
+function pencode(char) {
+  var hex = String(char)
+    .charCodeAt(0)
+    .toString(16)
+    .toUpperCase()
+  return hex.length === 1
+    ? '%0' + hex
+    : '%' + hex
+}
+
+/**
+ * Quote a string for HTTP.
+ *
+ * @param {string} val
+ * @return {string}
+ * @api private
+ */
+
+function qstring(val) {
+  var str = String(val)
+
+  return '"' + str.replace(quoteRegExp, '\\$1') + '"'
+}
+
+/**
+ * Encode a Unicode string for HTTP (RFC 5987).
+ *
+ * @param {string} val
+ * @return {string}
+ * @api private
+ */
+
+function ustring(val) {
+  var str = String(val)
+
+  // percent encode as UTF-8
+  var encoded = encodeURIComponent(str)
+    .replace(encodeUriAttrCharRegExp, pencode)
+
+  return 'UTF-8\'\'' + encoded
+}
+
+/**
+ * Class for parsed Content-Disposition header for v8 optimization
+ */
+
+function ContentDisposition(type, parameters) {
+  this.type = type
+  this.parameters = parameters
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f1423bc
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "content-disposition",
+  "description": "Create and parse Content-Disposition header",
+  "version": "0.5.0",
+  "contributors": [
+    "Douglas Christopher Wilson <doug at somethingdoug.com>"
+  ],
+  "license": "MIT",
+  "keywords": [
+    "content-disposition",
+    "http",
+    "rfc6266",
+    "res"
+  ],
+  "repository": "jshttp/content-disposition",
+  "devDependencies": {
+    "istanbul": "0.3.2",
+    "mocha": "~1.21.4"
+  },
+  "files": [
+    "LICENSE",
+    "HISTORY.md",
+    "README.md",
+    "index.js"
+  ],
+  "engines": {
+    "node": ">= 0.6"
+  },
+  "scripts": {
+    "test": "mocha --reporter spec --bail --check-leaks test/",
+    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
+    "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
+  }
+}
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..4b746b3
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,953 @@
+
+var assert = require('assert')
+var contentDisposition = require('..')
+
+describe('contentDisposition()', function () {
+  it('should create an attachment header', function () {
+    assert.equal(contentDisposition(), 'attachment')
+  })
+})
+
+describe('contentDisposition(filename)', function () {
+  it('should require a string', function () {
+    assert.throws(contentDisposition.bind(null, 42),
+      /filename.*string/)
+  })
+
+  it('should create a header with file name', function () {
+    assert.equal(contentDisposition('plans.pdf'),
+      'attachment; filename="plans.pdf"')
+  })
+
+  it('should use the basename of the string', function () {
+    assert.equal(contentDisposition('/path/to/plans.pdf'),
+      'attachment; filename="plans.pdf"')
+  })
+
+  describe('when "filename" is US-ASCII', function () {
+    it('should only include filename parameter', function () {
+      assert.equal(contentDisposition('plans.pdf'),
+        'attachment; filename="plans.pdf"')
+    })
+
+    it('should escape quotes', function () {
+      assert.equal(contentDisposition('the "plans".pdf'),
+        'attachment; filename="the \\"plans\\".pdf"')
+    })
+  })
+
+  describe('when "filename" is ISO-8859-1', function () {
+    it('should only include filename parameter', function () {
+      assert.equal(contentDisposition('«plans».pdf'),
+        'attachment; filename="«plans».pdf"')
+    })
+
+    it('should escape quotes', function () {
+      assert.equal(contentDisposition('the "plans" (1µ).pdf'),
+        'attachment; filename="the \\"plans\\" (1µ).pdf"')
+    })
+  })
+
+  describe('when "filename" is Unicode', function () {
+    it('should include filename* parameter', function () {
+      assert.equal(contentDisposition('планы.pdf'),
+        'attachment; filename="?????.pdf"; filename*=UTF-8\'\'%D0%BF%D0%BB%D0%B0%D0%BD%D1%8B.pdf')
+    })
+
+    it('should include filename fallback', function () {
+      assert.equal(contentDisposition('£ and € rates.pdf'),
+        'attachment; filename="£ and ? rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
+      assert.equal(contentDisposition('€ rates.pdf'),
+        'attachment; filename="? rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf')
+    })
+
+    it('should encode special characters', function () {
+      assert.equal(contentDisposition('€\'*%().pdf'),
+        'attachment; filename="?\'*%().pdf"; filename*=UTF-8\'\'%E2%82%AC%27%2A%25%28%29.pdf')
+    })
+  })
+
+  describe('when "filename" contains hex escape', function () {
+    it('should include filename* parameter', function () {
+      assert.equal(contentDisposition('the%20plans.pdf'),
+        'attachment; filename="the%20plans.pdf"; filename*=UTF-8\'\'the%2520plans.pdf')
+    })
+
+    it('should handle Unicode', function () {
+      assert.equal(contentDisposition('€%20£.pdf'),
+        'attachment; filename="?%20£.pdf"; filename*=UTF-8\'\'%E2%82%AC%2520%C2%A3.pdf')
+    })
+  })
+})
+
+describe('contentDisposition(filename, options)', function () {
+  describe('with "fallback" option', function () {
+    it('should require a string or Boolean', function () {
+      assert.throws(contentDisposition.bind(null, 'plans.pdf', { fallback: 42 }),
+        /fallback.*string/)
+    })
+
+    it('should default to true', function () {
+      assert.equal(contentDisposition('€ rates.pdf'),
+        'attachment; filename="? rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf')
+    })
+
+    describe('when "false"', function () {
+      it('should not generate ISO-8859-1 fallback', function () {
+      assert.equal(contentDisposition('£ and € rates.pdf', { fallback: false }),
+        'attachment; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
+      })
+
+      it('should keep ISO-8859-1 filename', function () {
+        assert.equal(contentDisposition('£ rates.pdf', { fallback: false }),
+          'attachment; filename="£ rates.pdf"')
+      })
+    })
+
+    describe('when "true"', function () {
+      it('should generate ISO-8859-1 fallback', function () {
+        assert.equal(contentDisposition('£ and € rates.pdf', { fallback: true }),
+          'attachment; filename="£ and ? rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
+      })
+
+      it('should pass through ISO-8859-1 filename', function () {
+        assert.equal(contentDisposition('£ rates.pdf', { fallback: true }),
+          'attachment; filename="£ rates.pdf"')
+      })
+    })
+
+    describe('when a string', function () {
+      it('should require an ISO-8859-1 string', function () {
+        assert.throws(contentDisposition.bind(null, '€ rates.pdf', { fallback: '€ rates.pdf' }),
+          /fallback.*iso-8859-1/i)
+      })
+
+      it('should use as ISO-8859-1 fallback', function () {
+        assert.equal(contentDisposition('£ and € rates.pdf', { fallback: '£ and EURO rates.pdf' }),
+          'attachment; filename="£ and EURO rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
+      })
+
+      it('should use as fallback even when filename is ISO-8859-1', function () {
+        assert.equal(contentDisposition('"£ rates".pdf', { fallback: '£ rates.pdf' }),
+          'attachment; filename="£ rates.pdf"; filename*=UTF-8\'\'%22%C2%A3%20rates%22.pdf')
+      })
+
+      it('should do nothing if equal to filename', function () {
+        assert.equal(contentDisposition('plans.pdf', { fallback: 'plans.pdf' }),
+          'attachment; filename="plans.pdf"')
+      })
+
+      it('should use the basename of the string', function () {
+        assert.equal(contentDisposition('€ rates.pdf', { fallback: '/path/to/EURO rates.pdf' }),
+          'attachment; filename="EURO rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf')
+      })
+
+      it('should do nothing without filename option', function () {
+        assert.equal(contentDisposition(undefined, { fallback: 'plans.pdf' }),
+          'attachment')
+      })
+    })
+  })
+
+  describe('with "type" option', function () {
+    it('should default to attachment', function () {
+      assert.equal(contentDisposition(),
+        'attachment')
+    })
+
+    it('should require a string', function () {
+      assert.throws(contentDisposition.bind(null, undefined, { type: 42 }),
+        /invalid type/)
+    })
+
+    it('should require a valid type', function () {
+      assert.throws(contentDisposition.bind(null, undefined, { type: 'invlaid;type' }),
+        /invalid type/)
+    })
+
+    it('should create a header with inline type', function () {
+      assert.equal(contentDisposition(undefined, { type: 'inline' }),
+        'inline')
+    })
+
+    it('should create a header with inline type & filename', function () {
+      assert.equal(contentDisposition('plans.pdf', { type: 'inline' }),
+        'inline; filename="plans.pdf"')
+    })
+
+    it('should normalize type', function () {
+      assert.equal(contentDisposition(undefined, { type: 'INLINE' }),
+        'inline')
+    })
+  })
+})
+
+describe('contentDisposition.parse(string)', function () {
+  it('should require string', function () {
+    assert.throws(contentDisposition.parse.bind(null), /argument string.*required/)
+  })
+
+  it('should reject non-strings', function () {
+    assert.throws(contentDisposition.parse.bind(null, 42), /argument string.*required/)
+  })
+
+  describe('with only type', function () {
+    it('should reject quoted value', function () {
+      assert.throws(contentDisposition.parse.bind(null, '"attachment"'),
+        /invalid type format/)
+    })
+
+    it('should reject trailing semicolon', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment;'),
+        /invalid.*format/)
+    })
+
+    it('should parse "attachment"', function () {
+      assert.deepEqual(contentDisposition.parse('attachment'), {
+        type: 'attachment',
+        parameters: {}
+      })
+    })
+
+    it('should parse "inline"', function () {
+      assert.deepEqual(contentDisposition.parse('inline'), {
+        type: 'inline',
+        parameters: {}
+      })
+    })
+
+    it('should parse "form-data"', function () {
+      assert.deepEqual(contentDisposition.parse('form-data'), {
+        type: 'form-data',
+        parameters: {}
+      })
+    })
+
+    it('should parse with trailing LWS', function () {
+      assert.deepEqual(contentDisposition.parse('attachment   '), {
+        type: 'attachment',
+        parameters: {}
+      })
+    })
+
+    it('should normalize to lower-case', function () {
+      assert.deepEqual(contentDisposition.parse('ATTACHMENT'), {
+        type: 'attachment',
+        parameters: {}
+      })
+    })
+  })
+
+  describe('with parameters', function () {
+    it('should reject trailing semicolon', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename="rates.pdf";'),
+        /invalid parameter format/)
+    })
+
+    it('should reject invalid parameter name', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename@="rates.pdf"'),
+        /invalid parameter format/)
+    })
+
+    it('should reject missing parameter value', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename='),
+        /invalid parameter format/)
+    })
+
+    it('should reject invalid parameter value', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=trolly,trains'),
+        /invalid parameter format/)
+    })
+
+    it('should reject invalid parameters', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=total/; foo=bar'),
+        /invalid parameter format/)
+    })
+
+    it('should reject duplicate parameters', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo; filename=bar'),
+        /invalid duplicate parameter/)
+    })
+
+    it('should reject missing type', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'filename="plans.pdf"'),
+        /invalid type format/)
+      assert.throws(contentDisposition.parse.bind(null, '; filename="plans.pdf"'),
+        /invalid type format/)
+    })
+
+    it('should lower-case parameter name', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; FILENAME="plans.pdf"'), {
+        type: 'attachment',
+        parameters: { filename: 'plans.pdf' }
+      })
+    })
+
+    it('should parse quoted parameter value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename="plans.pdf"'), {
+        type: 'attachment',
+        parameters: { filename: 'plans.pdf' }
+      })
+    })
+
+    it('should parse & unescape quoted value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename="the \\"plans\\".pdf"'), {
+        type: 'attachment',
+        parameters: { filename: 'the "plans".pdf' }
+      })
+    })
+
+    it('should include all parameters', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename="plans.pdf"; foo=bar'), {
+        type: 'attachment',
+        parameters: { filename: 'plans.pdf', foo: 'bar' }
+      })
+    })
+
+    it('should parse token filename', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename=plans.pdf'), {
+        type: 'attachment',
+        parameters: { filename: 'plans.pdf' }
+      })
+    })
+
+    it('should parse ISO-8859-1 filename', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename="£ rates.pdf"'), {
+        type: 'attachment',
+        parameters: { filename: '£ rates.pdf' }
+      })
+    })
+  })
+
+  describe('with extended parameters', function () {
+    it('should reject quoted extended parameter value', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*="UTF-8\'\'%E2%82%AC%20rates.pdf"'),
+        /invalid extended.*value/)
+    })
+
+    it('should parse UTF-8 extended parameter value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+    })
+
+    it('should parse UTF-8 extended parameter value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'%E4%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '\ufffd rates.pdf' }
+      })
+    })
+
+    it('should parse ISO-8859-1 extended parameter value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=ISO-8859-1\'\'%A3%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '£ rates.pdf' }
+      })
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=ISO-8859-1\'\'%82%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '? rates.pdf' }
+      })
+    })
+
+    it('should not be case-sensitive for charser', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=utf-8\'\'%E2%82%AC%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+    })
+
+    it('should reject unsupported charset', function () {
+      assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*=ISO-8859-2\'\'%A4%20rates.pdf'),
+        /unsupported charset/)
+    })
+
+    it('should parse with embedded language', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'en\'%E2%82%AC%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+    })
+
+    it('should prefer extended parameter value', function () {
+      assert.deepEqual(contentDisposition.parse('attachment; filename="EURO rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+      assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf; filename="EURO rates.pdf"'), {
+        type: 'attachment',
+        parameters: { 'filename': '€ rates.pdf' }
+      })
+    })
+  })
+
+  describe('from TC 2231', function () {
+    describe('Disposition-Type Inline', function () {
+      it('should parse "inline"', function () {
+        assert.deepEqual(contentDisposition.parse('inline'), {
+          type: 'inline',
+          parameters: {}
+        })
+      })
+
+      it('should reject ""inline""', function () {
+        assert.throws(contentDisposition.parse.bind(null, '"inline"'),
+          /invalid type format/)
+      })
+
+      it('should parse "inline; filename="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('inline; filename="foo.html"'), {
+          type: 'inline',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should parse "inline; filename="Not an attachment!""', function () {
+        assert.deepEqual(contentDisposition.parse('inline; filename="Not an attachment!"'), {
+          type: 'inline',
+          parameters: { filename: 'Not an attachment!' }
+        })
+      })
+
+      it('should parse "inline; filename="foo.pdf""', function () {
+        assert.deepEqual(contentDisposition.parse('inline; filename="foo.pdf"'), {
+          type: 'inline',
+          parameters: { filename: 'foo.pdf' }
+        })
+      })
+    })
+
+    describe('Disposition-Type Attachment', function () {
+      it('should parse "attachment"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment'), {
+          type: 'attachment',
+          parameters: {}
+        })
+      })
+
+      it('should reject ""attachment""', function () {
+        assert.throws(contentDisposition.parse.bind(null, '"attachment"'),
+          /invalid type format/)
+      })
+
+      it('should parse "ATTACHMENT"', function () {
+        assert.deepEqual(contentDisposition.parse('ATTACHMENT'), {
+          type: 'attachment',
+          parameters: {}
+        })
+      })
+
+      it('should parse "attachment; filename="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="0000000000111111111122222""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="0000000000111111111122222"'), {
+          type: 'attachment',
+          parameters: { filename: '0000000000111111111122222' }
+        })
+      })
+
+      it('should parse "attachment; filename="00000000001111111111222222222233333""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="00000000001111111111222222222233333"'), {
+          type: 'attachment',
+          parameters: { filename: '00000000001111111111222222222233333' }
+        })
+      })
+
+      it('should parse "attachment; filename="f\\oo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="f\\oo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="\\"quoting\\" tested.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="\\"quoting\\" tested.html"'), {
+          type: 'attachment',
+          parameters: { filename: '"quoting" tested.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="Here\'s a semicolon;.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="Here\'s a semicolon;.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'Here\'s a semicolon;.html' }
+        })
+      })
+
+      it('should parse "attachment; foo="bar"; filename="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; foo="bar"; filename="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html', foo: 'bar' }
+        })
+      })
+
+      it('should parse "attachment; foo="\\"\\\\";filename="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; foo="\\"\\\\";filename="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html', foo: '"\\' }
+        })
+      })
+
+      it('should parse "attachment; FILENAME="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; FILENAME="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should parse "attachment; filename=foo.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename=foo.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should reject "attachment; filename=foo,bar.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo,bar.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo.html ;"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo.html ;'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; ;filename=foo"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; ;filename=foo'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo bar.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo bar.html'),
+          /invalid parameter format/)
+      })
+
+      it('should parse "attachment; filename=\'foo.bar\'', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename=\'foo.bar\''), {
+          type: 'attachment',
+          parameters: { filename: '\'foo.bar\'' }
+        })
+      })
+
+      it('should parse "attachment; filename="foo-ä.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-ä.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="foo-ä.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-ä.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="foo-%41.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-%41.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-%41.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="50%.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="50%.html"'), {
+          type: 'attachment',
+          parameters: { filename: '50%.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="foo-%\\41.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-%\\41.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-%41.html' }
+        })
+      })
+
+      it('should parse "attachment; name="foo-%41.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; name="foo-%41.html"'), {
+          type: 'attachment',
+          parameters: { name: 'foo-%41.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="ä-%41.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="ä-%41.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'ä-%41.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="foo-%c3%a4-%e2%82%ac.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-%c3%a4-%e2%82%ac.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-%c3%a4-%e2%82%ac.html' }
+        })
+      })
+
+      it('should parse "attachment; filename ="foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename ="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html' }
+        })
+      })
+
+      it('should reject "attachment; filename="foo.html"; filename="bar.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename="foo.html"; filename="bar.html"'),
+          /invalid duplicate parameter/)
+      })
+
+      it('should reject "attachment; filename=foo[1](2).html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo[1](2).html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo-ä.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo-ä.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo-ä.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo-ä.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "filename=foo.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'filename=foo.html'),
+          /invalid type format/)
+      })
+
+      it('should reject "x=y; filename=foo.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'x=y; filename=foo.html'),
+          /invalid type format/)
+      })
+
+      it('should reject ""foo; filename=bar;baz"; filename=qux"', function () {
+        assert.throws(contentDisposition.parse.bind(null, '"foo; filename=bar;baz"; filename=qux'),
+          /invalid type format/)
+      })
+
+      it('should reject "filename=foo.html, filename=bar.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'filename=foo.html, filename=bar.html'),
+          /invalid type format/)
+      })
+
+      it('should reject "; filename=foo.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, '; filename=foo.html'),
+          /invalid type format/)
+      })
+
+      it('should reject ": inline; attachment; filename=foo.html', function () {
+        assert.throws(contentDisposition.parse.bind(null, ': inline; attachment; filename=foo.html'),
+          /invalid type format/)
+      })
+
+      it('should reject "inline; attachment; filename=foo.html', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'inline; attachment; filename=foo.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; inline; filename=foo.html', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; inline; filename=foo.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename="foo.html".txt', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename="foo.html".txt'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename="bar', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename="bar'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo"bar;baz"qux', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo"bar;baz"qux'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=foo.html, attachment; filename=bar.html', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=foo.html, attachment; filename=bar.html'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; foo=foo filename=bar', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; foo=foo filename=bar'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment; filename=bar foo=foo', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename=bar foo=foo'),
+          /invalid parameter format/)
+      })
+
+      it('should reject "attachment filename=bar', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment filename=bar'),
+          /invalid type format/)
+      })
+
+      it('should reject "filename=foo.html; attachment', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'filename=foo.html; attachment'),
+          /invalid type format/)
+      })
+
+      it('should parse "attachment; xfilename=foo.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; xfilename=foo.html'), {
+          type: 'attachment',
+          parameters: { xfilename: 'foo.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="/foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="/foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: '/foo.html' }
+        })
+      })
+
+      it('should parse "attachment; filename="\\\\foo.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="\\\\foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: '\\foo.html' }
+        })
+      })
+    })
+
+    describe('Additional Parameters', function () {
+      it('should parse "attachment; creation-date="Wed, 12 Feb 1997 16:29:51 -0500""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; creation-date="Wed, 12 Feb 1997 16:29:51 -0500"'), {
+          type: 'attachment',
+          parameters: { 'creation-date': 'Wed, 12 Feb 1997 16:29:51 -0500' }
+        })
+      })
+
+      it('should parse "attachment; modification-date="Wed, 12 Feb 1997 16:29:51 -0500""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"'), {
+          type: 'attachment',
+          parameters: { 'modification-date': 'Wed, 12 Feb 1997 16:29:51 -0500' }
+        })
+      })
+    })
+
+    describe('Disposition-Type Extension', function () {
+      it('should parse "foobar"', function () {
+        assert.deepEqual(contentDisposition.parse('foobar'), {
+          type: 'foobar',
+          parameters: {}
+        })
+      })
+
+      it('should parse "attachment; example="filename=example.txt""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; example="filename=example.txt"'), {
+          type: 'attachment',
+          parameters: { example: 'filename=example.txt' }
+        })
+      })
+    })
+
+    describe('RFC 2231/5987 Encoding: Character Sets', function () {
+      it('should parse "attachment; filename*=iso-8859-1\'\'foo-%E4.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=iso-8859-1\'\'foo-%E4.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*=UTF-8\'\'foo-%c3%a4-%e2%82%ac.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'foo-%c3%a4-%e2%82%ac.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä-€.html' }
+        })
+      })
+
+      it('should reject "attachment; filename*=\'\'foo-%c3%a4-%e2%82%ac.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*=\'\'foo-%c3%a4-%e2%82%ac.html'),
+          /invalid extended.*value/)
+      })
+
+      it('should parse "attachment; filename*=UTF-8\'\'foo-a%cc%88.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'foo-a%cc%88.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*=iso-8859-1\'\'foo-%c3%a4-%e2%82%ac.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=iso-8859-1\'\'foo-%c3%a4-%e2%82%ac.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä-�.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*=utf-8\'\'foo-%E4.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=utf-8\'\'foo-%E4.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-\ufffd.html' }
+        })
+      })
+
+      it('should reject "attachment; filename *=UTF-8\'\'foo-%c3%a4.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename *=UTF-8\'\'foo-%c3%a4.html'),
+          /invalid parameter format/)
+      })
+
+      it('should parse "attachment; filename*= UTF-8\'\'foo-%c3%a4.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*= UTF-8\'\'foo-%c3%a4.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename* =UTF-8\'\'foo-%c3%a4.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename* =UTF-8\'\'foo-%c3%a4.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should reject "attachment; filename*="UTF-8\'\'foo-%c3%a4.html""', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*="UTF-8\'\'foo-%c3%a4.html"'),
+          /invalid extended field value/)
+      })
+
+      it('should reject "attachment; filename*="foo%20bar.html""', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*="foo%20bar.html"'),
+          /invalid extended field value/)
+      })
+
+      it('should reject "attachment; filename*=UTF-8\'foo-%c3%a4.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*=UTF-8\'foo-%c3%a4.html'),
+          /invalid extended field value/)
+      })
+
+      it('should reject "attachment; filename*=UTF-8\'\'foo%"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*=UTF-8\'\'foo%'),
+          /invalid extended field value/)
+      })
+
+      it('should reject "attachment; filename*=UTF-8\'\'f%oo.html"', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename*=UTF-8\'\'f%oo.html'),
+          /invalid extended field value/)
+      })
+
+      it('should parse "attachment; filename*=UTF-8\'\'A-%2541.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'A-%2541.html'), {
+          type: 'attachment',
+          parameters: { filename: 'A-%41.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*=UTF-8\'\'%5cfoo.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'%5cfoo.html'), {
+          type: 'attachment',
+          parameters: { filename: '\\foo.html' }
+        })
+      })
+    })
+
+    describe('RFC2231 Encoding: Continuations', function () {
+      it('should parse "attachment; filename*0="foo."; filename*1="html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0="foo."; filename*1="html"'), {
+          type: 'attachment',
+          parameters: { 'filename*0': 'foo.', 'filename*1': 'html' }
+        })
+      })
+
+      it('should parse "attachment; filename*0="foo"; filename*1="\\b\\a\\r.html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0="foo"; filename*1="\\b\\a\\r.html"'), {
+          type: 'attachment',
+          parameters: { 'filename*0': 'foo', 'filename*1': 'bar.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*0*=UTF-8\'\'foo-%c3%a4; filename*1=".html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0*=UTF-8\'\'foo-%c3%a4; filename*1=".html"'), {
+          type: 'attachment',
+          parameters: { 'filename*0*': 'UTF-8\'\'foo-%c3%a4', 'filename*1': '.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*0="foo"; filename*01="bar""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0="foo"; filename*01="bar"'), {
+          type: 'attachment',
+          parameters: { 'filename*0': 'foo', 'filename*01': 'bar' }
+        })
+      })
+
+      it('should parse "attachment; filename*0="foo"; filename*2="bar""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0="foo"; filename*2="bar"'), {
+          type: 'attachment',
+          parameters: { 'filename*0': 'foo', 'filename*2': 'bar' }
+        })
+      })
+
+      it('should parse "attachment; filename*1="foo."; filename*2="html""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*1="foo."; filename*2="html"'), {
+          type: 'attachment',
+          parameters: { 'filename*1': 'foo.', 'filename*2': 'html' }
+        })
+      })
+
+      it('should parse "attachment; filename*1="bar"; filename*0="foo""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*1="bar"; filename*0="foo"'), {
+          type: 'attachment',
+          parameters: { 'filename*1': 'bar', 'filename*0': 'foo' }
+        })
+      })
+    })
+
+    describe('RFC2231 Encoding: Fallback Behaviour', function () {
+      it('should parse "attachment; filename="foo-ae.html"; filename*=UTF-8\'\'foo-%c3%a4.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="foo-ae.html"; filename*=UTF-8\'\'foo-%c3%a4.html'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*=UTF-8\'\'foo-%c3%a4.html; filename="foo-ae.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*=UTF-8\'\'foo-%c3%a4.html; filename="foo-ae.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo-ä.html' }
+        })
+      })
+
+      it('should parse "attachment; filename*0*=ISO-8859-15\'\'euro-sign%3d%a4; filename*=ISO-8859-1\'\'currency-sign%3d%a4', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename*0*=ISO-8859-15\'\'euro-sign%3d%a4; filename*=ISO-8859-1\'\'currency-sign%3d%a4'), {
+          type: 'attachment',
+          parameters: { filename: 'currency-sign=¤', 'filename*0*': 'ISO-8859-15\'\'euro-sign%3d%a4' }
+        })
+      })
+
+      it('should parse "attachment; foobar=x; filename="foo.html"', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; foobar=x; filename="foo.html"'), {
+          type: 'attachment',
+          parameters: { filename: 'foo.html', foobar: 'x' }
+        })
+      })
+    })
+
+    describe('RFC2047 Encoding', function () {
+      it('should reject "attachment; filename==?ISO-8859-1?Q?foo-=E4.html?="', function () {
+        assert.throws(contentDisposition.parse.bind(null, 'attachment; filename==?ISO-8859-1?Q?foo-=E4.html?='),
+          /invalid parameter format/)
+      })
+
+      it('should parse "attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?=""', function () {
+        assert.deepEqual(contentDisposition.parse('attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="'), {
+          type: 'attachment',
+          parameters: { filename: '=?ISO-8859-1?Q?foo-=E4.html?=' }
+        })
+      })
+    })
+  })
+})

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-content-disposition.git



More information about the Pkg-javascript-commits mailing list