avancement planning
This commit is contained in:
-66
@@ -1,66 +0,0 @@
|
||||
1.0.0 / 2024-08-31
|
||||
==================
|
||||
|
||||
* drop node <18
|
||||
* allow utf8 as alias for utf-8
|
||||
|
||||
0.5.4 / 2021-12-10
|
||||
==================
|
||||
|
||||
* deps: safe-buffer@5.2.1
|
||||
|
||||
0.5.3 / 2018-12-17
|
||||
==================
|
||||
|
||||
* Use `safe-buffer` for improved Buffer API
|
||||
|
||||
0.5.2 / 2016-12-08
|
||||
==================
|
||||
|
||||
* Fix `parse` to accept any linear whitespace character
|
||||
|
||||
0.5.1 / 2016-01-17
|
||||
==================
|
||||
|
||||
* perf: enable strict mode
|
||||
|
||||
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
|
||||
+21
-22
@@ -17,7 +17,7 @@ $ npm install content-disposition
|
||||
## API
|
||||
|
||||
```js
|
||||
var contentDisposition = require('content-disposition')
|
||||
const contentDisposition = require('content-disposition')
|
||||
```
|
||||
|
||||
### contentDisposition(filename, options)
|
||||
@@ -67,7 +67,7 @@ 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')
|
||||
const 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
|
||||
@@ -86,13 +86,12 @@ are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-
|
||||
### Send a file for download
|
||||
|
||||
```js
|
||||
var contentDisposition = require('content-disposition')
|
||||
var destroy = require('destroy')
|
||||
var fs = require('fs')
|
||||
var http = require('http')
|
||||
var onFinished = require('on-finished')
|
||||
const contentDisposition = require('content-disposition')
|
||||
const fs = require('fs')
|
||||
const http = require('http')
|
||||
const onFinished = require('on-finished')
|
||||
|
||||
var filePath = '/path/to/public/plans.pdf'
|
||||
const filePath = '/path/to/public/plans.pdf'
|
||||
|
||||
http.createServer(function onRequest (req, res) {
|
||||
// set headers
|
||||
@@ -100,10 +99,10 @@ http.createServer(function onRequest (req, res) {
|
||||
res.setHeader('Content-Disposition', contentDisposition(filePath))
|
||||
|
||||
// send file
|
||||
var stream = fs.createReadStream(filePath)
|
||||
const stream = fs.createReadStream(filePath)
|
||||
stream.pipe(res)
|
||||
onFinished(res, function () {
|
||||
destroy(stream)
|
||||
stream.destroy()
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -121,22 +120,22 @@ $ npm test
|
||||
- [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
|
||||
[rfc-2616]: https://datatracker.ietf.org/doc/html/rfc2616
|
||||
[rfc-5987]: https://datatracker.ietf.org/doc/html/rfc5987
|
||||
[rfc-6266]: https://datatracker.ietf.org/doc/html/rfc6266
|
||||
[tc-2231]: http://greenbytes.de/tech/tc2231/
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/content-disposition.svg
|
||||
[npm-url]: https://npmjs.org/package/content-disposition
|
||||
[node-version-image]: https://img.shields.io/node/v/content-disposition.svg
|
||||
[npm-image]: https://img.shields.io/npm/v/content-disposition
|
||||
[npm-url]: https://www.npmjs.com/package/content-disposition
|
||||
[node-version-image]: https://img.shields.io/node/v/content-disposition
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
|
||||
[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg
|
||||
[downloads-url]: https://npmjs.org/package/content-disposition
|
||||
[github-actions-ci-image]: https://img.shields.io/github/workflow/status/jshttp/content-disposition/ci/master?label=ci
|
||||
[github-actions-ci-url]: https://github.com/jshttp/content-disposition?query=workflow%3Aci
|
||||
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/jshttp/content-disposition
|
||||
[coveralls-url]: https://coveralls.io/github/jshttp/content-disposition?branch=master
|
||||
[downloads-image]: https://img.shields.io/npm/dm/content-disposition
|
||||
[downloads-url]: https://www.npmjs.com/package/content-disposition
|
||||
[github-actions-ci-image]: https://img.shields.io/github/actions/workflow/status/jshttp/content-disposition/ci.yml
|
||||
[github-actions-ci-url]: https://github.com/jshttp/content-disposition/actions/workflows/ci.yml
|
||||
|
||||
+118
-41
@@ -15,12 +15,11 @@ module.exports = contentDisposition
|
||||
module.exports.parse = parse
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* TextDecoder instance for UTF-8 decoding when decodeURIComponent fails due to invalid byte sequences.
|
||||
* @type {TextDecoder}
|
||||
* @private
|
||||
*/
|
||||
|
||||
var basename = require('path').basename
|
||||
var Buffer = require('safe-buffer').Buffer
|
||||
const utf8Decoder = new TextDecoder('utf-8')
|
||||
|
||||
/**
|
||||
* RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
|
||||
@@ -29,14 +28,6 @@ var Buffer = require('safe-buffer').Buffer
|
||||
|
||||
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
|
||||
|
||||
/**
|
||||
* RegExp to match percent encoding escape.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
|
||||
var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
|
||||
|
||||
/**
|
||||
* RegExp to match non-latin1 characters.
|
||||
* @private
|
||||
@@ -200,7 +191,7 @@ function createparams (filename, fallback) {
|
||||
var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
|
||||
|
||||
// set extended filename parameter
|
||||
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
||||
if (hasFallback || !isQuotedString || hasHexEscape(name)) {
|
||||
params['filename*'] = name
|
||||
}
|
||||
|
||||
@@ -263,32 +254,41 @@ function format (obj) {
|
||||
*/
|
||||
|
||||
function decodefield (str) {
|
||||
var match = EXT_VALUE_REGEXP.exec(str)
|
||||
const match = EXT_VALUE_REGEXP.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(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
|
||||
const charset = match[1].toLowerCase()
|
||||
const encoded = match[2]
|
||||
|
||||
switch (charset) {
|
||||
case 'iso-8859-1':
|
||||
value = getlatin1(binary)
|
||||
break
|
||||
{
|
||||
const binary = decodeHexEscapes(encoded)
|
||||
return getlatin1(binary)
|
||||
}
|
||||
case 'utf-8':
|
||||
case 'utf8':
|
||||
value = Buffer.from(binary, 'binary').toString('utf8')
|
||||
break
|
||||
default:
|
||||
throw new TypeError('unsupported charset in extended field')
|
||||
}
|
||||
{
|
||||
try {
|
||||
return decodeURIComponent(encoded)
|
||||
} catch {
|
||||
// Failed to decode with decodeURIComponent, fallback to lenient decoding which replaces invalid UTF-8 byte sequences with the Unicode replacement character
|
||||
// TODO: Consider removing in the next major version to be more strict about invalid percent-encodings
|
||||
const binary = decodeHexEscapes(encoded)
|
||||
|
||||
return value
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
for (let idx = 0; idx < binary.length; idx++) {
|
||||
bytes[idx] = binary.charCodeAt(idx)
|
||||
}
|
||||
|
||||
return utf8Decoder.decode(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new TypeError('unsupported charset in extended field')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,19 +384,6 @@ function parse (string) {
|
||||
return new ContentDisposition(type, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Percent decode a single character.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} hex
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function pdecode (str, hex) {
|
||||
return String.fromCharCode(parseInt(hex, 16))
|
||||
}
|
||||
|
||||
/**
|
||||
* Percent encode a single character.
|
||||
*
|
||||
@@ -457,3 +444,93 @@ function ContentDisposition (type, parameters) {
|
||||
this.type = type
|
||||
this.parameters = parameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last portion of a path
|
||||
*
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
function basename (path) {
|
||||
const normalized = path.replaceAll('\\', '/')
|
||||
|
||||
let end = normalized.length
|
||||
while (end > 0 && normalized[end - 1] === '/') {
|
||||
end--
|
||||
}
|
||||
|
||||
if (end === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let start = end - 1
|
||||
while (start >= 0 && normalized[start] !== '/') {
|
||||
start--
|
||||
}
|
||||
|
||||
return normalized.slice(start + 1, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a hex digit [0-9A-Fa-f]
|
||||
*
|
||||
* @param {string} char
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
function isHexDigit (char) {
|
||||
const code = char.charCodeAt(0)
|
||||
return (
|
||||
(code >= 48 && code <= 57) || // 0-9
|
||||
(code >= 65 && code <= 70) || // A-F
|
||||
(code >= 97 && code <= 102) // a-f
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string contains percent encoding escapes.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
function hasHexEscape (str) {
|
||||
const maxIndex = str.length - 3
|
||||
let lastIndex = -1
|
||||
|
||||
while ((lastIndex = str.indexOf('%', lastIndex + 1)) !== -1 && lastIndex <= maxIndex) {
|
||||
if (isHexDigit(str[lastIndex + 1]) && isHexDigit(str[lastIndex + 2])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hex escapes in a string (e.g., %20 -> space)
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
function decodeHexEscapes (str) {
|
||||
const firstEscape = str.indexOf('%')
|
||||
if (firstEscape === -1) return str
|
||||
|
||||
let result = str.slice(0, firstEscape)
|
||||
for (let idx = firstEscape; idx < str.length; idx++) {
|
||||
if (
|
||||
str[idx] === '%' &&
|
||||
idx + 2 < str.length &&
|
||||
isHexDigit(str[idx + 1]) &&
|
||||
isHexDigit(str[idx + 2])
|
||||
) {
|
||||
result += String.fromCharCode(Number.parseInt(str[idx + 1] + str[idx + 2], 16))
|
||||
idx += 2
|
||||
} else {
|
||||
result += str[idx]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+16
-20
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "content-disposition",
|
||||
"description": "Create and parse Content-Disposition header",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -11,34 +11,30 @@
|
||||
"res"
|
||||
],
|
||||
"repository": "jshttp/content-disposition",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
},
|
||||
"devDependencies": {
|
||||
"deep-equal": "1.0.1",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-standard": "13.0.1",
|
||||
"eslint-plugin-import": "2.25.3",
|
||||
"eslint-plugin-markdown": "2.2.1",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "5.2.0",
|
||||
"eslint-plugin-standard": "4.1.0",
|
||||
"mocha": "^9.2.2",
|
||||
"nyc": "15.1.0"
|
||||
"c8": "^10.1.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-markdown": "^3.0.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.6.0",
|
||||
"eslint-plugin-standard": "^4.1.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"README.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --bail --check-leaks test/",
|
||||
"test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||
"test": "node --test --test-reporter spec",
|
||||
"test-ci": "c8 --reporter=lcovonly --reporter=text npm test",
|
||||
"test-cov": "c8 --reporter=html --reporter=text npm test"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user