diff --git a/app/components/document/index.tsx b/app/components/document/index.tsx index 667556344..c5be91773 100644 --- a/app/components/document/index.tsx +++ b/app/components/document/index.tsx @@ -51,7 +51,7 @@ const Document = forwardRef(({canDownloadFiles, chil let exists = false; try { - path = decodeURIComponent(file.localPath || ''); + path = file.localPath || ''; if (path) { exists = await fileExists(path); } @@ -103,7 +103,7 @@ const Document = forwardRef(({canDownloadFiles, chil const openDocument = useCallback(async () => { if (!didCancel && !preview) { - let path = decodeURIComponent(file.localPath || ''); + let path = file.localPath || ''; let exists = false; if (path) { exists = await fileExists(path); @@ -116,7 +116,7 @@ const Document = forwardRef(({canDownloadFiles, chil setPreview(true); setStatusBarColor('dark-content'); FileViewer.open(path!.replace('file://', ''), { - displayName: decodeURIComponent(file.name), + displayName: file.name, onDismiss: onDonePreviewingFile, showOpenWithDialog: true, showAppsSuggestions: true, diff --git a/app/components/files/file_info.tsx b/app/components/files/file_info.tsx index cd6ab5f64..957f35809 100644 --- a/app/components/files/file_info.tsx +++ b/app/components/files/file_info.tsx @@ -79,7 +79,7 @@ const FileInfo = ({disabled, file, channelName, showDate, onPress}: FileInfoProp ellipsizeMode='tail' style={style.fileName} > - {decodeURIComponent(file.name.trim())} + {file.name.trim()} {channelName && diff --git a/app/components/markdown/markdown_image/index.tsx b/app/components/markdown/markdown_image/index.tsx index 23bead8ca..6b6c5dd3c 100644 --- a/app/components/markdown/markdown_image/index.tsx +++ b/app/components/markdown/markdown_image/index.tsx @@ -30,7 +30,7 @@ import {calculateDimensions, getViewPortWidth, isGifTooLarge} from '@utils/image import {getMarkdownImageSize} from '@utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {secureGetFromRecord} from '@utils/types'; -import {normalizeProtocol, tryOpenURL} from '@utils/url'; +import {normalizeProtocol, safeDecodeURIComponent, tryOpenURL} from '@utils/url'; import type {GalleryItemType} from '@typings/screens/gallery'; @@ -89,8 +89,8 @@ const MarkdownImage = ({ const uri = source.startsWith('/') ? serverUrl + source : source; const fileInfo = useMemo(() => { - const link = decodeURIComponent(uri); - let filename = parseUrl(link.substr(link.lastIndexOf('/'))).pathname.replace('/', ''); + const decodedLink = safeDecodeURIComponent(uri); + let filename = parseUrl(decodedLink.substr(decodedLink.lastIndexOf('/'))).pathname.replace('/', ''); let extension = metadata?.format || filename.split('.').pop(); if (extension === filename) { const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.')); @@ -105,7 +105,7 @@ const MarkdownImage = ({ has_preview_image: true, mime_type: lookupMimeType(filename), post_id: postId, - uri: link, + uri, width: originalSize.width, height: originalSize.height, } as FileInfo; diff --git a/app/components/markdown/markdown_table_image/index.tsx b/app/components/markdown/markdown_table_image/index.tsx index a285b141f..0c423979e 100644 --- a/app/components/markdown/markdown_table_image/index.tsx +++ b/app/components/markdown/markdown_table_image/index.tsx @@ -14,6 +14,7 @@ import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery'; import {generateId} from '@utils/general'; import {calculateDimensions, isGifTooLarge} from '@utils/images'; import {secureGetFromRecord} from '@utils/types'; +import {safeDecodeURIComponent} from '@utils/url'; import type {GalleryItemType} from '@typings/screens/gallery'; @@ -58,8 +59,9 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL, const getFileInfo = () => { const height = metadata?.height || 0; const width = metadata?.width || 0; - const link = decodeURIComponent(getImageSource()); - let filename = parseUrl(link.substr(link.lastIndexOf('/'))).pathname.replace('/', ''); + const uri = getImageSource(); + const decodedLink = safeDecodeURIComponent(uri); + let filename = parseUrl(decodedLink.substring(decodedLink.lastIndexOf('/'))).pathname.replace('/', ''); let extension = filename.split('.').pop(); if (extension === filename) { @@ -74,7 +76,7 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL, extension, has_preview_image: true, post_id: postId, - uri: link, + uri, width, height, }; diff --git a/app/screens/channel_bookmark/components/bookmark_file/bookmark_file.tsx b/app/screens/channel_bookmark/components/bookmark_file/bookmark_file.tsx index 4fa463492..cf5d670bc 100644 --- a/app/screens/channel_bookmark/components/bookmark_file/bookmark_file.tsx +++ b/app/screens/channel_bookmark/components/bookmark_file/bookmark_file.tsx @@ -308,7 +308,7 @@ const BookmarkFile = ({channelId, close, disabled, initialFile, maxFileSize, set ellipsizeMode='tail' style={styles.filename} > - {decodeURIComponent(file.name.trim())} + {file.name.trim()} {info} diff --git a/app/screens/channel_bookmark/index.tsx b/app/screens/channel_bookmark/index.tsx index 81f310494..18501669b 100644 --- a/app/screens/channel_bookmark/index.tsx +++ b/app/screens/channel_bookmark/index.tsx @@ -159,13 +159,13 @@ const ChannelBookmarkAddOrEdit = ({ ...(bookmark || emptyBookmark), owner_id: ownerId, channel_id: channelId, - display_name: decodeURIComponent(f.name), + display_name: f.name, type: 'file', file_id: f.id, }; setBookmarkToSave(b); setFile(f); - }, [bookmark, channelId, ownerId]); + }, [bookmark, channelId, ownerId, setBookmarkToSave]); const setBookmarkDisplayName = useCallback((displayName: string) => { if (bookmark) { diff --git a/app/screens/gallery/footer/download_with_action/index.tsx b/app/screens/gallery/footer/download_with_action/index.tsx index 63d99f856..1110a3212 100644 --- a/app/screens/gallery/footer/download_with_action/index.tsx +++ b/app/screens/gallery/footer/download_with_action/index.tsx @@ -273,7 +273,7 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, gallery actionToExecute({ code: 200, ok: true, - data: {path}, + data: {path: path.replace('file://', '')}, }); } else { if (item.type === 'avatar') { diff --git a/app/utils/file/file_picker/index.ts b/app/utils/file/file_picker/index.ts index 3daec52fe..2e008733f 100644 --- a/app/utils/file/file_picker/index.ts +++ b/app/utils/file/file_picker/index.ts @@ -11,6 +11,7 @@ import Permissions from 'react-native-permissions'; import {dismissBottomSheet} from '@screens/navigation'; import {extractFileInfo, lookupMimeType} from '@utils/file'; import {logWarning} from '@utils/log'; +import {safeDecodeURIComponent} from '@utils/url'; import type {IntlShape} from 'react-intl'; @@ -128,7 +129,7 @@ export default class FilePickerUtil { const type = file.type || lookupMimeType(uri); let fileName = file.fileName; if (type.includes('video/') && uri) { - fileName = decodeURIComponent(uri.split('\\').pop()?.split('/').pop() || ''); + fileName = safeDecodeURIComponent(uri.split('\\').pop()?.split('/').pop() || ''); } if (uri) { diff --git a/app/utils/file/index.ts b/app/utils/file/index.ts index ac495ecbf..f075da0a1 100644 --- a/app/utils/file/index.ts +++ b/app/utils/file/index.ts @@ -413,7 +413,7 @@ export async function extractFileInfo(files: Array { + test('should decode a valid URI component', () => { + const encoded = 'Hello%20World'; + const decoded = safeDecodeURIComponent(encoded); + expect(decoded).toBe('Hello World'); + }); + + test('should return the input if it is not a valid URI component', () => { + const invalidEncoded = '%E0%A4%A'; + const result = safeDecodeURIComponent(invalidEncoded); + expect(result).toBe(invalidEncoded); + }); + + test('should decode a complex URI component', () => { + const encoded = 'Hello%20World%21%20How%20are%20you%3F'; + const decoded = safeDecodeURIComponent(encoded); + expect(decoded).toBe('Hello World! How are you?'); + }); + + test('should return empty string if input is empty', () => { + const encoded = ''; + const decoded = safeDecodeURIComponent(encoded); + expect(decoded).toBe(''); + }); +}); diff --git a/app/utils/url/index.ts b/app/utils/url/index.ts index def29fcbe..2054d7c85 100644 --- a/app/utils/url/index.ts +++ b/app/utils/url/index.ts @@ -252,3 +252,11 @@ export function extractFilenameFromUrl(url: string) { const uri = urlParse(url); return uri.pathname.split('/').pop(); } + +export function safeDecodeURIComponent(v: string) { + try { + return decodeURIComponent(v); + } catch { + return v; + } +}