diff --git a/android/app/build.gradle b/android/app/build.gradle index f224d895e2fa..f7a83db89d9a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040801 - versionName "1.4.8-1" + versionCode 1001040802 + versionName "1.4.8-2" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7c3fbf13697a..9465f768afe1 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.8.1 + 1.4.8.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 0d2561b67b74..b4ae3184e624 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.8.1 + 1.4.8.2 diff --git a/package-lock.json b/package-lock.json index 0bc56cb5d907..8ba5f7f109c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.8-1", + "version": "1.4.8-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.8-1", + "version": "1.4.8-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f0b48a32e5e7..875dc572a3fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.8-1", + "version": "1.4.8-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index b6cc0cbf21a4..fbc49590d5ae 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -27,8 +27,8 @@ const propTypes = { /** Additional information about the attachment file */ file: PropTypes.shape({ /** File name of the attachment */ - name: PropTypes.string, - }), + name: PropTypes.string.isRequired, + }).isRequired, /** Whether the attachment has been flagged */ hasBeenFlagged: PropTypes.bool, diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 0f1fa15c99ca..6cd85e4243b2 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -1,6 +1,7 @@ import {Parser as HtmlParser} from 'htmlparser2'; import lodashGet from 'lodash/get'; import _ from 'underscore'; +import * as FileUtils from '@libs/fileDownload/FileUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -25,14 +26,16 @@ function extractAttachmentsFromReport(parentReportAction, reportActions, transac } const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]; + const source = tryResolveUrlFromApiRoot(expensifySource || attribs.src); + const fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`); // By iterating actions in chronological order and prepending each attachment // we ensure correct order of attachments even across actions with multiple attachments. attachments.unshift({ + source, reportActionID: attribs['data-id'], - source: tryResolveUrlFromApiRoot(expensifySource || attribs.src), isAuthTokenRequired: Boolean(expensifySource), - file: {name: attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE]}, + file: {name: fileName}, isReceipt: false, hasBeenFlagged: attribs['data-flagged'] === 'true', }); diff --git a/src/components/Attachments/propTypes.js b/src/components/Attachments/propTypes.js index 698a41de9648..13adc468ce64 100644 --- a/src/components/Attachments/propTypes.js +++ b/src/components/Attachments/propTypes.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; const attachmentSourcePropType = PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.number]); const attachmentFilePropType = PropTypes.shape({ - name: PropTypes.string, + name: PropTypes.string.isRequired, }); const attachmentPropType = PropTypes.shape({ @@ -13,7 +13,7 @@ const attachmentPropType = PropTypes.shape({ source: attachmentSourcePropType.isRequired, /** File object can be an instance of File or Object */ - file: attachmentFilePropType, + file: attachmentFilePropType.isRequired, }); const attachmentsPropType = PropTypes.arrayOf(attachmentPropType); diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index 618571ddf400..09cc1222310f 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -1,6 +1,7 @@ import {Alert, Linking, Platform} from 'react-native'; import DateUtils from '@libs/DateUtils'; import * as Localize from '@libs/Localize'; +import Log from '@libs/Log'; import CONST from '@src/CONST'; import type {ReadFileAsync, SplitExtensionFromFileName} from './types'; @@ -75,13 +76,22 @@ function showCameraPermissionsAlert() { } /** - * Generate a random file name with timestamp and file extension + * 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. + * 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 getAttachmentName(url: string): string { - if (!url) { - return ''; +function getFileName(url: string): string { + const fileName = url.split(/[#?/]/).pop() ?? ''; + if (!fileName) { + Log.warn('[FileUtils] Could not get attachment name', {url}); } - return `${DateUtils.getDBTime()}.${url.split(/[#?]/)[0].split('.').pop()?.trim()}`; + + return decodeURIComponent(fileName).replace(CONST.REGEX.ILLEGAL_FILENAME_CHARACTERS, '_'); } function isImage(fileName: string): boolean { @@ -231,7 +241,7 @@ export { showPermissionErrorAlert, showCameraPermissionsAlert, splitExtensionFromFileName, - getAttachmentName, + getFileName, getFileType, cleanFileName, appendTimeToFileName, diff --git a/src/libs/fileDownload/index.android.ts b/src/libs/fileDownload/index.android.ts index 41c7cb29550a..8496c1cb6cf5 100644 --- a/src/libs/fileDownload/index.android.ts +++ b/src/libs/fileDownload/index.android.ts @@ -38,7 +38,7 @@ function handleDownload(url: string, fileName: string): Promise { // Android files will download to Download directory const path = dirs.DownloadDir; - const attachmentName = FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(url); + const attachmentName = FileUtils.appendTimeToFileName(fileName || FileUtils.getFileName(url)); const isLocalFile = url.startsWith('file://'); diff --git a/src/libs/fileDownload/index.ios.ts b/src/libs/fileDownload/index.ios.ts index fdc4a78e0b9b..7672b4b14926 100644 --- a/src/libs/fileDownload/index.ios.ts +++ b/src/libs/fileDownload/index.ios.ts @@ -73,7 +73,7 @@ const fileDownload: FileDownload = (fileUrl, fileName) => new Promise((resolve) => { let fileDownloadPromise; const fileType = FileUtils.getFileType(fileUrl); - const attachmentName = FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(fileUrl); + const attachmentName = FileUtils.appendTimeToFileName(fileName || FileUtils.getFileName(fileUrl)); switch (fileType) { case CONST.ATTACHMENT_FILE_TYPE.IMAGE: diff --git a/src/libs/fileDownload/index.ts b/src/libs/fileDownload/index.ts index ef36647e549d..4f43b215925f 100644 --- a/src/libs/fileDownload/index.ts +++ b/src/libs/fileDownload/index.ts @@ -29,10 +29,7 @@ const fileDownload: FileDownload = (url, fileName) => { // adding href to anchor link.href = href; link.style.display = 'none'; - link.setAttribute( - 'download', - FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(url), // generating the file name - ); + link.download = FileUtils.appendTimeToFileName(fileName || FileUtils.getFileName(url)); // Append to html link element page document.body.appendChild(link);