-
Notifications
You must be signed in to change notification settings - Fork 400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle query params correctly for client app urls with no trailing slash #8120
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,14 @@ import { getLanguage, isValidLang } from 'core/i18n/utils'; | |
import log from 'core/logger'; | ||
|
||
export function prefixMiddleware(req, res, next, { _config = config } = {}) { | ||
const URLParts = req.originalUrl.split('?'); | ||
|
||
// Split on slashes after removing the leading slash. | ||
const URLParts = req.originalUrl.replace(/^\//, '').split('/'); | ||
const URLPathParts = URLParts[0].replace(/^\//, '').split('/'); | ||
|
||
// Get lang and app parts from the URL. At this stage they may be incorrect | ||
// or missing. | ||
const [langFromURL, appFromURL] = URLParts; | ||
const [langFromURL, appFromURL] = URLPathParts; | ||
|
||
// Get language from URL or fall-back to detecting it from accept-language | ||
// header. | ||
|
@@ -45,14 +47,16 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) { | |
let prependedOrMovedApplication = false; | ||
|
||
if (hasValidLocaleException) { | ||
log.info(oneLine`Second part of URL is a locale exception (${URLParts[1]}); | ||
log.info(oneLine`Second part of URL is a locale exception (${ | ||
URLPathParts[1] | ||
}); | ||
make sure the clientApp is valid`); | ||
|
||
// Normally we look for a clientApp in the second part of a URL, but URLs | ||
// that match a locale exception don't have a locale so we look for the | ||
// clientApp in the first part of the URL. | ||
if (!isValidClientApp(langFromURL, { _config })) { | ||
URLParts[0] = application; | ||
URLPathParts[0] = application; | ||
isApplicationFromHeader = true; | ||
prependedOrMovedApplication = true; | ||
} | ||
|
@@ -65,25 +69,28 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) { | |
// * It's valid and we've mapped it e.g: pt -> pt-PT. | ||
// * The lang is invalid but we have a valid application | ||
// e.g. /bogus/firefox/. | ||
log.info(`Replacing lang in URL ${URLParts[0]} -> ${lang}`); | ||
URLParts[0] = lang; | ||
} else if (isValidLocaleUrlException(URLParts[0], { _config })) { | ||
log.info(`Replacing lang in URL ${URLPathParts[0]} -> ${lang}`); | ||
URLPathParts[0] = lang; | ||
} else if (isValidLocaleUrlException(URLPathParts[0], { _config })) { | ||
log.info(`Prepending clientApp to URL: ${application}`); | ||
URLParts.splice(0, 0, application); | ||
URLPathParts.splice(0, 0, application); | ||
isApplicationFromHeader = true; | ||
prependedOrMovedApplication = true; | ||
} else if (!hasValidLang) { | ||
// If lang wasn't valid or was missing prepend one. | ||
log.info(`Prepending lang to URL: ${lang}`); | ||
URLParts.splice(0, 0, lang); | ||
URLPathParts.splice(0, 0, lang); | ||
// If we've prepended the lang to the URL we need to re-check our | ||
// URL exception and make sure it's valid. | ||
hasValidClientAppUrlException = isValidClientAppUrlException(URLParts[1], { | ||
_config, | ||
}); | ||
hasValidClientAppUrlException = isValidClientAppUrlException( | ||
URLPathParts[1], | ||
{ | ||
_config, | ||
}, | ||
); | ||
} | ||
|
||
if (!hasValidClientApp && isValidClientApp(URLParts[1], { _config })) { | ||
if (!hasValidClientApp && isValidClientApp(URLPathParts[1], { _config })) { | ||
// We skip prepending an app if we'd previously prepended a lang and the | ||
// 2nd part of the URL is now a valid app. | ||
log.info('Application in URL is valid following prepending a lang.'); | ||
|
@@ -93,7 +100,7 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) { | |
); | ||
} else if (hasValidLocaleException || hasValidClientAppUrlException) { | ||
if ( | ||
clientAppRoutes.includes(URLParts[1]) === false && | ||
clientAppRoutes.includes(URLPathParts[1]) === false && | ||
(hasValidLang || hasValidLocaleException) | ||
) { | ||
log.info('Exception in URL found; we fallback to addons-server.'); | ||
|
@@ -107,14 +114,16 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) { | |
} else if (!hasValidClientApp) { | ||
// If the app supplied is not valid we need to prepend one. | ||
log.info(`Prepending application to URL: ${application}`); | ||
URLParts.splice(1, 0, application); | ||
URLPathParts.splice(1, 0, application); | ||
isApplicationFromHeader = true; | ||
} | ||
|
||
// Redirect to the new URL. | ||
// For safety we'll deny a redirect to a URL starting with '//' since | ||
// that will be treated as a protocol-free URL. | ||
const newURL = `/${URLParts.join('/')}`; | ||
URLParts[0] = `/${URLPathParts.join('/')}`; | ||
const newURL = URLParts.join('?'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have named variables for each url part, then we could build the url as is (I think): const newURL = [`/${URLParts.join('/')}`, queryString].join('?'); This reads a bit better, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except that in the case of there being no queryString you need to check for it being Both of the issues with destructuring are solved if the second parameter is a spread: function splitURL(url) {
const [path, ...queryString] = url.split('?');
const URLParts = path.replace(/^\//, '').split('/');
const newURL = [`/${URLParts.join('/')}`].concat(queryString).join('?');
return newURL;
}
// Sane Query string
console.log(splitURL('/foo/bar?foo=1'));
// Bad query string
console.log(splitURL('/foo/bar?foo=1?foo=2'));
// No query string
console.log(splitURL('/foo/bar')); Outputs:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if that's more readable though... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah well, maybe not... Is a URL with more than one |
||
|
||
if (newURL !== req.originalUrl && !newURL.startsWith('//')) { | ||
// Collect vary headers to apply to the redirect | ||
// so we can make it cacheable. | ||
|
@@ -130,7 +139,7 @@ export function prefixMiddleware(req, res, next, { _config = config } = {}) { | |
|
||
// Add the data to res.locals to be utilised later. | ||
/* eslint-disable no-param-reassign */ | ||
const [newLang, newApp] = URLParts; | ||
const [newLang, newApp] = URLPathParts; | ||
res.locals.lang = newLang; | ||
// The newApp part of the URL might not be a client application | ||
// so it's important to re-check that here before assuming it's good. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we name the two different parts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about doing that, but it assumes there's 2 parts, and that's not an unreasonable assumption, but if the url had multiple
?
we'd lose the later ones using destructuring. Should we throwaway data if a url contains that mistake?See below for a possible solution.