mirror of
https://github.com/tutao/tutanota.git
synced 2025-12-08 06:09:50 +00:00
[ios] use uploadNative for blob attachments
Problem: * the mailModel would decide which upload mthod to use according to the attachment type (DataFile -> normal, FileReference -> native) * getLogs returns DataFiles * blob access tokens reference https:// blobstore URLs * these shouldn't be used by the webview (only asset: and apis: URLs) Fix (has at least these possibilities): * rewrite blobstore URLs to apis: protocol before giving them to mail model. continue using the normal upload, the URL will be rewritten to https: on the native side again * write the data file to disk and upload the blob with the normal native method, which is allowed to use https: URLS. * add a method to blobFacade that allows natively uploading byte arrays * somehow get a FileRef from getLogs this commit uses the second method. it mirrors the way other attachments are uploaded in the app and doesn't complicate the IPC with additional params or methods. fix #4361
This commit is contained in:
parent
a86793e7e8
commit
6f4600f940
3 changed files with 68 additions and 60 deletions
|
|
@ -4,7 +4,7 @@ import {Indexer} from "./search/Indexer"
|
|||
import type {EntityRestInterface} from "./rest/EntityRestClient"
|
||||
import {EntityRestClient} from "./rest/EntityRestClient"
|
||||
import {UserManagementFacade} from "./facades/UserManagementFacade"
|
||||
import {DefaultEntityRestCache} from "./rest/DefaultEntityRestCache.js"
|
||||
import {CacheStorage, DefaultEntityRestCache} from "./rest/DefaultEntityRestCache.js"
|
||||
import {GroupManagementFacade} from "./facades/GroupManagementFacade"
|
||||
import {MailFacade} from "./facades/MailFacade"
|
||||
import {MailAddressFacade} from "./facades/MailAddressFacade"
|
||||
|
|
@ -42,7 +42,6 @@ import {IServiceExecutor} from "../common/ServiceRequest"
|
|||
import {ServiceExecutor} from "./rest/ServiceExecutor"
|
||||
import {BookingFacade} from "./facades/BookingFacade"
|
||||
import {BlobFacade} from "./facades/BlobFacade"
|
||||
import {CacheStorage} from "./rest/DefaultEntityRestCache.js"
|
||||
import {UserFacade} from "./facades/UserFacade"
|
||||
import {OfflineStorage} from "./offline/OfflineStorage.js"
|
||||
import {exposeNativeInterface} from "../common/ExposeNativeInterface"
|
||||
|
|
@ -194,7 +193,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData)
|
|||
const aesApp = new AesApp(new NativeCryptoFacadeSendDispatcher(worker), random)
|
||||
locator.blob = new BlobFacade(locator.user, locator.serviceExecutor, locator.restClient, suspensionHandler, fileApp, aesApp, locator.instanceMapper, locator.crypto)
|
||||
locator.file = new FileFacade(locator.user, locator.restClient, suspensionHandler, fileApp, aesApp, locator.instanceMapper, locator.serviceExecutor, locator.crypto)
|
||||
locator.mail = new MailFacade(locator.user, locator.file, locator.cachingEntityClient, locator.crypto, locator.serviceExecutor, locator.blob)
|
||||
locator.mail = new MailFacade(locator.user, locator.file, locator.cachingEntityClient, locator.crypto, locator.serviceExecutor, locator.blob, fileApp)
|
||||
const nativePushFacade = new NativePushFacadeSendDispatcher(worker)
|
||||
// not needed for admin client
|
||||
if (!isAdminClient()) {
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ import {
|
|||
createDeleteMailFolderData,
|
||||
createDraftAttachment,
|
||||
createDraftCreateData,
|
||||
createDraftData, createDraftRecipient,
|
||||
createDraftUpdateData, createEncryptedMailAddress,
|
||||
createDraftData,
|
||||
createDraftRecipient,
|
||||
createDraftUpdateData,
|
||||
createEncryptedMailAddress,
|
||||
createExternalUserData,
|
||||
createListUnsubscribeData,
|
||||
createMoveMailData,
|
||||
|
|
@ -60,7 +62,8 @@ import {NotFoundError} from "../../common/error/RestError"
|
|||
import type {EntityUpdate, PublicKeyReturn, User} from "../../entities/sys/TypeRefs.js"
|
||||
import {
|
||||
BlobReferenceTokenWrapper,
|
||||
createPublicKeyData, CustomerTypeRef,
|
||||
createPublicKeyData,
|
||||
CustomerTypeRef,
|
||||
ExternalUserReferenceTypeRef,
|
||||
GroupInfoTypeRef,
|
||||
GroupRootTypeRef,
|
||||
|
|
@ -73,7 +76,6 @@ import {
|
|||
byteLength,
|
||||
contains,
|
||||
defer,
|
||||
downcast,
|
||||
isNotNull,
|
||||
isSameTypeRefByAttr,
|
||||
neverNull,
|
||||
|
|
@ -84,7 +86,7 @@ import {
|
|||
} from "@tutao/tutanota-utils"
|
||||
import {BlobFacade} from "./BlobFacade"
|
||||
import {FileFacade} from "./FileFacade"
|
||||
import {assertWorkerOrNode, isApp} from "../../common/Env"
|
||||
import {assertWorkerOrNode, isApp, isDesktop} from "../../common/Env"
|
||||
import {EntityClient} from "../../common/EntityClient"
|
||||
import {getEnabledMailAddressesForGroupInfo, getUserGroupMemberships} from "../../common/utils/GroupUtils"
|
||||
import {containsId, getLetId, isSameId, stringToCustomId} from "../../common/utils/EntityUtils"
|
||||
|
|
@ -106,13 +108,14 @@ import {
|
|||
sha256Hash,
|
||||
} from "@tutao/tutanota-crypto"
|
||||
import {DataFile} from "../../common/DataFile";
|
||||
import {FileReference} from "../../common/utils/FileUtils";
|
||||
import {FileReference, isDataFile, isFileReference} from "../../common/utils/FileUtils";
|
||||
import {CounterService} from "../../entities/monitor/Services"
|
||||
import {PublicKeyService} from "../../entities/sys/Services.js"
|
||||
import {IServiceExecutor} from "../../common/ServiceRequest"
|
||||
import {createWriteCounterData} from "../../entities/monitor/TypeRefs"
|
||||
import {UserFacade} from "./UserFacade"
|
||||
import {PartialRecipient, Recipient, RecipientList, RecipientType} from "../../common/recipients/Recipient"
|
||||
import {NativeFileApp} from "../../../native/common/FileApp"
|
||||
|
||||
assertWorkerOrNode()
|
||||
type Attachments = ReadonlyArray<TutanotaFile | DataFile | FileReference>
|
||||
|
|
@ -159,7 +162,9 @@ export class MailFacade {
|
|||
private readonly crypto: CryptoFacade,
|
||||
private readonly serviceExecutor: IServiceExecutor,
|
||||
private readonly blobFacade: BlobFacade,
|
||||
) {}
|
||||
private readonly fileApp: NativeFileApp,
|
||||
) {
|
||||
}
|
||||
|
||||
async createMailFolder(name: string, parent: IdTuple, ownerGroupId: Id): Promise<void> {
|
||||
const mailGroupKey = this.userFacade.getGroupKey(ownerGroupId)
|
||||
|
|
@ -348,57 +353,58 @@ export class MailFacade {
|
|||
mailGroupKey: Aes128Key,
|
||||
useBlobs: boolean
|
||||
): Promise<DraftAttachment[]> {
|
||||
if (providedFiles) {
|
||||
return promiseMap(providedFiles, async (providedFile) => {
|
||||
// check if this is a new attachment or an existing one
|
||||
if (providedFile._type === "DataFile") {
|
||||
// user added attachment
|
||||
const fileSessionKey = aes128RandomKey()
|
||||
const dataFile = downcast<DataFile>(providedFile)
|
||||
if (useBlobs) {
|
||||
// return
|
||||
const referenceTokens = await this.blobFacade.encryptAndUpload(ArchiveDataType.Attachments, dataFile.data, senderMailGroupId, fileSessionKey)
|
||||
return this.createAndEncryptDraftAttachment(referenceTokens, fileSessionKey, dataFile, mailGroupKey)
|
||||
} else {
|
||||
return this.fileFacade.uploadFileData(dataFile, fileSessionKey).then(fileDataId => {
|
||||
return this.createAndEncryptLegacyDraftAttachment(fileDataId, fileSessionKey, dataFile, mailGroupKey)
|
||||
})
|
||||
}
|
||||
} else if (providedFile._type === "FileReference") {
|
||||
const fileSessionKey = aes128RandomKey()
|
||||
const fileRef = downcast<FileReference>(providedFile)
|
||||
if (useBlobs) {
|
||||
const referenceTokens = await this.blobFacade.encryptAndUploadNative(ArchiveDataType.Attachments, fileRef.location, senderMailGroupId, fileSessionKey)
|
||||
return this.createAndEncryptDraftAttachment(referenceTokens, fileSessionKey, fileRef, mailGroupKey)
|
||||
} else {
|
||||
return this.fileFacade.uploadFileDataNative(fileRef, fileSessionKey).then(fileDataId => {
|
||||
return this.createAndEncryptLegacyDraftAttachment(fileDataId, fileSessionKey, fileRef, mailGroupKey)
|
||||
})
|
||||
}
|
||||
} else if (!containsId(existingFileIds, getLetId(providedFile))) {
|
||||
// forwarded attachment which was not in the draft before
|
||||
return this.crypto.resolveSessionKeyForInstance(providedFile).then(fileSessionKey => {
|
||||
const attachment = createDraftAttachment()
|
||||
attachment.existingFile = getLetId(providedFile)
|
||||
attachment.ownerEncFileSessionKey = encryptKey(mailGroupKey, neverNull(fileSessionKey))
|
||||
return attachment
|
||||
})
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}) // disable concurrent file upload to avoid timeout because of missing progress events on Firefox.
|
||||
.then(attachments => attachments.filter(isNotNull))
|
||||
.then(it => {
|
||||
// only delete the temporary files after all attachments have been uploaded
|
||||
if (isApp()) {
|
||||
this.fileFacade.clearFileData().catch(e => console.warn("Failed to clear files", e))
|
||||
}
|
||||
if (providedFiles == null || providedFiles.length === 0) return []
|
||||
|
||||
return it
|
||||
return promiseMap(providedFiles, async (providedFile) => {
|
||||
// check if this is a new attachment or an existing one
|
||||
if (isDataFile(providedFile)) {
|
||||
// user added attachment
|
||||
const fileSessionKey = aes128RandomKey()
|
||||
if (useBlobs) {
|
||||
let referenceTokens: Array<BlobReferenceTokenWrapper>
|
||||
if (isApp() || isDesktop()) {
|
||||
const {location} = await this.fileApp.writeDataFile(providedFile)
|
||||
referenceTokens = await this.blobFacade.encryptAndUploadNative(ArchiveDataType.Attachments, location, senderMailGroupId, fileSessionKey)
|
||||
await this.fileApp.deleteFile(location)
|
||||
} else {
|
||||
referenceTokens = await this.blobFacade.encryptAndUpload(ArchiveDataType.Attachments, providedFile.data, senderMailGroupId, fileSessionKey)
|
||||
}
|
||||
return this.createAndEncryptDraftAttachment(referenceTokens, fileSessionKey, providedFile, mailGroupKey)
|
||||
} else {
|
||||
const fileDataId = await this.fileFacade.uploadFileData(providedFile, fileSessionKey)
|
||||
return this.createAndEncryptLegacyDraftAttachment(fileDataId, fileSessionKey, providedFile, mailGroupKey)
|
||||
}
|
||||
} else if (isFileReference(providedFile)) {
|
||||
const fileSessionKey = aes128RandomKey()
|
||||
if (useBlobs) {
|
||||
const referenceTokens = await this.blobFacade.encryptAndUploadNative(ArchiveDataType.Attachments, providedFile.location, senderMailGroupId, fileSessionKey)
|
||||
return this.createAndEncryptDraftAttachment(referenceTokens, fileSessionKey, providedFile, mailGroupKey)
|
||||
} else {
|
||||
const fileDataId = await this.fileFacade.uploadFileDataNative(providedFile, fileSessionKey)
|
||||
return this.createAndEncryptLegacyDraftAttachment(fileDataId, fileSessionKey, providedFile, mailGroupKey)
|
||||
}
|
||||
} else if (!containsId(existingFileIds, getLetId(providedFile))) {
|
||||
// forwarded attachment which was not in the draft before
|
||||
return this.crypto.resolveSessionKeyForInstance(providedFile).then(fileSessionKey => {
|
||||
const attachment = createDraftAttachment()
|
||||
attachment.existingFile = getLetId(providedFile)
|
||||
attachment.ownerEncFileSessionKey = encryptKey(mailGroupKey, neverNull(fileSessionKey))
|
||||
return attachment
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}) // disable concurrent file upload to avoid timeout because of missing progress events on Firefox.
|
||||
.then(attachments => attachments.filter(isNotNull))
|
||||
.then(it => {
|
||||
// only delete the temporary files after all attachments have been uploaded
|
||||
if (isApp()) {
|
||||
this.fileFacade.clearFileData().catch(e => console.warn("Failed to clear files", e))
|
||||
}
|
||||
|
||||
return it
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
createAndEncryptLegacyDraftAttachment(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {FileFacade} from "../../../../../src/api/worker/facades/FileFacade.js"
|
|||
import {EntityClient} from "../../../../../src/api/common/EntityClient.js"
|
||||
import {BlobFacade} from "../../../../../src/api/worker/facades/BlobFacade.js"
|
||||
import {UserFacade} from "../../../../../src/api/worker/facades/UserFacade"
|
||||
import {NativeFileApp} from "../../../../../src/native/common/FileApp.js"
|
||||
|
||||
|
||||
o.spec("MailFacade test", function () {
|
||||
|
|
@ -19,6 +20,7 @@ o.spec("MailFacade test", function () {
|
|||
let fileFacade: FileFacade
|
||||
let entity: EntityClient
|
||||
let blobFacade: BlobFacade
|
||||
let fileApp: NativeFileApp
|
||||
|
||||
o.beforeEach(function () {
|
||||
userFacade = object()
|
||||
|
|
@ -27,7 +29,8 @@ o.spec("MailFacade test", function () {
|
|||
entity = object()
|
||||
cryptoFacade = object()
|
||||
serviceExecutor = object()
|
||||
facade = new MailFacade(userFacade, fileFacade, entity, cryptoFacade, serviceExecutor, blobFacade)
|
||||
fileApp = object()
|
||||
facade = new MailFacade(userFacade, fileFacade, entity, cryptoFacade, serviceExecutor, blobFacade, fileApp)
|
||||
})
|
||||
|
||||
o.spec("checkMailForPhishing", function () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue