fix(twenty-shared): stop decoding URI components in URL normalization
https://sonarly.com/issue/15564?type=bug The `lowercaseUrlOriginAndRemoveTrailingSlash` function applies `decodeURIComponent` to URL pathname and search components, converting intentionally encoded characters like `%2F` to `/`, which breaks URLs that depend on encoding (e.g., Google Maps). Fix: Removed `safeDecodeURIComponent()` wrapping from `url.pathname` and `url.search` in `lowercaseUrlOriginAndRemoveTrailingSlash`. This function was added in commit `1119e3d77e` to handle accented characters, but `new URL()` already normalizes non-ASCII characters in pathnames (encoding `é` as `%C3%A9`). The `decodeURIComponent` call was incorrectly decoding structurally significant percent-encoded characters like `%2F` (encoded slash) back to `/`, breaking URLs that depend on encoding (e.g., Google Maps `data=` parameter). The fix restores the original `url.pathname + url.search + url.hash` concatenation, which correctly preserves all percent-encoding as provided by the URL constructor. The `safeDecodeURIComponent` utility itself is kept since it's still used by the IMAP message text extractor service. Updated test expectations to match correct URL normalization behavior: - Percent-encoded characters like `%2F`, `%20`, `%2520` are preserved as-is - Non-ASCII characters are encoded by the URL constructor (e.g., `é` → `%C3%A9`) - Added a Google Maps URL test case to prevent regression
This commit is contained in:
+21
-12
@@ -39,19 +39,19 @@ describe('lowercaseUrlOriginAndRemoveTrailingSlash', () => {
|
||||
expected: 'https://www.example.com/TEST#Hash',
|
||||
},
|
||||
{
|
||||
title: 'should preserve special characters in path',
|
||||
title: 'should encode special characters in path via URL normalization',
|
||||
input: 'https://test.test/edouard-ménard-22219837',
|
||||
expected: 'https://test.test/edouard-ménard-22219837',
|
||||
expected: 'https://test.test/edouard-m%C3%A9nard-22219837',
|
||||
},
|
||||
{
|
||||
title: 'should decode already encoded special characters in path',
|
||||
title: 'should preserve already encoded special characters in path',
|
||||
input: 'https://test.test/edouard-m%C3%A9nard-22219837',
|
||||
expected: 'https://test.test/edouard-ménard-22219837',
|
||||
expected: 'https://test.test/edouard-m%C3%A9nard-22219837',
|
||||
},
|
||||
{
|
||||
title: 'should preserve special characters in query params',
|
||||
title: 'should encode special characters in query params',
|
||||
input: 'https://example.com/path?name=José',
|
||||
expected: 'https://example.com/path?name=José',
|
||||
expected: 'https://example.com/path?name=Jos%C3%A9',
|
||||
},
|
||||
{
|
||||
title:
|
||||
@@ -60,13 +60,12 @@ describe('lowercaseUrlOriginAndRemoveTrailingSlash', () => {
|
||||
expected: 'https://example.com/test%E0%A4%A',
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should preserve double-encoded URLs (encoded percent signs stay encoded once)',
|
||||
title: 'should preserve double-encoded URLs without decoding them',
|
||||
input: 'https://example.com/test%2520name',
|
||||
expected: 'https://example.com/test%20name',
|
||||
expected: 'https://example.com/test%2520name',
|
||||
},
|
||||
{
|
||||
title: 'should preserve special characters in hash fragments',
|
||||
title: 'should encode special characters in hash fragments',
|
||||
input: 'https://example.com/path#frédéric',
|
||||
expected: 'https://example.com/path#fr%C3%A9d%C3%A9ric',
|
||||
},
|
||||
@@ -76,9 +75,19 @@ describe('lowercaseUrlOriginAndRemoveTrailingSlash', () => {
|
||||
expected: 'https://example.com/path#fr%C3%A9d%C3%A9ric',
|
||||
},
|
||||
{
|
||||
title: 'should handle mixed encoded and non-encoded in same URL',
|
||||
title:
|
||||
'should preserve percent-encoded slashes and query params without decoding',
|
||||
input: 'https://example.com/path%2Fwith%2Fslashes?query=hello%20world',
|
||||
expected: 'https://example.com/path/with/slashes?query=hello world',
|
||||
expected:
|
||||
'https://example.com/path%2Fwith%2Fslashes?query=hello%20world',
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should preserve percent-encoded characters in Google Maps URLs',
|
||||
input:
|
||||
'https://www.google.com/maps/place/Test/data=!16s%2Fg%2F1ptwh8096',
|
||||
expected:
|
||||
'https://www.google.com/maps/place/Test/data=!16s%2Fg%2F1ptwh8096',
|
||||
},
|
||||
])('$title', ({ input, expected }) => {
|
||||
expect(lowercaseUrlOriginAndRemoveTrailingSlash(input)).toBe(expected);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getURLSafely } from '@/utils/getURLSafely';
|
||||
import { isDefined } from '@/utils/validation';
|
||||
import { safeDecodeURIComponent } from './safeDecodeURIComponent';
|
||||
|
||||
export const lowercaseUrlOriginAndRemoveTrailingSlash = (rawUrl: string) => {
|
||||
const url = getURLSafely(rawUrl);
|
||||
@@ -10,10 +9,7 @@ export const lowercaseUrlOriginAndRemoveTrailingSlash = (rawUrl: string) => {
|
||||
}
|
||||
|
||||
const lowercaseOrigin = url.origin.toLowerCase();
|
||||
const path =
|
||||
safeDecodeURIComponent(url.pathname) +
|
||||
safeDecodeURIComponent(url.search) +
|
||||
url.hash;
|
||||
const path = url.pathname + url.search + url.hash;
|
||||
|
||||
return (lowercaseOrigin + path).replace(/\/$/, '');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user