Skip to content

Commit 76eaa32

Browse files
committed
Encode URL in res.location/res.redirect if not already encoded
fixes #2897 fixes #3003
1 parent c12cc88 commit 76eaa32

File tree

5 files changed

+63
-10
lines changed

5 files changed

+63
-10
lines changed

History.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ unreleased
55
* Add `cacheControl` option to `res.sendFile`/`res.sendfile`
66
* Add `options` argument to `req.range`
77
- Includes the `combine` option
8+
* Encode URL in `res.location`/`res.redirect` if not already encoded
89
* Fix some redirect handling in `res.sendFile`/`res.sendfile`
910
* Fix Windows absolute path check using forward slashes
1011
* Improve error with invalid arguments to `req.get()`

lib/response.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
var contentDisposition = require('content-disposition');
1616
var deprecate = require('depd')('express');
17+
var encodeUrl = require('encodeurl');
1718
var escapeHtml = require('escape-html');
1819
var http = require('http');
1920
var isAbsolute = require('./utils').isAbsolute;
@@ -832,8 +833,7 @@ res.location = function location(url) {
832833
}
833834

834835
// set location
835-
this.set('Location', loc);
836-
return this;
836+
return this.set('Location', encodeUrl(loc));
837837
};
838838

839839
/**
@@ -871,13 +871,12 @@ res.redirect = function redirect(url) {
871871
}
872872

873873
// Set location header
874-
this.location(address);
875-
address = this.get('Location');
874+
address = this.location(address).get('Location');
876875

877876
// Support text/{plain,html} by default
878877
this.format({
879878
text: function(){
880-
body = statusCodes[status] + '. Redirecting to ' + encodeURI(address);
879+
body = statusCodes[status] + '. Redirecting to ' + address;
881880
},
882881

883882
html: function(){

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"cookie-signature": "1.0.6",
3636
"debug": "~2.2.0",
3737
"depd": "~1.1.0",
38+
"encodeurl": "~1.0.1",
3839
"escape-html": "~1.0.3",
3940
"etag": "~1.7.0",
4041
"finalhandler": "0.4.1",

test/res.location.js

+26
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,31 @@ describe('res', function(){
1616
.expect('Location', 'http://google.com')
1717
.expect(200, done)
1818
})
19+
20+
it('should encode "url"', function (done) {
21+
var app = express()
22+
23+
app.use(function (req, res) {
24+
res.location('https://google.com?q=\u2603 §10').end()
25+
})
26+
27+
request(app)
28+
.get('/')
29+
.expect('Location', 'https://google.com?q=%E2%98%83%20%C2%A710')
30+
.expect(200, done)
31+
})
32+
33+
it('should not touch already-encoded sequences in "url"', function (done) {
34+
var app = express()
35+
36+
app.use(function (req, res) {
37+
res.location('https://google.com?q=%A710').end()
38+
})
39+
40+
request(app)
41+
.get('/')
42+
.expect('Location', 'https://google.com?q=%A710')
43+
.expect(200, done)
44+
})
1945
})
2046
})

test/res.redirect.js

+31-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ describe('res', function(){
1818
.expect('location', 'http://google.com')
1919
.expect(302, done)
2020
})
21+
22+
it('should encode "url"', function (done) {
23+
var app = express()
24+
25+
app.use(function (req, res) {
26+
res.redirect('https://google.com?q=\u2603 §10')
27+
})
28+
29+
request(app)
30+
.get('/')
31+
.expect('Location', 'https://google.com?q=%E2%98%83%20%C2%A710')
32+
.expect(302, done)
33+
})
34+
35+
it('should not touch already-encoded sequences in "url"', function (done) {
36+
var app = express()
37+
38+
app.use(function (req, res) {
39+
res.redirect('https://google.com?q=%A710')
40+
})
41+
42+
request(app)
43+
.get('/')
44+
.expect('Location', 'https://google.com?q=%A710')
45+
.expect(302, done)
46+
})
2147
})
2248

2349
describe('.redirect(status, url)', function(){
@@ -85,16 +111,16 @@ describe('res', function(){
85111
var app = express();
86112

87113
app.use(function(req, res){
88-
res.redirect('<lame>');
114+
res.redirect('<la\'me>');
89115
});
90116

91117
request(app)
92118
.get('/')
93119
.set('Host', 'http://example.com')
94120
.set('Accept', 'text/html')
95121
.expect('Content-Type', /html/)
96-
.expect('Location', '<lame>')
97-
.expect(302, '<p>' + http.STATUS_CODES[302] + '. Redirecting to <a href="&lt;lame&gt;">&lt;lame&gt;</a></p>', done);
122+
.expect('Location', '%3Cla\'me%3E')
123+
.expect(302, '<p>' + http.STATUS_CODES[302] + '. Redirecting to <a href="%3Cla&#39;me%3E">%3Cla&#39;me%3E</a></p>', done)
98124
})
99125

100126
it('should include the redirect type', function(done){
@@ -141,8 +167,8 @@ describe('res', function(){
141167
.set('Host', 'http://example.com')
142168
.set('Accept', 'text/plain, */*')
143169
.expect('Content-Type', /plain/)
144-
.expect('Location', 'http://example.com/?param=<script>alert("hax");</script>')
145-
.expect(302, http.STATUS_CODES[302] + '. Redirecting to http://example.com/?param=%3Cscript%3Ealert(%22hax%22);%3C/script%3E', done);
170+
.expect('Location', 'http://example.com/?param=%3Cscript%3Ealert(%22hax%22);%3C/script%3E')
171+
.expect(302, http.STATUS_CODES[302] + '. Redirecting to http://example.com/?param=%3Cscript%3Ealert(%22hax%22);%3C/script%3E', done)
146172
})
147173

148174
it('should include the redirect type', function(done){

0 commit comments

Comments
 (0)