Skip to content

Commit cddd17a

Browse files
committed
fix use searchParams original encoding
1 parent 0f6f081 commit cddd17a

File tree

3 files changed

+37
-22
lines changed

3 files changed

+37
-22
lines changed

packages/open-next/src/core/routing/matcher.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,19 +196,16 @@ export function handleRewrites<T extends RewriteDefinition>(
196196
);
197197
// We need to use a localized path if the rewrite is not locale specific
198198
const pathToUse = rewrite.locale === false ? rawPath : localizedRawPath;
199-
// We need to encode the "+" character with its UTF-8 equivalent "%20" to avoid 2 issues:
200-
// 1. The compile function from path-to-regexp will throw an error if it finds a "+" character.
201-
// https://github.com/pillarjs/path-to-regexp?tab=readme-ov-file#unexpected--or-
202-
// 2. The convertToQueryString function will replace the "+" character with %2B instead of %20.
203-
// %2B does not get interpreted as a space in the URL thus breaking the query string.
204-
const encodePlusQueryString = queryString.replaceAll("+", "%20");
199+
205200
debug("urlParts", { pathname, protocol, hostname, queryString });
206-
const toDestinationPath = compile(escapeRegex(pathname));
201+
const toDestinationPath = compile(escapeRegex(pathname, { isPath: true }));
207202
const toDestinationHost = compile(escapeRegex(hostname));
208-
const toDestinationQuery = compile(escapeRegex(encodePlusQueryString));
203+
const toDestinationQuery = compile(escapeRegex(queryString));
209204
const params = {
210205
// params for the source
211-
...getParamsFromSource(match(escapeRegex(rewrite.source)))(pathToUse),
206+
...getParamsFromSource(
207+
match(escapeRegex(rewrite.source, { isPath: true })),
208+
)(pathToUse),
212209
// params for the has
213210
...rewrite.has?.reduce((acc, cur) => {
214211
return Object.assign(acc, computeHas(cur));
@@ -219,7 +216,7 @@ export function handleRewrites<T extends RewriteDefinition>(
219216
}, {}),
220217
};
221218
const isUsingParams = Object.keys(params).length > 0;
222-
let rewrittenQuery = encodePlusQueryString;
219+
let rewrittenQuery = queryString;
223220
let rewrittenHost = hostname;
224221
let rewrittenPath = pathname;
225222
if (isUsingParams) {

packages/open-next/src/core/routing/util.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,20 @@ export function convertRes(res: OpenNextNodeResponse): InternalResult {
122122
* Make sure that multi-value query parameters are transformed to
123123
* ?key=value1&key=value2&... so that Next converts those parameters
124124
* to an array when reading the query parameters
125+
* query should be properly encoded before using this function
125126
* @__PURE__
126127
*/
127128
export function convertToQueryString(query: Record<string, string | string[]>) {
128-
// URLSearchParams is a representation of the PARSED query.
129-
// So we must decode the value before appending it to the URLSearchParams.
130-
// https://stackoverflow.com/a/45516812
131-
const urlQuery = new URLSearchParams();
129+
const queryStrings: string[] = [];
132130
Object.entries(query).forEach(([key, value]) => {
133131
if (Array.isArray(value)) {
134-
value.forEach((entry) => urlQuery.append(key, decodeURIComponent(entry)));
132+
value.forEach((entry) => queryStrings.push(`${key}=${entry}`));
135133
} else {
136-
urlQuery.append(key, decodeURIComponent(value));
134+
queryStrings.push(`${key}=${value}`);
137135
}
138136
});
139-
const queryString = urlQuery.toString();
140137

141-
return queryString ? `?${queryString}` : "";
138+
return queryStrings.length > 0 ? `?${queryStrings.join("&")}` : "";
142139
}
143140

144141
/**
@@ -182,11 +179,15 @@ export function getMiddlewareMatch(
182179
*
183180
* @__PURE__
184181
*/
185-
export function escapeRegex(str: string) {
186-
return str
182+
export function escapeRegex(
183+
str: string,
184+
{ isPath }: { isPath?: boolean } = {},
185+
) {
186+
const result = str
187187
.replaceAll("(.)", "_µ1_")
188188
.replaceAll("(..)", "_µ2_")
189189
.replaceAll("(...)", "_µ3_");
190+
return isPath ? result : result.replaceAll("+", "_µ4_");
190191
}
191192

192193
/**
@@ -197,7 +198,8 @@ export function unescapeRegex(str: string) {
197198
return str
198199
.replaceAll("_µ1_", "(.)")
199200
.replaceAll("_µ2_", "(..)")
200-
.replaceAll("_µ3_", "(...)");
201+
.replaceAll("_µ3_", "(...)")
202+
.replaceAll("_µ4_", "+");
201203
}
202204

203205
/**

packages/tests-unit/tests/core/routing/util.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ describe("convertToQueryString", () => {
308308
"?key=value1&key=value2&another=value3",
309309
);
310310
});
311+
312+
it("should respect existing query encoding", () => {
313+
const query = {
314+
key: ["value%201", "value2+something+else"],
315+
another: "value3",
316+
};
317+
expect(convertToQueryString(query)).toBe(
318+
"?key=value%201&key=value2+something+else&another=value3",
319+
);
320+
});
311321
});
312322

313323
describe("convertToQuery", () => {
@@ -383,13 +393,18 @@ describe("regex", () => {
383393
["/a/b/(..)(..)c", "/a/b/_µ2__µ2_c"],
384394
["/a/(...)b", "/a/_µ3_b"],
385395
["/feed/(..)photo/[id]", "/feed/_µ2_photo/[id]"],
396+
["?something=a+b", "?something=a_µ4_b"],
386397
])(
387-
"should escape (.), (..), (...) with _µ1_, _µ2_, _µ3_ - %s",
398+
"should escape (.), (..), (...), + with _µ1_, _µ2_, _µ3_, _µ4_ - %s",
388399
(input, expected) => {
389400
const result = escapeRegex(input);
390401
expect(result).toBe(expected);
391402
},
392403
);
404+
405+
it("should not escape plus for a path", () => {
406+
expect(escapeRegex("/:path+", { isPath: true })).toBe("/:path+");
407+
});
393408
});
394409

395410
describe("unescapeRegex", () => {
@@ -399,6 +414,7 @@ describe("regex", () => {
399414
["/a/b/_µ2__µ2_c", "/a/b/(..)(..)c"],
400415
["/a/_µ3_b", "/a/(...)b"],
401416
["/feed/_µ2_photo/[id]", "/feed/(..)photo/[id]"],
417+
["?something=a_µ4_b", "?something=a+b"],
402418
])(
403419
"should unescape _µ1_, _µ2_, _µ3_ with (.), (..), (...) - %s",
404420
(input, expected) => {

0 commit comments

Comments
 (0)