mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 18:57:43 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49744abd11 | ||
|
|
6e97452f60 | ||
|
|
6a23d34d65 | ||
|
|
8c12cdf93b | ||
|
|
7fea74fcf0 | ||
|
|
dac7a0475a | ||
|
|
997919b488 | ||
|
|
36fb59c6c7 | ||
|
|
3a5edfaff0 | ||
|
|
52d978119a | ||
|
|
fe93005e04 | ||
|
|
20415843f4 | ||
|
|
1faf228935 | ||
|
|
2e0fb646d0 | ||
|
|
59fc27028e | ||
|
|
51fc39ccf8 | ||
|
|
8e229f9275 | ||
|
|
a024c8a7b6 | ||
|
|
7e562c6d8d | ||
|
|
1bcde96bc8 | ||
|
|
7d36477568 | ||
|
|
40d2d8f2c8 | ||
|
|
77ada906db |
82
.github/workflows/ci.yml
vendored
82
.github/workflows/ci.yml
vendored
@@ -13,6 +13,9 @@ on:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Cancel in progress workflows
|
||||
# in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run
|
||||
concurrency:
|
||||
@@ -25,14 +28,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js {{ matrix.node-version }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --ignore-scripts --only=dev
|
||||
run: npm install --ignore-scripts --include=dev
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
@@ -65,6 +67,8 @@ jobs:
|
||||
- "20"
|
||||
- "21"
|
||||
- "22"
|
||||
- "23"
|
||||
- "24"
|
||||
# Use supported versions of our testing tools under older versions of Node
|
||||
# Install npm in some specific cases where we need to
|
||||
include:
|
||||
@@ -93,22 +97,25 @@ jobs:
|
||||
npm-i: "mocha@6.2.2 nyc@14.1.1 supertest@6.1.6"
|
||||
|
||||
- node-version: "8"
|
||||
npm-i: "mocha@7.2.0 nyc@14.1.1"
|
||||
npm-i: "mocha@7.2.0 nyc@14.1.1 supertest@6.1.6"
|
||||
|
||||
- node-version: "9"
|
||||
npm-i: "mocha@7.2.0 nyc@14.1.1"
|
||||
npm-i: "mocha@7.2.0 nyc@14.1.1 supertest@6.1.6"
|
||||
|
||||
- node-version: "10"
|
||||
npm-i: "mocha@8.4.0"
|
||||
npm-i: "mocha@8.4.0 supertest@6.1.6"
|
||||
|
||||
- node-version: "11"
|
||||
npm-i: "mocha@8.4.0"
|
||||
npm-i: "mocha@8.4.0 supertest@6.1.6"
|
||||
|
||||
- node-version: "12"
|
||||
npm-i: "mocha@9.2.2"
|
||||
npm-i: "mocha@9.2.2 supertest@6.1.6"
|
||||
|
||||
- node-version: "13"
|
||||
npm-i: "mocha@9.2.2"
|
||||
npm-i: "mocha@9.2.2 supertest@6.1.6"
|
||||
|
||||
- node-version: "15"
|
||||
npm-i: "supertest@6.1.6"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -130,13 +137,13 @@ jobs:
|
||||
npm config set loglevel error
|
||||
shell: bash
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Install Node version specific dev deps
|
||||
if: ${{ matrix.npm-i != '' }}
|
||||
run: npm install --save-dev ${{ matrix.npm-i }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Remove non-test dependencies
|
||||
run: npm rm --silent --save-dev connect-redis
|
||||
|
||||
@@ -147,44 +154,39 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
npm run test-ci
|
||||
cp coverage/lcov.info "coverage/${{ matrix.node-version }}.lcov"
|
||||
|
||||
- name: Collect code coverage
|
||||
run: |
|
||||
mv ./coverage "./${{ matrix.node-version }}"
|
||||
mkdir ./coverage
|
||||
mv "./${{ matrix.node-version }}" "./coverage/${{ matrix.node-version }}"
|
||||
run: npm run test-ci
|
||||
|
||||
- name: Upload code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: ./coverage
|
||||
name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }}
|
||||
path: ./coverage/lcov.info
|
||||
retention-days: 1
|
||||
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install lcov
|
||||
shell: bash
|
||||
run: sudo apt-get -y install lcov
|
||||
- name: Install lcov
|
||||
shell: bash
|
||||
run: sudo apt-get -y install lcov
|
||||
|
||||
- name: Collect coverage reports
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage
|
||||
path: ./coverage
|
||||
- name: Collect coverage reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./coverage
|
||||
pattern: coverage-node-*
|
||||
|
||||
- name: Merge coverage reports
|
||||
shell: bash
|
||||
run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./coverage/lcov.info
|
||||
- name: Merge coverage reports
|
||||
shell: bash
|
||||
run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload coverage report
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
file: ./lcov.info
|
||||
|
||||
29
History.md
29
History.md
@@ -1,3 +1,32 @@
|
||||
4.22.0 / 2025-12-01
|
||||
==========
|
||||
* 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))
|
||||
* deps: use tilde notation for dependencies
|
||||
* deps: qs@6.14.0
|
||||
|
||||
4.21.2 / 2024-11-06
|
||||
==========
|
||||
|
||||
* deps: path-to-regexp@0.1.12
|
||||
- Fix backtracking protection
|
||||
* deps: path-to-regexp@0.1.11
|
||||
- Throws an error on invalid path values
|
||||
|
||||
4.21.1 / 2024-10-08
|
||||
==========
|
||||
|
||||
* Backported a fix for [CVE-2024-47764](https://nvd.nist.gov/vuln/detail/CVE-2024-47764)
|
||||
|
||||
|
||||
4.21.0 / 2024-09-11
|
||||
==========
|
||||
|
||||
* Deprecate `res.location("back")` and `res.redirect("back")` magic string
|
||||
* deps: serve-static@1.16.2
|
||||
* includes send@0.19.0
|
||||
* deps: finalhandler@1.3.1
|
||||
* deps: qs@6.13.0
|
||||
|
||||
4.20.0 / 2024-09-10
|
||||
==========
|
||||
* deps: serve-static@0.16.0
|
||||
|
||||
@@ -32,7 +32,8 @@ app.param(['to', 'from'], function(req, res, next, num, name){
|
||||
// Load user by id
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
if (req.user = users[id]) {
|
||||
req.user = users[id];
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
next(createError(404, 'failed to find user'));
|
||||
|
||||
@@ -916,6 +916,7 @@ res.location = function location(url) {
|
||||
|
||||
// "back" is an alias for the referrer
|
||||
if (url === 'back') {
|
||||
deprecate('res.location("back"): use res.location(req.get("Referrer") || "/") and refer to https://dub.sh/security-redirect for best practices');
|
||||
loc = this.req.get('Referrer') || '/';
|
||||
} else {
|
||||
loc = String(url);
|
||||
|
||||
@@ -287,7 +287,7 @@ function createETagGenerator (options) {
|
||||
|
||||
function parseExtendedQueryString(str) {
|
||||
return qs.parse(str, {
|
||||
allowPrototypes: true
|
||||
plainObjects: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
38
package.json
38
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Fast, unopinionated, minimalist web framework",
|
||||
"version": "4.20.0",
|
||||
"version": "4.22.0",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
|
||||
@@ -15,6 +15,10 @@
|
||||
"license": "MIT",
|
||||
"repository": "expressjs/express",
|
||||
"homepage": "http://expressjs.com/",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
},
|
||||
"keywords": [
|
||||
"express",
|
||||
"framework",
|
||||
@@ -30,32 +34,32 @@
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"body-parser": "~1.20.3",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.6.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.10",
|
||||
"path-to-regexp": "~0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"qs": "~6.14.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.0",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
@@ -71,11 +75,11 @@
|
||||
"hbs": "4.2.0",
|
||||
"marked": "0.7.0",
|
||||
"method-override": "3.0.0",
|
||||
"mocha": "10.2.0",
|
||||
"mocha": "^6.2.2",
|
||||
"morgan": "1.10.0",
|
||||
"nyc": "15.1.0",
|
||||
"nyc": "^14.1.1",
|
||||
"pbkdf2-password": "1.2.1",
|
||||
"supertest": "6.3.0",
|
||||
"supertest": "^6.1.6",
|
||||
"vhost": "~3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -55,6 +55,17 @@ describe('app.router', function(){
|
||||
.expect(200, done)
|
||||
})
|
||||
|
||||
it('should not support ' + method.toUpperCase() + ' without a path', function () {
|
||||
if (method === 'get' || (method === 'query' && shouldSkipQuery(process.versions.node))) {
|
||||
this.skip();
|
||||
}
|
||||
var app = express();
|
||||
|
||||
assert.throws(function () {
|
||||
app[method](function (req, res) { });
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject numbers for app.' + method, function(){
|
||||
var app = express();
|
||||
assert.throws(app[method].bind(app, '/', 3), /Number/)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
var assert = require('assert')
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
var qs = require('qs');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.query', function(){
|
||||
@@ -38,6 +39,22 @@ describe('req', function(){
|
||||
.get('/?user.name=tj')
|
||||
.expect(200, '{"user.name":"tj"}', done);
|
||||
});
|
||||
|
||||
it('should not be able to access object prototype properties', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?foo=yee')
|
||||
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
|
||||
});
|
||||
|
||||
it('should be able to use object prototype property names as keys', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=yee')
|
||||
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" is simple', function () {
|
||||
@@ -48,6 +65,22 @@ describe('req', function(){
|
||||
.get('/?user%5Bname%5D=tj')
|
||||
.expect(200, '{"user[name]":"tj"}', done);
|
||||
});
|
||||
|
||||
it('should not be able to access object prototype properties', function (done) {
|
||||
var app = createApp('simple', true);
|
||||
|
||||
request(app)
|
||||
.get('/?foo=yee')
|
||||
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
|
||||
});
|
||||
|
||||
it('should be able to use object prototype property names as keys', function (done) {
|
||||
var app = createApp('simple', true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=yee')
|
||||
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" is a function', function () {
|
||||
@@ -60,6 +93,18 @@ describe('req', function(){
|
||||
.get('/?user%5Bname%5D=tj')
|
||||
.expect(200, '{"length":17}', done);
|
||||
});
|
||||
|
||||
// test exists to verify behavior for folks wishing to workaround our qs defaults
|
||||
it('should drop object prototype property names and be able to access object prototype properties', function (done) {
|
||||
var app = createApp(
|
||||
function (str) {
|
||||
return qs.parse(str)
|
||||
}, true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=biscuits')
|
||||
.expect(200, '{"query":{},"hasOwnProperty":false}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" disabled', function () {
|
||||
@@ -70,6 +115,22 @@ describe('req', function(){
|
||||
.get('/?user%5Bname%5D=tj')
|
||||
.expect(200, '{}', done);
|
||||
});
|
||||
|
||||
it('should not be able to access object prototype properties', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?foo=yee')
|
||||
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
|
||||
});
|
||||
|
||||
it('should be able to use object prototype property names as keys', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=yee')
|
||||
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" enabled', function () {
|
||||
@@ -80,6 +141,22 @@ describe('req', function(){
|
||||
.get('/?user%5Bname%5D=tj')
|
||||
.expect(200, '{"user[name]":"tj"}', done);
|
||||
});
|
||||
|
||||
it('should not be able to access object prototype properties', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?foo=yee')
|
||||
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
|
||||
});
|
||||
|
||||
it('should be able to use object prototype property names as keys', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=yee')
|
||||
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser fn" is missing', function () {
|
||||
@@ -97,6 +174,22 @@ describe('req', function(){
|
||||
.get('/?user[name]=tj&user.name=tj')
|
||||
.expect(200, '{"user":{"name":"tj"},"user.name":"tj"}', done);
|
||||
});
|
||||
|
||||
it('should not be able to access object prototype properties', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?foo=yee')
|
||||
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
|
||||
});
|
||||
|
||||
it('should be able to use object prototype property names as keys', function (done) {
|
||||
var app = createApp('extended', true);
|
||||
|
||||
request(app)
|
||||
.get('/?hasOwnProperty=yee')
|
||||
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" an unknown value', function () {
|
||||
@@ -108,7 +201,7 @@ describe('req', function(){
|
||||
})
|
||||
})
|
||||
|
||||
function createApp(setting) {
|
||||
function createApp(setting, isPrototypePropertyTest) {
|
||||
var app = express();
|
||||
|
||||
if (setting !== undefined) {
|
||||
@@ -116,7 +209,17 @@ function createApp(setting) {
|
||||
}
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.send(req.query);
|
||||
if(isPrototypePropertyTest) {
|
||||
try {
|
||||
var hasOwnProperty = req.query.hasOwnProperty('✨ express ✨');
|
||||
res.send({ query: req.query, hasOwnProperty: hasOwnProperty });
|
||||
} catch (error) {
|
||||
res.send({ query: req.query, error: error.toString() });
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.send(req.query);
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
|
||||
@@ -77,10 +77,10 @@ function getMajorVersion(versionString) {
|
||||
}
|
||||
|
||||
function shouldSkipQuery(versionString) {
|
||||
// Skipping HTTP QUERY tests on Node 21, it is reported in http.METHODS on 21.7.2 but not supported
|
||||
// update this implementation to run on supported versions of 21 once they exist
|
||||
// Skipping HTTP QUERY tests below Node 22, QUERY wasn't fully supported by Node until 22
|
||||
// we could update this implementation to run on supported versions of 21 once they exist
|
||||
// upstream tracking https://github.com/nodejs/node/issues/51562
|
||||
// express tracking issue: https://github.com/expressjs/express/issues/5615
|
||||
return Number(getMajorVersion(versionString)) === 21
|
||||
return Number(getMajorVersion(versionString)) < 22
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user