diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index fe77949b8..a62f5556a 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -13,4 +13,4 @@ jobs: uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest, macos-latest, windows-latest' - version: '16.13.0, 16, 18, 20' + version: '16.13.0, 16, 18, 20, 21' diff --git a/.gitignore b/.gitignore index e5c9f1457..7e7b04ea0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ npm-debug.log dist lib !lib/application.test-d.ts +package-lock.json diff --git a/src/response.ts b/src/response.ts index d9115ef58..3384dbdf2 100644 --- a/src/response.ts +++ b/src/response.ts @@ -206,11 +206,15 @@ export default class Response { * this.redirect('back'); * this.redirect('back', '/index.html'); * this.redirect('/login'); - * this.redirect('http://google.com'); + * this.redirect('http://google.com'); // will format to 'http://google.com/' */ redirect(url: string, alt?: string) { // location if (url === 'back') url = this.ctx.get('Referrer') || alt || '/'; + if (url.startsWith('https://') || url.startsWith('http://')) { + // formatting url again avoid security escapes + url = new URL(url).toString(); + } this.set('Location', encodeUrl(url)); // status diff --git a/test/response/redirect.test.ts b/test/response/redirect.test.ts index 70b08feda..6b69f23dd 100644 --- a/test/response/redirect.test.ts +++ b/test/response/redirect.test.ts @@ -7,7 +7,14 @@ describe('ctx.redirect(url)', () => { it('should redirect to the given url', () => { const ctx = context(); ctx.redirect('http://google.com'); - assert.strictEqual(ctx.response.header.location, 'http://google.com'); + assert.strictEqual(ctx.response.header.location, 'http://google.com/'); + assert.strictEqual(ctx.status, 302); + }); + + it('should url formatting is required before redirect', () => { + const ctx = context(); + ctx.redirect('http://google.com\\@baidu.com'); + assert.strictEqual(ctx.response.header.location, 'http://google.com/@baidu.com'); assert.strictEqual(ctx.status, 302); }); @@ -15,7 +22,7 @@ describe('ctx.redirect(url)', () => { const app = new Koa(); app.use(ctx => { - ctx.redirect('http://google.com/😓'); + ctx.redirect('https://google.com/😓?hello=你好(*´▽`)ノノ&p=123&q=%F0%9F%98%93%3Fhello%3D%E4%BD%A0%E5%A5%BD%28'); }); request(app.callback()) @@ -23,7 +30,7 @@ describe('ctx.redirect(url)', () => { .end((err, res) => { if (err) return done(err); assert.strictEqual(res.status, 302); - assert.strictEqual(res.headers.location, 'http://google.com/%F0%9F%98%93'); + assert.strictEqual(res.headers.location, 'https://google.com/%F0%9F%98%93?hello=%E4%BD%A0%E5%A5%BD(*%C2%B4%E2%96%BD%EF%BD%80)%E3%83%8E%E3%83%8E&p=123&q=%F0%9F%98%93%3Fhello%3D%E4%BD%A0%E5%A5%BD%28'); done(); }); }); @@ -63,7 +70,7 @@ describe('ctx.redirect(url)', () => { ctx.header.accept = 'text/html'; ctx.redirect(url); assert.strictEqual(ctx.response.header['content-type'], 'text/html; charset=utf-8'); - assert.strictEqual(ctx.body, `Redirecting to ${url}.`); + assert.strictEqual(ctx.body, `Redirecting to ${url}/.`); }); it('should escape the url', () => { @@ -83,7 +90,7 @@ describe('ctx.redirect(url)', () => { const url = 'http://google.com'; ctx.header.accept = 'text/plain'; ctx.redirect(url); - assert.strictEqual(ctx.body, `Redirecting to ${url}.`); + assert.strictEqual(ctx.body, `Redirecting to ${url}/.`); }); }); @@ -95,7 +102,7 @@ describe('ctx.redirect(url)', () => { ctx.header.accept = 'text/plain'; ctx.redirect('http://google.com'); assert.strictEqual(ctx.status, 301); - assert.strictEqual(ctx.body, `Redirecting to ${url}.`); + assert.strictEqual(ctx.body, `Redirecting to ${url}/.`); }); }); @@ -107,7 +114,7 @@ describe('ctx.redirect(url)', () => { ctx.header.accept = 'text/plain'; ctx.redirect('http://google.com'); assert.strictEqual(ctx.status, 302); - assert.strictEqual(ctx.body, `Redirecting to ${url}.`); + assert.strictEqual(ctx.body, `Redirecting to ${url}/.`); }); }); @@ -115,9 +122,9 @@ describe('ctx.redirect(url)', () => { it('should overwrite content-type', () => { const ctx = context(); ctx.body = {}; - const url = 'http://google.com'; + const url = 'http://google.com/?foo=bar'; ctx.header.accept = 'text/plain'; - ctx.redirect('http://google.com'); + ctx.redirect(url); assert.strictEqual(ctx.status, 302); assert.strictEqual(ctx.body, `Redirecting to ${url}.`); assert.strictEqual(ctx.type, 'text/plain');