Skip to content

Commit 0b1cd30

Browse files
Handle query params correctly for client app urls with no trailing slash (#8120)
* Handle query params correctly for client app urls with no trailing slash * Add test for locale only starting url
1 parent b981933 commit 0b1cd30

File tree

2 files changed

+56
-17
lines changed

2 files changed

+56
-17
lines changed

src/core/middleware/prefixMiddleware.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { getLanguage, isValidLang } from 'core/i18n/utils';
1111
import log from 'core/logger';
1212

1313
export function prefixMiddleware(req, res, next, { _config = config } = {}) {
14+
const URLParts = req.originalUrl.split('?');
15+
1416
// Split on slashes after removing the leading slash.
15-
const URLParts = req.originalUrl.replace(/^\//, '').split('/');
17+
const URLPathParts = URLParts[0].replace(/^\//, '').split('/');
1618

1719
// Get lang and app parts from the URL. At this stage they may be incorrect
1820
// or missing.
19-
const [langFromURL, appFromURL] = URLParts;
21+
const [langFromURL, appFromURL] = URLPathParts;
2022

2123
// Get language from URL or fall-back to detecting it from accept-language
2224
// header.
@@ -45,14 +47,16 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) {
4547
let prependedOrMovedApplication = false;
4648

4749
if (hasValidLocaleException) {
48-
log.info(oneLine`Second part of URL is a locale exception (${URLParts[1]});
50+
log.info(oneLine`Second part of URL is a locale exception (${
51+
URLPathParts[1]
52+
});
4953
make sure the clientApp is valid`);
5054

5155
// Normally we look for a clientApp in the second part of a URL, but URLs
5256
// that match a locale exception don't have a locale so we look for the
5357
// clientApp in the first part of the URL.
5458
if (!isValidClientApp(langFromURL, { _config })) {
55-
URLParts[0] = application;
59+
URLPathParts[0] = application;
5660
isApplicationFromHeader = true;
5761
prependedOrMovedApplication = true;
5862
}
@@ -65,25 +69,28 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) {
6569
// * It's valid and we've mapped it e.g: pt -> pt-PT.
6670
// * The lang is invalid but we have a valid application
6771
// e.g. /bogus/firefox/.
68-
log.info(`Replacing lang in URL ${URLParts[0]} -> ${lang}`);
69-
URLParts[0] = lang;
70-
} else if (isValidLocaleUrlException(URLParts[0], { _config })) {
72+
log.info(`Replacing lang in URL ${URLPathParts[0]} -> ${lang}`);
73+
URLPathParts[0] = lang;
74+
} else if (isValidLocaleUrlException(URLPathParts[0], { _config })) {
7175
log.info(`Prepending clientApp to URL: ${application}`);
72-
URLParts.splice(0, 0, application);
76+
URLPathParts.splice(0, 0, application);
7377
isApplicationFromHeader = true;
7478
prependedOrMovedApplication = true;
7579
} else if (!hasValidLang) {
7680
// If lang wasn't valid or was missing prepend one.
7781
log.info(`Prepending lang to URL: ${lang}`);
78-
URLParts.splice(0, 0, lang);
82+
URLPathParts.splice(0, 0, lang);
7983
// If we've prepended the lang to the URL we need to re-check our
8084
// URL exception and make sure it's valid.
81-
hasValidClientAppUrlException = isValidClientAppUrlException(URLParts[1], {
82-
_config,
83-
});
85+
hasValidClientAppUrlException = isValidClientAppUrlException(
86+
URLPathParts[1],
87+
{
88+
_config,
89+
},
90+
);
8491
}
8592

86-
if (!hasValidClientApp && isValidClientApp(URLParts[1], { _config })) {
93+
if (!hasValidClientApp && isValidClientApp(URLPathParts[1], { _config })) {
8794
// We skip prepending an app if we'd previously prepended a lang and the
8895
// 2nd part of the URL is now a valid app.
8996
log.info('Application in URL is valid following prepending a lang.');
@@ -93,7 +100,7 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) {
93100
);
94101
} else if (hasValidLocaleException || hasValidClientAppUrlException) {
95102
if (
96-
clientAppRoutes.includes(URLParts[1]) === false &&
103+
clientAppRoutes.includes(URLPathParts[1]) === false &&
97104
(hasValidLang || hasValidLocaleException)
98105
) {
99106
log.info('Exception in URL found; we fallback to addons-server.');
@@ -107,14 +114,16 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) {
107114
} else if (!hasValidClientApp) {
108115
// If the app supplied is not valid we need to prepend one.
109116
log.info(`Prepending application to URL: ${application}`);
110-
URLParts.splice(1, 0, application);
117+
URLPathParts.splice(1, 0, application);
111118
isApplicationFromHeader = true;
112119
}
113120

114121
// Redirect to the new URL.
115122
// For safety we'll deny a redirect to a URL starting with '//' since
116123
// that will be treated as a protocol-free URL.
117-
const newURL = `/${URLParts.join('/')}`;
124+
URLParts[0] = `/${URLPathParts.join('/')}`;
125+
const newURL = URLParts.join('?');
126+
118127
if (newURL !== req.originalUrl && !newURL.startsWith('//')) {
119128
// Collect vary headers to apply to the redirect
120129
// so we can make it cacheable.
@@ -130,7 +139,7 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) {
130139

131140
// Add the data to res.locals to be utilised later.
132141
/* eslint-disable no-param-reassign */
133-
const [newLang, newApp] = URLParts;
142+
const [newLang, newApp] = URLPathParts;
134143
res.locals.lang = newLang;
135144
// The newApp part of the URL might not be a client application
136145
// so it's important to re-check that here before assuming it's good.

tests/unit/core/middleware/test_prefixMiddleware.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,36 @@ describe(__filename, () => {
229229
sinon.assert.called(fakeRes.redirect);
230230
});
231231

232+
it('should not redirect for locale + app urls missing a trailing slash with query params', () => {
233+
const fakeReq = {
234+
originalUrl: '/en-US/android?foo=1',
235+
headers: {},
236+
};
237+
prefixMiddleware(fakeReq, fakeRes, fakeNext, { _config: fakeConfig });
238+
expect(fakeRes.locals.lang).toEqual('en-US');
239+
expect(fakeRes.locals.clientApp).toEqual('android');
240+
sinon.assert.notCalled(fakeRes.redirect);
241+
sinon.assert.called(fakeNext);
242+
});
243+
244+
it('should redirect for app url missing a trailing slash with query params', () => {
245+
const fakeReq = {
246+
originalUrl: '/android?foo=2',
247+
headers: {},
248+
};
249+
prefixMiddleware(fakeReq, fakeRes, fakeNext, { _config: fakeConfig });
250+
sinon.assert.calledWith(fakeRes.redirect, 301, '/en-US/android?foo=2');
251+
});
252+
253+
it('should redirect for locale url missing a trailing slash with query params', () => {
254+
const fakeReq = {
255+
originalUrl: '/en-US?foo=3',
256+
headers: {},
257+
};
258+
prefixMiddleware(fakeReq, fakeRes, fakeNext, { _config: fakeConfig });
259+
sinon.assert.calledWith(fakeRes.redirect, 301, '/en-US/firefox?foo=3');
260+
});
261+
232262
it('should not mangle a query string for a redirect', () => {
233263
const fakeReq = {
234264
originalUrl: '/foo/bar?test=1&bar=2',

0 commit comments

Comments
 (0)