diff --git a/packages/loot-core/src/server/cloud-storage.js b/packages/loot-core/src/server/cloud-storage.js index fd5a9a5cdb11acaa88ef33de80ae4a284a0ce2ab..d37d97530243a470095869244abd23642cd91f3f 100644 --- a/packages/loot-core/src/server/cloud-storage.js +++ b/packages/loot-core/src/server/cloud-storage.js @@ -161,8 +161,13 @@ export async function exportBuffer() { } export async function importBuffer(fileData, buffer) { - let zipped = new AdmZip(buffer); - let entries = zipped.getEntries(); + let zipped, entries; + try { + zipped = new AdmZip(buffer); + entries = zipped.getEntries(); + } catch (err) { + throw FileDownloadError('not-zip-file'); + } let dbEntry = entries.find(e => e.entryName.includes('db.sqlite')); let metaEntry = entries.find(e => e.entryName.includes('metadata.json')); diff --git a/packages/loot-core/src/server/main.js b/packages/loot-core/src/server/main.js index d81348c105ba442101a19a84b17ba8cd7518bbfd..eed06087f194358cc25b9124a41e3a694386894a 100644 --- a/packages/loot-core/src/server/main.js +++ b/packages/loot-core/src/server/main.js @@ -2108,10 +2108,18 @@ handlers['import-budget'] = async function ({ filepath, type }) { // duplicate some of the workflow await handlers['close-budget'](); - let { id } = await cloudStorage.importBuffer( - { cloudFileId: null, groupId: null }, - buffer, - ); + let id; + try { + ({ id } = await cloudStorage.importBuffer( + { cloudFileId: null, groupId: null }, + buffer, + )); + } catch (e) { + if (e.type === 'FileDownloadError') { + return { error: e.reason }; + } + throw e; + } // We never want to load cached data from imported files, so // delete the cache diff --git a/packages/loot-core/src/shared/errors.js b/packages/loot-core/src/shared/errors.js index 3b20c05198905f360aa2478a2584ca4cf269b3b1..fc0a8cd1952f729b25f2c94b9065d3f2a814c8d8 100644 --- a/packages/loot-core/src/shared/errors.js +++ b/packages/loot-core/src/shared/errors.js @@ -28,6 +28,7 @@ export function getDownloadError({ reason, meta, fileName }) { case 'network': case 'download-failure': return 'Downloading the file failed. Check your network connection.'; + case 'not-zip-file': case 'invalid-zip-file': case 'invalid-meta-file': return 'Downloaded file is invalid, sorry! Contact help@actualbudget.com for support.'; diff --git a/packages/loot-design/src/components/manager/ImportActual.js b/packages/loot-design/src/components/manager/ImportActual.js index f541b061f9745b83c0be7ba1e061eb07a12dc123..50cb6da77282a5e917616eaa3b9609f5defd6933 100644 --- a/packages/loot-design/src/components/manager/ImportActual.js +++ b/packages/loot-design/src/components/manager/ImportActual.js @@ -12,6 +12,12 @@ function getErrorMessage(error) { return 'Unable to parse file. Please select a JSON file exported from nYNAB.'; case 'not-ynab5': return 'This file is not valid. Please select a JSON file exported from nYNAB.'; + case 'not-zip-file': + return 'This file is not valid. Please select an unencrypted archive of Actual data.'; + case 'invalid-zip-file': + return 'This archive is not a valid Actual export file.'; + case 'invalid-metadata-file': + return 'The metadata file in the given archive is corrupted.'; default: return 'An unknown error occurred while importing. Sorry! We have been notified of this issue.'; } @@ -25,7 +31,7 @@ function Import({ modalProps, availableImports }) { async function onImport() { const res = await window.Actual.openFileDialog({ properties: ['openFile'], - filters: [{ name: 'actual', extensions: ['zip'] }], + filters: [{ name: 'actual', extensions: ['zip', 'blob'] }], }); if (res) { setImporting(true); diff --git a/upcoming-release-notes/785.md b/upcoming-release-notes/785.md new file mode 100644 index 0000000000000000000000000000000000000000..534b88ee09126b8cd4a54c16e63f96a673b93196 --- /dev/null +++ b/upcoming-release-notes/785.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [Jackenmen] +--- + +Allow importing `.blob` files from actual-server