fix(twenty-shared): preserve special characters in URLs#16312
fix(twenty-shared): preserve special characters in URLs#16312prastoin merged 5 commits intotwentyhq:mainfrom
Conversation
The lowercaseUrlOriginAndRemoveTrailingSlash function was encoding special characters (like accented letters) because URL.pathname automatically encodes them. This caused URLs like: https://test.test/frédéric-destombes-22219837 to become: https://test.test/fr%C3%A9d%C3%A9ric-destombes-22219837 Fixed by using decodeURIComponent on pathname and search params. Fixes twentyhq#16225
| const path = | ||
| decodeURIComponent(url.pathname) + decodeURIComponent(url.search) + url.hash; |
There was a problem hiding this comment.
Bug: decodeURIComponent() calls in lowercaseUrlOriginAndRemoveTrailingSlash lack error handling, leading to server crashes for malformed URLs.
Severity: CRITICAL | Confidence: High
🔍 Detailed Analysis
The lowercaseUrlOriginAndRemoveTrailingSlash function uses decodeURIComponent() on url.pathname and url.search without error handling. decodeURIComponent() throws a URIError for malformed percent-encoding (e.g., %invalid). The URL constructor accepts such malformed URLs, allowing url.pathname to contain sequences that will cause decodeURIComponent() to throw. Since the function processes user-provided URLs from APIs, a malformed URL like https://example.com/path%invalid will lead to an uncaught URIError, crashing the server.
💡 Suggested Fix
Replace direct calls to decodeURIComponent() with the existing safeDecodeURIComponent() utility to gracefully handle malformed percent-encoding.
🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location:
packages/twenty-shared/src/utils/url/lowercaseUrlOriginAndRemoveTrailingSlash.ts#L12-L13
Potential issue: The `lowercaseUrlOriginAndRemoveTrailingSlash` function uses
`decodeURIComponent()` on `url.pathname` and `url.search` without error handling.
`decodeURIComponent()` throws a `URIError` for malformed percent-encoding (e.g.,
`%invalid`). The `URL` constructor accepts such malformed URLs, allowing `url.pathname`
to contain sequences that will cause `decodeURIComponent()` to throw. Since the function
processes user-provided URLs from APIs, a malformed URL like
`https://example.com/path%invalid` will lead to an uncaught `URIError`, crashing the
server.
Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 5455160
Greptile OverviewGreptile SummaryThis PR fixes URL encoding issues where special characters (accented letters) were being percent-encoded when saved via REST or GraphQL APIs. The fix decodes Key Changes:
Critical Issue:
Confidence Score: 2/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant API as REST/GraphQL API
participant Transform as transformLinksValue
participant Util as lowercaseUrlOriginAndRemoveTrailingSlash
participant URL as URL Parser
API->>Transform: Save URL with special chars<br/>(e.g., "frédéric")
Transform->>Util: Process URL
Util->>URL: new URL(rawUrl)
Note over URL: Automatically encodes<br/>pathname to "%C3%A9"
URL-->>Util: URL object with encoded pathname
Util->>Util: decodeURIComponent(pathname)
Note over Util: ⚠️ Can throw URIError<br/>on malformed encoding
Util->>Util: decodeURIComponent(search)
Util-->>Transform: Decoded URL with "frédéric"
Transform-->>API: Store decoded URL
|
| const lowercaseOrigin = url.origin.toLowerCase(); | ||
| const path = url.pathname + url.search + url.hash; | ||
| const path = | ||
| decodeURIComponent(url.pathname) + decodeURIComponent(url.search) + url.hash; |
There was a problem hiding this comment.
logic: decodeURIComponent throws URIError on malformed percent-encoded strings. The codebase already has safeDecodeURIComponent in packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts that handles this.
| decodeURIComponent(url.pathname) + decodeURIComponent(url.search) + url.hash; | |
| safeDecodeURIComponent(url.pathname) + safeDecodeURIComponent(url.search) + url.hash; |
Consider moving safeDecodeURIComponent to twenty-shared/utils and using it here to prevent runtime errors on malformed URLs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-shared/src/utils/url/lowercaseUrlOriginAndRemoveTrailingSlash.ts
Line: 13:13
Comment:
**logic:** `decodeURIComponent` throws `URIError` on malformed percent-encoded strings. The codebase already has `safeDecodeURIComponent` in `packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts` that handles this.
```suggestion
safeDecodeURIComponent(url.pathname) + safeDecodeURIComponent(url.search) + url.hash;
```
Consider moving `safeDecodeURIComponent` to `twenty-shared/utils` and using it here to prevent runtime errors on malformed URLs.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where URLs with special characters (like accented letters) were being unintentionally percent-encoded when saved via REST or GraphQL APIs. The fix decodes the pathname and search parameters to preserve the original special characters.
Key Changes:
- Modified
lowercaseUrlOriginAndRemoveTrailingSlashto decode pathname and search parameters usingdecodeURIComponent() - Added test cases to verify special characters are preserved in paths and query parameters
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
packages/twenty-shared/src/utils/url/lowercaseUrlOriginAndRemoveTrailingSlash.ts |
Added decodeURIComponent() calls to pathname and search parameters to preserve special characters |
packages/twenty-shared/src/utils/url/__tests__/lowercaseUrlOriginAndRemoveTrailingSlash.test.ts |
Added test cases for URLs with special characters in paths and query parameters |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const path = | ||
| decodeURIComponent(url.pathname) + decodeURIComponent(url.search) + url.hash; |
There was a problem hiding this comment.
The decodeURIComponent() calls can throw a URIError if the URL contains malformed percent-encoding sequences (e.g., %E0%A4%A - incomplete UTF-8 sequence). This should be wrapped in a try-catch block to prevent the application from crashing. Consider falling back to the original encoded values if decoding fails.
Example fix:
const decodeSafely = (str: string) => {
try {
return decodeURIComponent(str);
} catch {
return str;
}
};
const path = decodeSafely(url.pathname) + decodeSafely(url.search) + url.hash;
packages/twenty-shared/src/utils/url/__tests__/lowercaseUrlOriginAndRemoveTrailingSlash.test.ts
Show resolved
Hide resolved
…ge import - Created safeDecodeURIComponent utility in twenty-shared/utils/url - Fixed broken import in lowercaseUrlOriginAndRemoveTrailingSlash.ts that was incorrectly importing from twenty-server's internal path - Updated twenty-server's imap-message-text-extractor.service.ts to use the shared utility from twenty-shared/utils - Removed duplicate utility from twenty-server This consolidates the safeDecodeURIComponent function into the shared package, following the existing pattern for cross-package utility sharing.
- Malformed percent-encoding (incomplete sequences like %E0%A4%A) - Double-encoded URLs (%2520 -> %20) - Special characters in hash fragments (URL.hash encodes them) - Mixed encoded/non-encoded characters in same URL
|
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:8259 This environment will automatically shut down when the PR is closed or after 5 hours. |
|
|
||
| const lowercaseOrigin = url.origin.toLowerCase(); | ||
| const path = url.pathname + url.search + url.hash; | ||
| const path = |
There was a problem hiding this comment.
Remark: We don't want inserting null values here
We should not use the safe here but throw in case the link is invalid by definition
This requires a wider refactor checking that all callers of this method do validate the link before being passed to this method
Nevermind double checked the safeDecodeURIComponent implem and it's returning the text
export const safeDecodeURIComponent = (text: string): string => {
try {
return decodeURIComponent(text);
} catch {
return text;
}
};There was a problem hiding this comment.
Thanks for double-checking! Yes, it falls back to the original text if decoding fails.
Can you plrase approve my PR?
prastoin
left a comment
There was a problem hiding this comment.
Thanks for your contribution !
|
Thanks @asasin235 for your contribution! |

Fix: URL Encoding Bug & Code Refactor
Issue
Bug: URLs with encoded characters (e.g.,
%20for spaces) were being double-encoded or incorrectly processed in lowercaseUrlOriginAndRemoveTrailingSlash, causing URL mismatches and potential data integrity issues.Build Error:
TS2307: Cannot find module 'src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util'Root Cause Analysis
1. Missing URL Decoding
The lowercaseUrlOriginAndRemoveTrailingSlash function was processing URLs without properly decoding URI components. When URLs contained encoded characters like
%20,%2F, etc., they weren't being normalized correctly.2. Invalid Cross-Package Import
The fix attempted to import safeDecodeURIComponent from
twenty-server:This failed because:
twenty-shared, a separate packagetwenty-shared/utils) for cross-package imports, not direct file pathsSolution
1. Bug Fix: Added Safe URI Decoding
Updated lowercaseUrlOriginAndRemoveTrailingSlash.ts to properly decode URL components:
The safeDecodeURIComponent wrapper handles malformed URI sequences gracefully by returning the original string if decoding fails, preventing runtime crashes.
2. Refactor: Consolidated Shared Utility
Before: Duplicate utility existed in
twenty-serverAfter: Single source of truth in
twenty-sharedThis follows the established pattern in the codebase where shared utilities live in
twenty-sharedand are imported via subpath exports:Files Changed
twenty-server/.../imap-message-text-extractor.service.tstwenty-shared/utilstwenty-server/.../safe-decode-uri-component.util.tsThe Utility
This wrapper is necessary because
decodeURIComponent()throws aURIErroron malformed sequences (e.g.,%E0%A4%A). The safe version returns the original string instead of crashing.Testing
twenty-sharedlowercaseUrlOriginAndRemoveTrailingSlash.test.tsvalidates URL normalization behaviorImpact