From 07dd2cce9bf00e914a78683d03447d959bb1471e Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 4 Feb 2021 18:52:13 +0000 Subject: [PATCH] fix(server, vue-app): address encoding issues with query params (#8748) Co-authored-by: Pooya Parsa --- packages/server/package.json | 3 +- packages/server/src/middleware/nuxt.js | 3 +- packages/server/test/middleware/nuxt.test.js | 22 +++++++----- packages/vue-app/template/router.js | 14 +------- test/dev/encoding.test.js | 38 +++++++++++++++++++- test/fixtures/encoding/layouts/default.vue | 2 ++ 6 files changed, 58 insertions(+), 24 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 3a93aa5aa9fd..14fb3a3a68c4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -23,7 +23,8 @@ "pify": "^5.0.0", "serve-placeholder": "^1.2.3", "serve-static": "^1.14.1", - "server-destroy": "^1.0.1" + "server-destroy": "^1.0.1", + "ufo": "^0.6.1" }, "publishConfig": { "access": "public" diff --git a/packages/server/src/middleware/nuxt.js b/packages/server/src/middleware/nuxt.js index c958a8b109de..7155e9c6d69a 100644 --- a/packages/server/src/middleware/nuxt.js +++ b/packages/server/src/middleware/nuxt.js @@ -1,6 +1,7 @@ import generateETag from 'etag' import fresh from 'fresh' import consola from 'consola' +import { normalizeURL } from 'ufo' import { getContext, TARGETS } from '@nuxt/utils' @@ -9,7 +10,7 @@ export default ({ options, nuxt, renderRoute, resources }) => async function nux const context = getContext(req, res) try { - const url = decodeURI(req.url) + const url = normalizeURL(req.url) res.statusCode = 200 const result = await renderRoute(url, context) diff --git a/packages/server/test/middleware/nuxt.test.js b/packages/server/test/middleware/nuxt.test.js index bd8bcd61eb98..46384cca535d 100644 --- a/packages/server/test/middleware/nuxt.test.js +++ b/packages/server/test/middleware/nuxt.test.js @@ -329,18 +329,24 @@ describe('server: nuxtMiddleware', () => { expect(consola.error).toBeCalledWith(err) }) - test('should return 400 if request is uri error', async () => { + test('should return handle uri errors by normalizing', async () => { const context = createContext() const result = { html: 'rendered html' } context.renderRoute.mockReturnValue(result) const nuxtMiddleware = createNuxtMiddleware(context) const { req, res, next } = createServerContext() - - const err = Error('URI malformed') - err.name = 'URIError' - - await nuxtMiddleware({ ...req, url: 'http://localhost/test/server/%c1%81' }, res, next) - - expect(next).toBeCalledWith(err) + const paths = ['%c1%81', '%c1', '%'] + + for (const path of paths) { + await nuxtMiddleware( + { ...req, url: 'http://localhost/test/server/' + path }, + res, + next + ) + + expect(next).toBeCalledTimes(0) + expect(res.statusCode).toBe(200) + next.mockReset() + } }) }) diff --git a/packages/vue-app/template/router.js b/packages/vue-app/template/router.js index b761d9f88b2c..f45ee8b87918 100644 --- a/packages/vue-app/template/router.js +++ b/packages/vue-app/template/router.js @@ -101,14 +101,6 @@ export const routerOptions = { fallback: <%= router.fallback %> } -function decodeObj(obj) { - for (const key in obj) { - if (typeof obj[key] === 'string') { - obj[key] = decode(obj[key]) - } - } -} - export function createRouter (ssrContext, config) { const base = (config.app && config.app.basePath) || routerOptions.base const router = new Router({ ...routerOptions, base }) @@ -124,11 +116,7 @@ export function createRouter (ssrContext, config) { if (typeof to === 'string') { to = normalizeURL(to) } - const r = resolve(to, current, append) - if (r && r.resolved && r.resolved.query) { - decodeObj(r.resolved.query) - } - return r + return resolve(to, current, append) } return router diff --git a/test/dev/encoding.test.js b/test/dev/encoding.test.js index f035ea36f0e6..299877373251 100644 --- a/test/dev/encoding.test.js +++ b/test/dev/encoding.test.js @@ -1,3 +1,4 @@ +import fetch from 'node-fetch' import { getPort, loadFixture, Nuxt, rp } from '../utils' let port @@ -22,7 +23,7 @@ describe('encoding', () => { }) test('/ö/dynamic?q=food,coffee (encodeURIComponent)', async () => { - const { body: response } = await rp(url('/ö/dynamic?q=food%252Ccoffee')) + const { body: response } = await rp(url('/ö/dynamic?q=food,coffee')) expect(response).toContain('food,coffee') }) @@ -33,6 +34,41 @@ describe('encoding', () => { expect(response).toContain('About') }) + test('query params', async () => { + const queryStrings = { + '?email=some%20email.com': { email: 'some email.com' }, + '?str=%26&str2=%2526': { str: '&', str2: '%26' }, + '?t=coffee%2Cfood%2C': { t: 'coffee,food,' }, + '?redirect=%2Fhomologation%2Flist': { redirect: '/homologation/list' }, + '?email=some@email.com&token=DvtiwbIzry319e6KWimopA%3D%3D': { + email: 'some@email.com', + token: 'DvtiwbIzry319e6KWimopA==' + } + } + for (const [param, result] of Object.entries(queryStrings)) { + const { body: response } = await rp(url('/ö/dynamic/test') + param) + expect(response).toContain( + JSON.stringify(result) + .replace(/&/g, '&') + .replace(/"/g, '"') + ) + } + }) + + test('invalidly encoded route params are handled', async () => { + const paths = ['%c1%81', '%c1', '%'] + for (const path of paths) { + // We use node-fetch because got uses decodeURI on url and throws its own error + const response = await fetch(url('/ö/dynamic/') + path) + expect(response.ok).toBeTruthy() + expect(await response.text()).toContain( + JSON.stringify({ id: path }) + .replace(/&/g, '&') + .replace(/"/g, '"') + ) + } + }) + // Close server and ask nuxt to stop listening to file changes afterAll(async () => { await nuxt.close() diff --git a/test/fixtures/encoding/layouts/default.vue b/test/fixtures/encoding/layouts/default.vue index 4ea1ac4134fd..1d8406ebd0f8 100644 --- a/test/fixtures/encoding/layouts/default.vue +++ b/test/fixtures/encoding/layouts/default.vue @@ -26,6 +26,8 @@ export default { '/@about', '/тест', encodeURI('/тест'), + '/dynamic/%c', + '/dynamic/%', '/dynamic/سلام چطوری?q=cofee,food,دسر', encodeURI('/dynamic/سلام چطوری?q=cofee,food,دسر'), // Using encodeURIComponent on each segment