diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 35638a0b604e..ef4d7e3e4064 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -237,7 +237,9 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const validateAndCompleteAttachmentSelection = useCallback( (fileData: FileResponse) => { - if (fileData.width === -1 || fileData.height === -1) { + // Check if the file dimensions indicate corruption + // The width/height for corrupt file is -1 on android native and 0 on ios native + if (!fileData.width || !fileData.height || (fileData.width <= 0 && fileData.height <= 0)) { showImageCorruptionAlert(); return Promise.resolve(); } @@ -283,16 +285,18 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }; /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ if (fileDataName && Str.isImage(fileDataName)) { - ImageSize.getSize(fileDataUri).then(({width, height}) => { - fileDataObject.width = width; - fileDataObject.height = height; - validateAndCompleteAttachmentSelection(fileDataObject); - }); + ImageSize.getSize(fileDataUri) + .then(({width, height}) => { + fileDataObject.width = width; + fileDataObject.height = height; + validateAndCompleteAttachmentSelection(fileDataObject); + }) + .catch(() => showImageCorruptionAlert()); } else { return validateAndCompleteAttachmentSelection(fileDataObject); } }, - [validateAndCompleteAttachmentSelection], + [validateAndCompleteAttachmentSelection, showImageCorruptionAlert], ); /** diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index 06bd47f3b39b..d9893d93d2f6 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -1,4 +1,7 @@ +import Str from 'expensify-common/lib/str'; import {Alert, Linking, Platform} from 'react-native'; +import ImageSize from 'react-native-image-size'; +import type {FileObject} from '@components/AttachmentModal'; import DateUtils from '@libs/DateUtils'; import * as Localize from '@libs/Localize'; import Log from '@libs/Log'; @@ -238,6 +241,17 @@ function base64ToFile(base64: string, filename: string): File { return file; } +function validateImageForCorruption(file: FileObject): Promise { + if (!Str.isImage(file.name ?? '')) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + ImageSize.getSize(file.uri ?? '') + .then(() => resolve()) + .catch(() => reject(new Error('Error reading file: The file is corrupted'))); + }); +} + export { showGeneralErrorAlert, showSuccessAlert, @@ -250,4 +264,5 @@ export { appendTimeToFileName, readFileAsync, base64ToFile, + validateImageForCorruption, }; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index b9c4f866d493..5339b7fc14ad 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -175,25 +175,34 @@ function IOURequestStepScan({ }; function validateReceipt(file: FileObject) { - const {fileExtension} = FileUtils.splitExtensionFromFileName(file?.name ?? ''); - if ( - !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase() as (typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS)[number]) - ) { - setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); - return false; - } + return FileUtils.validateImageForCorruption(file) + .then(() => { + const {fileExtension} = FileUtils.splitExtensionFromFileName(file?.name ?? ''); + if ( + !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes( + fileExtension.toLowerCase() as (typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS)[number], + ) + ) { + setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); + return false; + } - if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded'); - return false; - } + if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded'); + return false; + } - if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); - return false; - } + if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); + return false; + } - return true; + return true; + }) + .catch(() => { + setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedImage'); + return false; + }); } const navigateBack = () => { @@ -230,21 +239,22 @@ function IOURequestStepScan({ * Sets the Receipt objects and navigates the user to the next page */ const setReceiptAndNavigate = (file: FileObject) => { - if (!validateReceipt(file)) { - return; - } - - // Store the receipt on the transaction object in Onyx - const source = URL.createObjectURL(file as Blob); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - IOU.setMoneyRequestReceipt(transactionID, source, file.name || '', action !== CONST.IOU.ACTION.EDIT); + validateReceipt(file).then((isFileValid) => { + if (!isFileValid) { + return; + } + // Store the receipt on the transaction object in Onyx + const source = URL.createObjectURL(file as Blob); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + IOU.setMoneyRequestReceipt(transactionID, source, file.name || '', action !== CONST.IOU.ACTION.EDIT); - if (action === CONST.IOU.ACTION.EDIT) { - updateScanAndNavigate(file, source); - return; - } + if (action === CONST.IOU.ACTION.EDIT) { + updateScanAndNavigate(file, source); + return; + } - navigateToConfirmationStep(); + navigateToConfirmationStep(); + }); }; const setupCameraPermissionsAndCapabilities = (stream: MediaStream) => {