From b82fb45a1729d51515c891fb250e7848400fe735 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 24 Feb 2024 04:15:46 +0530 Subject: [PATCH 1/6] fix: Video - Invalid file is downloaded when downloading video. Signed-off-by: Krishna Gupta --- src/CONST.ts | 1 + src/components/Image/index.js | 5 +++-- src/libs/addEncryptedAuthTokenToURL.ts | 3 ++- src/libs/fileDownload/FileUtils.ts | 14 +++++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ce1295d2d71f..96a22068958f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1003,6 +1003,7 @@ const CONST = { ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE: 'data-expensify-width', ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE: 'data-expensify-height', ATTACHMENT_DURATION_ATTRIBUTE: 'data-expensify-duration', + ENCRYPTED_AUTH_TOKEN_KEY: 'encryptedAuthToken', ATTACHMENT_PICKER_TYPE: { FILE: 'file', diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 59fcde8273fd..843f0aa6776e 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -4,6 +4,7 @@ import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useNetwork from '@hooks/useNetwork'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; @@ -21,8 +22,8 @@ function Image(props) { // There is currently a `react-native-web` bug preventing the authToken being passed // in the headers of the image request so the authToken is added as a query param. // On native the authToken IS passed in the image request headers - const authToken = lodashGet(session, 'encryptedAuthToken', null); - return {uri: `${propsSource.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; + const authToken = lodashGet(session, CONST.ENCRYPTED_AUTH_TOKEN_KEY, null); + return {uri: `${propsSource.uri}?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=${encodeURIComponent(authToken)}`}; } return propsSource; // The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034. diff --git a/src/libs/addEncryptedAuthTokenToURL.ts b/src/libs/addEncryptedAuthTokenToURL.ts index ea6f0bc6d34d..3aa881bd77b6 100644 --- a/src/libs/addEncryptedAuthTokenToURL.ts +++ b/src/libs/addEncryptedAuthTokenToURL.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; let encryptedAuthToken = ''; @@ -11,5 +12,5 @@ Onyx.connect({ * Add encryptedAuthToken to this attachment URL */ export default function (url: string) { - return `${url}?encryptedAuthToken=${encodeURIComponent(encryptedAuthToken)}`; + return `${url}?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=${encodeURIComponent(encryptedAuthToken)}`; } diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index 055abf140e64..aa51dcccf82b 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -87,7 +87,19 @@ function showCameraPermissionsAlert() { * with underscores. */ function getFileName(url: string): string { - const fileName = url.split(/[#?/]/).pop() ?? ''; + let fileName = url; + + // Remove encryptedAuthToken if exists + if (fileName.includes(`?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=`)) { + fileName = fileName.split(`?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=`)[0]; + } + + fileName = fileName.split(/[#?/]/).pop() ?? ''; + + if (!fileName) { + Log.warn('[FileUtils] Could not get attachment name', {url}); + } + if (!fileName) { Log.warn('[FileUtils] Could not get attachment name', {url}); } From 0de3817d596e0a4d021a49c825c68eda1d9f16c6 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 24 Feb 2024 04:34:04 +0530 Subject: [PATCH 2/6] minor refactor and comment update. Signed-off-by: Krishna Gupta --- src/libs/fileDownload/FileUtils.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index aa51dcccf82b..c0978e5ff5ca 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -80,21 +80,13 @@ function showCameraPermissionsAlert() { * Extracts a filename from a given URL and sanitizes it for file system usage. * * This function takes a URL as input and performs the following operations: - * 1. Extracts the last segment of the URL, which could be a file name, a path segment, - * or a query string parameter. + * 1. Extracts the last segment of the URL. * 2. Decodes the extracted segment from URL encoding to a plain string for better readability. * 3. Replaces any characters in the decoded string that are illegal in file names * with underscores. */ function getFileName(url: string): string { - let fileName = url; - - // Remove encryptedAuthToken if exists - if (fileName.includes(`?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=`)) { - fileName = fileName.split(`?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=`)[0]; - } - - fileName = fileName.split(/[#?/]/).pop() ?? ''; + const fileName = url.split('/').pop()?.split('?')[0].split('#')[0] ?? ''; if (!fileName) { Log.warn('[FileUtils] Could not get attachment name', {url}); @@ -123,7 +115,7 @@ function getFileType(fileUrl: string): string | undefined { return; } - const fileName = fileUrl.split('/').pop()?.split('?')[0].split('#')[0]; + const fileName = getFileName(fileUrl); if (!fileName) { return; From d01315586763808e663bb1def44dbfeaa0e4a0fc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 24 Feb 2024 04:39:30 +0530 Subject: [PATCH 3/6] Revert "fix: Video - Invalid file is downloaded when downloading video." This reverts commit b82fb45a1729d51515c891fb250e7848400fe735. --- src/CONST.ts | 1 - src/components/Image/index.js | 5 ++--- src/libs/addEncryptedAuthTokenToURL.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 96a22068958f..ce1295d2d71f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1003,7 +1003,6 @@ const CONST = { ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE: 'data-expensify-width', ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE: 'data-expensify-height', ATTACHMENT_DURATION_ATTRIBUTE: 'data-expensify-duration', - ENCRYPTED_AUTH_TOKEN_KEY: 'encryptedAuthToken', ATTACHMENT_PICKER_TYPE: { FILE: 'file', diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 843f0aa6776e..59fcde8273fd 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -4,7 +4,6 @@ import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useNetwork from '@hooks/useNetwork'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; @@ -22,8 +21,8 @@ function Image(props) { // There is currently a `react-native-web` bug preventing the authToken being passed // in the headers of the image request so the authToken is added as a query param. // On native the authToken IS passed in the image request headers - const authToken = lodashGet(session, CONST.ENCRYPTED_AUTH_TOKEN_KEY, null); - return {uri: `${propsSource.uri}?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=${encodeURIComponent(authToken)}`}; + const authToken = lodashGet(session, 'encryptedAuthToken', null); + return {uri: `${propsSource.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; } return propsSource; // The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034. diff --git a/src/libs/addEncryptedAuthTokenToURL.ts b/src/libs/addEncryptedAuthTokenToURL.ts index 3aa881bd77b6..ea6f0bc6d34d 100644 --- a/src/libs/addEncryptedAuthTokenToURL.ts +++ b/src/libs/addEncryptedAuthTokenToURL.ts @@ -1,5 +1,4 @@ import Onyx from 'react-native-onyx'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; let encryptedAuthToken = ''; @@ -12,5 +11,5 @@ Onyx.connect({ * Add encryptedAuthToken to this attachment URL */ export default function (url: string) { - return `${url}?${CONST.ENCRYPTED_AUTH_TOKEN_KEY}=${encodeURIComponent(encryptedAuthToken)}`; + return `${url}?encryptedAuthToken=${encodeURIComponent(encryptedAuthToken)}`; } From 2f3da03a1bb0cf2d58657aab0cb9296e1267625c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 24 Feb 2024 04:40:31 +0530 Subject: [PATCH 4/6] minor fix. Signed-off-by: Krishna Gupta --- src/libs/fileDownload/FileUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index c0978e5ff5ca..70ab01f62466 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -92,10 +92,6 @@ function getFileName(url: string): string { Log.warn('[FileUtils] Could not get attachment name', {url}); } - if (!fileName) { - Log.warn('[FileUtils] Could not get attachment name', {url}); - } - return decodeURIComponent(fileName).replace(CONST.REGEX.ILLEGAL_FILENAME_CHARACTERS, '_'); } From 4e68e2f995f75b56f917c579bd28841a1a25267e Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 25 Feb 2024 02:37:22 +0530 Subject: [PATCH 5/6] fix: download issue on desktop & native devices. Signed-off-by: Krishna Gupta --- src/components/VideoPlayerContexts/VideoPopoverMenuContext.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js index 801c1b2f44ca..ed502136716f 100644 --- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js +++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js @@ -5,7 +5,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import fileDownload from '@libs/fileDownload'; -import * as Url from '@libs/Url'; +import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import {usePlaybackContext} from './PlaybackContext'; @@ -27,7 +27,7 @@ function VideoPopoverMenuContextProvider({children}) { const downloadAttachment = useCallback(() => { currentVideoPlayerRef.current.getStatusAsync().then((status) => { - const sourceURI = `/${Url.getPathFromURL(status.uri)}`; + const sourceURI = tryResolveUrlFromApiRoot(status.uri); fileDownload(sourceURI); }); }, [currentVideoPlayerRef]); From 4e54e815b58fc43a14cfb65129f1233a2529057e Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 26 Feb 2024 09:54:04 +0530 Subject: [PATCH 6/6] fix failing video downloads on native. Signed-off-by: Krishna Gupta --- .../VideoPlayerContexts/VideoPopoverMenuContext.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js index ed502136716f..4bb10e526fe4 100644 --- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js +++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.js @@ -4,15 +4,15 @@ import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import fileDownload from '@libs/fileDownload'; -import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import {usePlaybackContext} from './PlaybackContext'; const VideoPopoverMenuContext = React.createContext(null); function VideoPopoverMenuContextProvider({children}) { - const {currentVideoPlayerRef} = usePlaybackContext(); + const {currentVideoPlayerRef, currentlyPlayingURL} = usePlaybackContext(); const {translate} = useLocalize(); const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]); const {isOffline} = useNetwork(); @@ -26,11 +26,9 @@ function VideoPopoverMenuContextProvider({children}) { ); const downloadAttachment = useCallback(() => { - currentVideoPlayerRef.current.getStatusAsync().then((status) => { - const sourceURI = tryResolveUrlFromApiRoot(status.uri); - fileDownload(sourceURI); - }); - }, [currentVideoPlayerRef]); + const sourceURI = currentlyPlayingURL.startsWith('blob:') || currentlyPlayingURL.startsWith('file:') ? currentlyPlayingURL : addEncryptedAuthTokenToURL(currentlyPlayingURL); + fileDownload(sourceURI); + }, [currentlyPlayingURL]); const menuItems = useMemo(() => { const items = [];