mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 07:15:06 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4ab7d65d7 | ||
|
|
c4cc78bdf5 | ||
|
|
925a1dff1e | ||
|
|
9c85a25c02 | ||
|
|
1140301f6a | ||
|
|
c76ed5ae05 | ||
|
|
2d4192ebb3 | ||
|
|
66404b347a | ||
|
|
d12772393c | ||
|
|
6b7ccfcf12 | ||
|
|
c9ecf7b658 | ||
|
|
a479419b16 | ||
|
|
5a4568abfe | ||
|
|
912893c07c | ||
|
|
ae265a90c7 | ||
|
|
9a3f7ff412 | ||
|
|
2cd372e34c | ||
|
|
04d3a49976 | ||
|
|
bc7d155f53 | ||
|
|
00bb633ca6 | ||
|
|
3c0ad4e8dc | ||
|
|
4ae96bdf5e | ||
|
|
6cd404eb28 | ||
|
|
3e81873b52 | ||
|
|
b5aae87594 | ||
|
|
b8fc000f31 | ||
|
|
c2fb76e99f | ||
|
|
9eb700151b |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -27,11 +27,11 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
@@ -53,12 +53,12 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
run: npm run test-ci
|
||||
|
||||
- name: Upload code coverage
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }}
|
||||
path: ./coverage/lcov.info
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
contents: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
run: sudo apt-get -y install lcov
|
||||
|
||||
- name: Collect coverage reports
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
path: ./coverage
|
||||
pattern: coverage-node-*
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -39,13 +39,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
|
||||
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config: |
|
||||
@@ -71,4 +71,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
|
||||
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
|
||||
10
.github/workflows/legacy.yml
vendored
10
.github/workflows/legacy.yml
vendored
@@ -37,12 +37,12 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
run: npm run test-ci
|
||||
|
||||
- name: Upload code coverage
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }}
|
||||
path: ./coverage/lcov.info
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
contents: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
run: sudo apt-get -y install lcov
|
||||
|
||||
- name: Collect coverage reports
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
path: ./coverage
|
||||
pattern: coverage-node-*
|
||||
|
||||
6
.github/workflows/scorecard.yml
vendored
6
.github/workflows/scorecard.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -67,6 +67,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
17
History.md
17
History.md
@@ -1,7 +1,24 @@
|
||||
# Unreleased Changes
|
||||
|
||||
## 🚀 Improvements
|
||||
|
||||
* Improve HTML structure in `res.redirect()` responses when HTML format is accepted by adding `<!DOCTYPE html>`, `<title>`, and `<body>` tags for better browser compatibility - by [@Bernice55231](https://github.com/Bernice55231) in [#5167](https://github.com/expressjs/express/pull/5167)
|
||||
|
||||
* When calling `app.render` with options set to null, the locals object is handled correctly, preventing unexpected errors and making the method behave the same as when options is omitted or an empty object is passed - by [AkaHarshit](https://github.com/AkaHarshit) in [#6903](https://github.com/expressjs/express/pull/6903)
|
||||
|
||||
```js
|
||||
app.render('index', null, callback); // now works as expected
|
||||
```
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
* Avoid duplicate Content-Type header processing in `res.send()` when sending string responses without an explicit Content-Type header - by [@bjohansebas](https://github.com/bjohansebas) in [#6991](https://github.com/expressjs/express/pull/6991)
|
||||
|
||||
5.2.1 / 2025-12-01
|
||||
=======================
|
||||
|
||||
* Revert security fix for [CVE-2024-51999](https://www.cve.org/CVERecord?id=CVE-2024-51999) ([GHSA-pj86-cfqh-vqx6](https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6))
|
||||
* The prior release (5.2.0) included an erroneous breaking change related to the extended query parser. There is no actual security vulnerability associated with this behavior (CVE-2024-51999 has been rejected). The change has been fully reverted in this release.
|
||||
|
||||
5.2.0 / 2025-12-01
|
||||
========================
|
||||
|
||||
@@ -154,7 +154,7 @@ See the [Contributing Guide] for more technical details on contributing.
|
||||
|
||||
### Security Issues
|
||||
|
||||
If you discover a security vulnerability in Express, please see [Security Policies and Procedures](SECURITY.md).
|
||||
If you discover a security vulnerability in Express, please see [Security Policies and Procedures](https://github.com/expressjs/express/security/policy).
|
||||
|
||||
### Running Tests
|
||||
|
||||
@@ -212,7 +212,9 @@ The original author of Express is [TJ Holowaychuk](https://github.com/tj)
|
||||
* [IamLizu](https://github.com/IamLizu) - **S M Mahmudul Hasan** (he/him)
|
||||
* [Phillip9587](https://github.com/Phillip9587) - **Phillip Barta**
|
||||
* [efekrskl](https://github.com/efekrskl) - **Efe Karasakal**
|
||||
|
||||
* [rxmarbles](https://github.com/rxmarbles) - **Rick Markins** (he/him)
|
||||
* [krzysdz](https://github.com/krzysdz)
|
||||
* [GroophyLifefor](https://github.com/GroophyLifefor) - **Murat Kirazkaya**
|
||||
|
||||
<details>
|
||||
<summary>Triagers emeriti members</summary>
|
||||
|
||||
56
SECURITY.md
56
SECURITY.md
@@ -1,56 +0,0 @@
|
||||
# Security Policies and Procedures
|
||||
|
||||
This document outlines security procedures and general policies for the Express
|
||||
project.
|
||||
|
||||
* [Reporting a Bug](#reporting-a-bug)
|
||||
* [Disclosure Policy](#disclosure-policy)
|
||||
* [Comments on this Policy](#comments-on-this-policy)
|
||||
|
||||
## Reporting a Bug
|
||||
|
||||
The Express team and community take all security bugs in Express seriously.
|
||||
Thank you for improving the security of Express. We appreciate your efforts and
|
||||
responsible disclosure and will make every effort to acknowledge your
|
||||
contributions.
|
||||
|
||||
Report security bugs by emailing `express-security@lists.openjsf.org`.
|
||||
|
||||
To ensure the timely response to your report, please ensure that the entirety
|
||||
of the report is contained within the email body and not solely behind a web
|
||||
link or an attachment.
|
||||
|
||||
The lead maintainer will acknowledge your email within 48 hours, and will send a
|
||||
more detailed response within 48 hours indicating the next steps in handling
|
||||
your report. After the initial reply to your report, the security team will
|
||||
endeavor to keep you informed of the progress towards a fix and full
|
||||
announcement, and may ask for additional information or guidance.
|
||||
|
||||
Report security bugs in third-party modules to the person or team maintaining
|
||||
the module.
|
||||
|
||||
## Pre-release Versions
|
||||
|
||||
Alpha and Beta releases are unstable and **not suitable for production use**.
|
||||
Vulnerabilities found in pre-releases should be reported according to the [Reporting a Bug](#reporting-a-bug) section.
|
||||
Due to the unstable nature of the branch it is not guaranteed that any fixes will be released in the next pre-release.
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
When the security team receives a security bug report, they will assign it to a
|
||||
primary handler. This person will coordinate the fix and release process,
|
||||
involving the following steps:
|
||||
|
||||
* Confirm the problem and determine the affected versions.
|
||||
* Audit code to find any potential similar problems.
|
||||
* Prepare fixes for all releases still under maintenance. These fixes will be
|
||||
released as fast as possible to npm.
|
||||
|
||||
## The Express Threat Model
|
||||
|
||||
We are currently working on a new version of the security model, the most updated version can be found [here](https://github.com/expressjs/security-wg/blob/main/docs/ThreatModel.md)
|
||||
|
||||
## Comments on this Policy
|
||||
|
||||
If you have suggestions on how this process could be improved please submit a
|
||||
pull request.
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
all:
|
||||
@./run 1 middleware 50
|
||||
@./run 5 middleware 50
|
||||
@./run 10 middleware 50
|
||||
@./run 15 middleware 50
|
||||
@./run 20 middleware 50
|
||||
@./run 30 middleware 50
|
||||
@./run 50 middleware 50
|
||||
@./run 100 middleware 50
|
||||
@./run 10 middleware 100
|
||||
@./run 10 middleware 250
|
||||
@./run 10 middleware 500
|
||||
@./run 10 middleware 1000
|
||||
@echo
|
||||
|
||||
.PHONY: all
|
||||
@@ -1,34 +0,0 @@
|
||||
# Express Benchmarks
|
||||
|
||||
## Installation
|
||||
|
||||
You will need to install [wrk](https://github.com/wg/wrk/blob/master/INSTALL) in order to run the benchmarks.
|
||||
|
||||
## Running
|
||||
|
||||
To run the benchmarks, first install the dependencies `npm i`, then run `make`
|
||||
|
||||
The output will look something like this:
|
||||
|
||||
```
|
||||
50 connections
|
||||
1 middleware
|
||||
7.15ms
|
||||
6784.01
|
||||
|
||||
[...redacted...]
|
||||
|
||||
1000 connections
|
||||
10 middleware
|
||||
139.21ms
|
||||
6155.19
|
||||
|
||||
```
|
||||
|
||||
### Tip: Include Node.js version in output
|
||||
|
||||
You can use `make && node -v` to include the node.js version in the output.
|
||||
|
||||
### Tip: Save the results to a file
|
||||
|
||||
You can use `make > results.log` to save the results to a file `results.log`.
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
var express = require('..');
|
||||
var app = express();
|
||||
|
||||
// number of middleware
|
||||
|
||||
var n = parseInt(process.env.MW || '1', 10);
|
||||
console.log(' %s middleware', n);
|
||||
|
||||
while (n--) {
|
||||
app.use(function(req, res, next){
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send('Hello World')
|
||||
});
|
||||
|
||||
app.listen(3333);
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo
|
||||
MW=$1 node $2 &
|
||||
pid=$!
|
||||
|
||||
echo " $3 connections"
|
||||
|
||||
sleep 2
|
||||
|
||||
wrk 'http://localhost:3333/?foo[bar]=baz' \
|
||||
-d 3 \
|
||||
-c $3 \
|
||||
-t 8 \
|
||||
| grep 'Requests/sec\|Latency' \
|
||||
| awk '{ print " " $2 }'
|
||||
|
||||
kill $pid
|
||||
@@ -16,31 +16,47 @@ var path = require('node:path');
|
||||
var redis = require('redis');
|
||||
|
||||
var db = redis.createClient();
|
||||
|
||||
// npm install redis
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// populate search
|
||||
// npm install redis
|
||||
|
||||
db.sadd('ferret', 'tobi');
|
||||
db.sadd('ferret', 'loki');
|
||||
db.sadd('ferret', 'jane');
|
||||
db.sadd('cat', 'manny');
|
||||
db.sadd('cat', 'luna');
|
||||
/**
|
||||
* Redis Initialization
|
||||
*/
|
||||
|
||||
async function initializeRedis() {
|
||||
try {
|
||||
// connect to Redis
|
||||
|
||||
await db.connect();
|
||||
|
||||
// populate search
|
||||
|
||||
await db.sAdd('ferret', 'tobi');
|
||||
await db.sAdd('ferret', 'loki');
|
||||
await db.sAdd('ferret', 'jane');
|
||||
await db.sAdd('cat', 'manny');
|
||||
await db.sAdd('cat', 'luna');
|
||||
} catch (err) {
|
||||
console.error('Error initializing Redis:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET search for :query.
|
||||
*/
|
||||
|
||||
app.get('/search/:query?', function(req, res, next){
|
||||
var query = req.params.query;
|
||||
db.smembers(query, function(err, vals){
|
||||
if (err) return next(err);
|
||||
res.send(vals);
|
||||
});
|
||||
app.get('/search/{:query}', function (req, res, next) {
|
||||
var query = req.params.query || '';
|
||||
db.sMembers(query)
|
||||
.then((vals) => res.send(vals))
|
||||
.catch((err) => {
|
||||
console.error(`Redis error for query "${query}":`, err);
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -54,8 +70,14 @@ app.get('/client.js', function(req, res){
|
||||
res.sendFile(path.join(__dirname, 'client.js'));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
/**
|
||||
* Start the Server
|
||||
*/
|
||||
|
||||
(async () => {
|
||||
await initializeRedis();
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -523,7 +523,7 @@ app.render = function render(name, options, callback) {
|
||||
var cache = this.cache;
|
||||
var done = callback;
|
||||
var engines = this.engines;
|
||||
var opts = options;
|
||||
var opts = options || {};
|
||||
var view;
|
||||
|
||||
// support callback function as second arg
|
||||
|
||||
@@ -83,16 +83,13 @@ req.header = function header(name) {
|
||||
};
|
||||
|
||||
/**
|
||||
* To do: update docs.
|
||||
*
|
||||
* Check if the given `type(s)` is acceptable, returning
|
||||
* the best match when true, otherwise `undefined`, in which
|
||||
* the best match when true, otherwise `false`, in which
|
||||
* case you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* The `type` value may be a single MIME type string
|
||||
* such as "application/json", an extension name
|
||||
* such as "json", a comma-delimited list such as "json, html, text/plain",
|
||||
* an argument list such as `"json", "html", "text/plain"`,
|
||||
* such as "json", an argument list such as `"json", "html", "text/plain"`,
|
||||
* or an array `["json", "html", "text/plain"]`. When a list
|
||||
* or array is given, the _best_ match, if any is returned.
|
||||
*
|
||||
@@ -107,7 +104,7 @@ req.header = function header(name) {
|
||||
* // => "html"
|
||||
* req.accepts('text/html');
|
||||
* // => "text/html"
|
||||
* req.accepts('json, text');
|
||||
* req.accepts('json', 'text');
|
||||
* // => "json"
|
||||
* req.accepts('application/json');
|
||||
* // => "application/json"
|
||||
@@ -115,12 +112,11 @@ req.header = function header(name) {
|
||||
* // Accept: text/*, application/json
|
||||
* req.accepts('image/png');
|
||||
* req.accepts('png');
|
||||
* // => undefined
|
||||
* // => false
|
||||
*
|
||||
* // Accept: text/*;q=.5, application/json
|
||||
* req.accepts(['html', 'json']);
|
||||
* req.accepts('html', 'json');
|
||||
* req.accepts('html, json');
|
||||
* // => "json"
|
||||
*
|
||||
* @param {String|Array} type(s)
|
||||
@@ -147,17 +143,34 @@ req.acceptsEncodings = function(){
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given `charset`s are acceptable,
|
||||
* otherwise you should respond with 406 "Not Acceptable".
|
||||
* Checks if the specified `charset`s are acceptable based on the request's `Accept-Charset` header.
|
||||
* Returns the best matching charset or an array of acceptable charsets.
|
||||
*
|
||||
* @param {String} ...charset
|
||||
* @return {String|Array}
|
||||
* The `charset` argument(s) can be:
|
||||
* - A single charset string (e.g., "utf-8")
|
||||
* - Multiple charset strings as arguments (e.g., `"utf-8", "iso-8859-1"`)
|
||||
* - A comma-delimited list of charsets (e.g., `"utf-8, iso-8859-1"`)
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // Accept-Charset: utf-8, iso-8859-1
|
||||
* req.acceptsCharsets('utf-8');
|
||||
* // => "utf-8"
|
||||
*
|
||||
* req.acceptsCharsets('utf-8', 'iso-8859-1');
|
||||
* // => "utf-8"
|
||||
*
|
||||
* req.acceptsCharsets('utf-8, utf-16');
|
||||
* // => "utf-8"
|
||||
*
|
||||
* @param {...String} charsets - The charset(s) to check against the `Accept-Charset` header.
|
||||
* @return {String|Array} - The best matching charset, or an array of acceptable charsets.
|
||||
* @public
|
||||
*/
|
||||
|
||||
req.acceptsCharsets = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.charsets.apply(accept, arguments);
|
||||
req.acceptsCharsets = function(...charsets) {
|
||||
const accept = accepts(this);
|
||||
return accept.charsets(...charsets);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,7 +126,6 @@ res.send = function send(body) {
|
||||
var chunk = body;
|
||||
var encoding;
|
||||
var req = this.req;
|
||||
var type;
|
||||
|
||||
// settings
|
||||
var app = this.app;
|
||||
@@ -134,7 +133,12 @@ res.send = function send(body) {
|
||||
switch (typeof chunk) {
|
||||
// string defaulting to html
|
||||
case 'string':
|
||||
if (!this.get('Content-Type')) {
|
||||
encoding = 'utf8';
|
||||
const type = this.get('Content-Type');
|
||||
|
||||
if (typeof type === 'string') {
|
||||
this.set('Content-Type', setCharset(type, 'utf-8'));
|
||||
} else {
|
||||
this.type('html');
|
||||
}
|
||||
break;
|
||||
@@ -153,17 +157,6 @@ res.send = function send(body) {
|
||||
break;
|
||||
}
|
||||
|
||||
// write strings in utf-8
|
||||
if (typeof chunk === 'string') {
|
||||
encoding = 'utf8';
|
||||
type = this.get('Content-Type');
|
||||
|
||||
// reflect this in content-type
|
||||
if (typeof type === 'string') {
|
||||
this.set('Content-Type', setCharset(type, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
// determine if ETag should be generated
|
||||
var etagFn = app.get('etag fn')
|
||||
var generateETag = !this.get('ETag') && typeof etagFn === 'function'
|
||||
@@ -850,7 +843,8 @@ res.redirect = function redirect(url) {
|
||||
|
||||
html: function(){
|
||||
var u = escapeHtml(address);
|
||||
body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
|
||||
body = '<!DOCTYPE html><head><title>' + statuses.message[status] + '</title></head>'
|
||||
+ '<body><p>' + statuses.message[status] + '. Redirecting to ' + u + '</p></body>'
|
||||
},
|
||||
|
||||
default: function(){
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"qs": "^6.14.2",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
|
||||
@@ -331,6 +331,24 @@ describe('app', function(){
|
||||
})
|
||||
})
|
||||
|
||||
it('should accept null or undefined options', function (done) {
|
||||
var app = createApp()
|
||||
|
||||
app.set('views', path.join(__dirname, 'fixtures'))
|
||||
app.locals.user = { name: 'tobi' }
|
||||
|
||||
app.render('user.tmpl', null, function (err, str) {
|
||||
if (err) return done(err);
|
||||
assert.strictEqual(str, '<p>tobi</p>')
|
||||
|
||||
app.render('user.tmpl', undefined, function (err2, str2) {
|
||||
if (err2) return done(err2);
|
||||
assert.strictEqual(str2, '<p>tobi</p>')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('caching', function(){
|
||||
it('should cache with cache option', function(done){
|
||||
var app = express();
|
||||
|
||||
@@ -45,6 +45,19 @@ describe('req', function(){
|
||||
.set('Accept-Charset', 'foo, bar')
|
||||
.expect('no', done);
|
||||
})
|
||||
|
||||
it('should return the best matching charset from multiple inputs', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.end(req.acceptsCharsets('utf-8', 'iso-8859-1'));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Charset', 'iso-8859-1, utf-8')
|
||||
.expect('iso-8859-1', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -327,18 +327,4 @@ describe('res', function(){
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not override previous Content-Types', function(done){
|
||||
var app = express();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.type('application/vnd.example+json');
|
||||
res.jsonp({ hello: 'world' });
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('content-type', 'application/vnd.example+json; charset=utf-8')
|
||||
.expect(200, '{"hello":"world"}', done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,19 +46,7 @@ describe('res', function(){
|
||||
.expect(200, done)
|
||||
})
|
||||
|
||||
it('should encode data uri1', function (done) {
|
||||
var app = express()
|
||||
app.use(function (req, res) {
|
||||
res.location('data:text/javascript,export default () => { }').end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Location', 'data:text/javascript,export%20default%20()%20=%3E%20%7B%20%7D')
|
||||
.expect(200, done)
|
||||
})
|
||||
|
||||
it('should encode data uri2', function (done) {
|
||||
it('should encode data uri', function (done) {
|
||||
var app = express()
|
||||
app.use(function (req, res) {
|
||||
res.location('data:text/javascript,export default () => { }').end();
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('res', function(){
|
||||
.set('Accept', 'text/html')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect('Location', 'http://google.com')
|
||||
.expect(302, '<p>Found. Redirecting to http://google.com</p>', done)
|
||||
.expect(302, '<!DOCTYPE html><head><title>Found</title></head><body><p>Found. Redirecting to http://google.com</p></body>', done)
|
||||
})
|
||||
|
||||
it('should escape the url', function(done){
|
||||
@@ -107,7 +107,7 @@ describe('res', function(){
|
||||
.set('Accept', 'text/html')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect('Location', '%3Cla\'me%3E')
|
||||
.expect(302, '<p>Found. Redirecting to %3Cla'me%3E</p>', done)
|
||||
.expect(302, '<!DOCTYPE html><head><title>Found</title></head><body><p>Found. Redirecting to %3Cla'me%3E</p></body>', done)
|
||||
})
|
||||
|
||||
it('should not render evil javascript links in anchor href (prevent XSS)', function(done){
|
||||
@@ -125,7 +125,7 @@ describe('res', function(){
|
||||
.set('Accept', 'text/html')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect('Location', encodedXss)
|
||||
.expect(302, '<p>Found. Redirecting to ' + encodedXss +'</p>', done);
|
||||
.expect(302, '<!DOCTYPE html><head><title>Found</title></head><body><p>Found. Redirecting to ' + encodedXss +'</p></body>', done);
|
||||
});
|
||||
|
||||
it('should include the redirect type', function(done){
|
||||
@@ -140,7 +140,7 @@ describe('res', function(){
|
||||
.set('Accept', 'text/html')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect('Location', 'http://google.com')
|
||||
.expect(301, '<p>Moved Permanently. Redirecting to http://google.com</p>', done);
|
||||
.expect(301, '<!DOCTYPE html><head><title>Moved Permanently</title></head><body><p>Moved Permanently. Redirecting to http://google.com</p></body>', done);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -42,5 +42,74 @@ describe('res', function(){
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/vnd.amazon.ebook', done);
|
||||
})
|
||||
|
||||
describe('edge cases', function(){
|
||||
it('should handle empty string gracefully', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.type('').end('test');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/octet-stream')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('should handle file extension with dots', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.type('.json').end('{"test": true}');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('should handle multiple file extensions', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.type('file.tar.gz').end('compressed');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/gzip')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('should handle uppercase extensions', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.type('FILE.JSON').end('{"test": true}');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('should handle extension with special characters', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.type('file@test.json').end('{"test": true}');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.end(done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -35,8 +35,15 @@ describe('utils.normalizeType acceptParams method', () => {
|
||||
params: {} // No parameters are added since "invalid" has no "="
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should default to application/octet-stream when mime lookup fails', () => {
|
||||
const result = utils.normalizeType('unknown-extension-xyz');
|
||||
assert.deepEqual(result, {
|
||||
value: 'application/octet-stream',
|
||||
params: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('utils.setCharset(type, charset)', function () {
|
||||
it('should do anything without type', function () {
|
||||
@@ -81,3 +88,28 @@ describe('utils.wetag(body, encoding)', function(){
|
||||
'W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('utils.compileETag()', function () {
|
||||
it('should return generateETag for true', function () {
|
||||
const fn = utils.compileETag(true);
|
||||
assert.strictEqual(fn('express!'), utils.wetag('express!'));
|
||||
});
|
||||
|
||||
it('should return undefined for false', function () {
|
||||
assert.strictEqual(utils.compileETag(false), undefined);
|
||||
});
|
||||
|
||||
it('should return generateETag for string values "strong" and "weak"', function () {
|
||||
assert.strictEqual(utils.compileETag('strong')("express"), utils.etag("express"));
|
||||
assert.strictEqual(utils.compileETag('weak')("express"), utils.wetag("express"));
|
||||
});
|
||||
|
||||
it('should throw for unknown string values', function () {
|
||||
assert.throws(() => utils.compileETag('foo'), TypeError);
|
||||
});
|
||||
|
||||
it('should throw for unsupported types like arrays and objects', function () {
|
||||
assert.throws(() => utils.compileETag([]), TypeError);
|
||||
assert.throws(() => utils.compileETag({}), TypeError);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user