317 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | |
| 
 | |
| var express = require('../')
 | |
|   , request = require('supertest')
 | |
|   , assert = require('node:assert')
 | |
|   , url = require('node:url');
 | |
| 
 | |
| describe('res', function(){
 | |
|   describe('.location(url)', function(){
 | |
|     it('should set the header', function(done){
 | |
|       var app = express();
 | |
| 
 | |
|       app.use(function(req, res){
 | |
|         res.location('http://google.com/').end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|       .get('/')
 | |
|       .expect('Location', 'http://google.com/')
 | |
|       .expect(200, done)
 | |
|     })
 | |
| 
 | |
|     it('should preserve trailing slashes when not present', function(done){
 | |
|       var app = express();
 | |
| 
 | |
|       app.use(function(req, res){
 | |
|         res.location('http://google.com').end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|       .get('/')
 | |
|       .expect('Location', 'http://google.com')
 | |
|       .expect(200, done)
 | |
|     })
 | |
| 
 | |
|     it('should encode "url"', function (done) {
 | |
|       var app = express()
 | |
| 
 | |
|       app.use(function (req, res) {
 | |
|         res.location('https://google.com?q=\u2603 §10').end()
 | |
|       })
 | |
| 
 | |
|       request(app)
 | |
|       .get('/')
 | |
|       .expect('Location', 'https://google.com?q=%E2%98%83%20%C2%A710')
 | |
|       .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) {
 | |
|       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 consistently handle non-string input: boolean', function (done) {
 | |
|       var app = express()
 | |
|       app.use(function (req, res) {
 | |
|         res.location(true).end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|         .get('/')
 | |
|         .expect('Location', 'true')
 | |
|         .expect(200, done)
 | |
|     });
 | |
| 
 | |
|     it('should consistently handle non-string inputs: object', function (done) {
 | |
|       var app = express()
 | |
|       app.use(function (req, res) {
 | |
|         res.location({}).end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|         .get('/')
 | |
|         .expect('Location', '[object%20Object]')
 | |
|         .expect(200, done)
 | |
|     });
 | |
| 
 | |
|     it('should consistently handle non-string inputs: array', function (done) {
 | |
|       var app = express()
 | |
|       app.use(function (req, res) {
 | |
|         res.location([]).end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|         .get('/')
 | |
|         .expect('Location', '')
 | |
|         .expect(200, done)
 | |
|     });
 | |
| 
 | |
|     it('should consistently handle empty string input', function (done) {
 | |
|       var app = express()
 | |
|       app.use(function (req, res) {
 | |
|         res.location('').end();
 | |
|       });
 | |
| 
 | |
|       request(app)
 | |
|         .get('/')
 | |
|         .expect('Location', '')
 | |
|         .expect(200, done)
 | |
|     });
 | |
| 
 | |
| 
 | |
|     if (typeof URL !== 'undefined') {
 | |
|       it('should accept an instance of URL', function (done) {
 | |
|         var app = express();
 | |
| 
 | |
|         app.use(function(req, res){
 | |
|           res.location(new URL('http://google.com/')).end();
 | |
|         });
 | |
| 
 | |
|         request(app)
 | |
|           .get('/')
 | |
|           .expect('Location', 'http://google.com/')
 | |
|           .expect(200, done);
 | |
|       });
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   describe('location header encoding', function() {
 | |
|     function createRedirectServerForDomain (domain) {
 | |
|       var app = express();
 | |
|       app.use(function (req, res) {
 | |
|         var host = url.parse(req.query.q, false, true).host;
 | |
|         // This is here to show a basic check one might do which
 | |
|         // would pass but then the location header would still be bad
 | |
|         if (host !== domain) {
 | |
|           res.status(400).end('Bad host: ' + host + ' !== ' + domain);
 | |
|         }
 | |
|         res.location(req.query.q).end();
 | |
|       });
 | |
|       return app;
 | |
|     }
 | |
| 
 | |
|     function testRequestedRedirect (app, inputUrl, expected, expectedHost, done) {
 | |
|       return request(app)
 | |
|         // Encode uri because old supertest does not and is required
 | |
|         // to test older node versions. New supertest doesn't re-encode
 | |
|         // so this works in both.
 | |
|         .get('/?q=' + encodeURIComponent(inputUrl))
 | |
|         .expect('') // No body.
 | |
|         .expect(200)
 | |
|         .expect('Location', expected)
 | |
|         .end(function (err, res) {
 | |
|           if (err) {
 | |
|             console.log('headers:', res.headers)
 | |
|             console.error('error', res.error, err);
 | |
|             return done(err, res);
 | |
|           }
 | |
| 
 | |
|           // Parse the hosts from the input URL and the Location header
 | |
|           var inputHost = url.parse(inputUrl, false, true).host;
 | |
|           var locationHost = url.parse(res.headers['location'], false, true).host;
 | |
| 
 | |
|           assert.strictEqual(locationHost, expectedHost);
 | |
| 
 | |
|           // Assert that the hosts are the same
 | |
|           if (inputHost !== locationHost) {
 | |
|             return done(new Error('Hosts do not match: ' + inputHost + " !== " +  locationHost));
 | |
|           }
 | |
| 
 | |
|           return done(null, res);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     it('should not touch already-encoded sequences in "url"', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'https://google.com?q=%A710',
 | |
|         'https://google.com?q=%A710',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should consistently handle relative urls', function (done) {
 | |
|       var app = createRedirectServerForDomain(null);
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         '/foo/bar',
 | |
|         '/foo/bar',
 | |
|         null,
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should not encode urls in such a way that they can bypass redirect allow lists', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'http://google.com\\@apple.com',
 | |
|         'http://google.com\\@apple.com',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should not be case sensitive', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'HTTP://google.com\\@apple.com',
 | |
|         'HTTP://google.com\\@apple.com',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should work with https', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'https://google.com\\@apple.com',
 | |
|         'https://google.com\\@apple.com',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should correctly encode schemaless paths', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         '//google.com\\@apple.com/',
 | |
|         '//google.com\\@apple.com/',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should keep backslashes in the path', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'https://google.com/foo\\bar\\baz',
 | |
|         'https://google.com/foo\\bar\\baz',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should escape header splitting for old node versions', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'http://google.com\\@apple.com/%0d%0afoo:%20bar',
 | |
|         'http://google.com\\@apple.com/%0d%0afoo:%20bar',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should encode unicode correctly', function (done) {
 | |
|       var app = createRedirectServerForDomain(null);
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         '/%e2%98%83',
 | |
|         '/%e2%98%83',
 | |
|         null,
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should encode unicode correctly even with a bad host', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'http://google.com\\@apple.com/%e2%98%83',
 | |
|         'http://google.com\\@apple.com/%e2%98%83',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should work correctly despite using deprecated url.parse', function (done) {
 | |
|       var app = createRedirectServerForDomain('google.com');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'https://google.com\'.bb.com/1.html',
 | |
|         'https://google.com\'.bb.com/1.html',
 | |
|         'google.com',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should encode file uri path', function (done) {
 | |
|       var app = createRedirectServerForDomain('');
 | |
|       testRequestedRedirect(
 | |
|         app,
 | |
|         'file:///etc\\passwd',
 | |
|         'file:///etc\\passwd',
 | |
|         '',
 | |
|         done
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| })
 |