fix: PagesRouter header parameters are not URL-encoded to support non-ASCII characters in app name (#10078)

This commit is contained in:
Manuel
2026-02-27 18:54:19 +00:00
committed by GitHub
parent 85afe91818
commit c92660bd9a
7 changed files with 65 additions and 2 deletions
+41
View File
@@ -1251,6 +1251,47 @@ describe('Pages Router', () => {
});
});
describe('special characters in config', () => {
it('should not URI-encode page param headers by default', async () => {
await reconfigureServer({
appId: 'test',
appName: 'ExampleAppName',
publicServerURL: 'http://localhost:8378/1',
});
const response = await request({
url: 'http://localhost:8378/1/apps/choose_password?appId=test',
});
expect(response.status).toBe(200);
expect(response.headers['x-parse-page-param-appname']).toBe('ExampleAppName');
expect(response.headers['x-parse-page-param-publicserverurl']).toBe(
'http://localhost:8378/1'
);
});
it('should URI-encode page param headers when encodePageParamHeaders is true', async () => {
await reconfigureServer({
appId: 'test',
appName: 'Product™',
publicServerURL: 'http://localhost:8378/1',
pages: {
encodePageParamHeaders: true,
},
});
const response = await request({
url: 'http://localhost:8378/1/apps/choose_password?appId=test',
});
expect(response.status).toBe(200);
expect(response.headers['x-parse-page-param-appname']).toBe(
encodeURIComponent('Product™')
);
expect(response.headers['x-parse-page-param-publicserverurl']).toBe(
encodeURIComponent('http://localhost:8378/1')
);
});
});
describe('XSS Protection', () => {
beforeEach(async () => {
await reconfigureServer({
+5
View File
@@ -339,6 +339,11 @@ export class Config {
} else if (!(pages.customRoutes instanceof Array)) {
throw 'Parse Server option pages.customRoutes must be an array.';
}
if (pages.encodePageParamHeaders === undefined) {
pages.encodePageParamHeaders = PagesOptions.encodePageParamHeaders.default;
} else if (!isBoolean(pages.encodePageParamHeaders)) {
throw 'Parse Server option pages.encodePageParamHeaders must be a boolean.';
}
}
static validateIdempotencyOptions(idempotencyOptions) {
+5
View File
@@ -21,4 +21,9 @@ module.exports = [
changeNewDefault: '[]',
solution: "Set 'fileUpload.allowedFileUrlDomains' to the domains you want to allow, or to '[]' to block all file URLs.",
},
{
optionKey: 'pages.encodePageParamHeaders',
changeNewDefault: 'true',
solution: "Set 'pages.encodePageParamHeaders' to 'true' to URI-encode non-ASCII characters in page parameter headers.",
},
];
+6
View File
@@ -688,6 +688,12 @@ module.exports.PagesOptions = {
action: parsers.booleanParser,
default: false,
},
encodePageParamHeaders: {
env: 'PARSE_SERVER_PAGES_ENCODE_PAGE_PARAM_HEADERS',
help: 'Is `true` if the page parameter headers should be URI-encoded. This is required if any page parameter value contains non-ASCII characters, such as the app name.',
action: parsers.booleanParser,
default: false,
},
forceRedirect: {
env: 'PARSE_SERVER_PAGES_FORCE_REDIRECT',
help: 'Is true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response).',
+1
View File
@@ -137,6 +137,7 @@
* @property {PagesRoute[]} customRoutes The custom routes.
* @property {PagesCustomUrlsOptions} customUrls The URLs to the custom pages.
* @property {Boolean} enableLocalization Is true if pages should be localized; this has no effect on custom page redirects.
* @property {Boolean} encodePageParamHeaders Is `true` if the page parameter headers should be URI-encoded. This is required if any page parameter value contains non-ASCII characters, such as the app name.
* @property {Boolean} forceRedirect Is true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response).
* @property {String} localizationFallbackLocale The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file.
* @property {String} localizationJsonPath The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale.
+3
View File
@@ -445,6 +445,9 @@ export interface PagesOptions {
/* The custom routes.
:DEFAULT: [] */
customRoutes: ?(PagesRoute[]);
/* Is `true` if the page parameter headers should be URI-encoded. This is required if any page parameter value contains non-ASCII characters, such as the app name.
:DEFAULT: false */
encodePageParamHeaders: ?boolean;
}
export interface PagesRoute {
+4 -2
View File
@@ -454,9 +454,10 @@ export class PagesRouter extends PromiseRouter {
// Add placeholders in header to allow parsing for programmatic use
// of response, instead of having to parse the HTML content.
const encode = this.pagesConfig.encodePageParamHeaders;
const headers = Object.entries(params).reduce((m, p) => {
if (p[1] !== undefined) {
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = p[1];
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = encode ? encodeURIComponent(p[1]) : p[1];
}
return m;
}, {});
@@ -576,9 +577,10 @@ export class PagesRouter extends PromiseRouter {
// Add parameters to header to allow parsing for programmatic use
// of response, instead of having to parse the HTML content.
const encode = this.pagesConfig.encodePageParamHeaders;
const headers = Object.entries(params).reduce((m, p) => {
if (p[1] !== undefined) {
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = p[1];
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = encode ? encodeURIComponent(p[1]) : p[1];
}
return m;
}, {});