From c7a2515d506d874cc4e9ea0bef22d75352dbcb6b Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 3 Jan 2022 22:29:39 +0200 Subject: [PATCH 01/17] #1254 add pwd param to composeEmail --- Core/source/mobile-interface/endpoints.ts | 2 +- Core/source/mobile-interface/validate-input.ts | 4 ++-- FlowCrypt.xcodeproj/project.pbxproj | 16 ++++++++-------- FlowCrypt/Core/CoreTypes.swift | 2 +- .../EnterpriseServerApi.swift | 2 +- ...Exntensions.swift => CodableExtensions.swift} | 7 ++++++- ...wift => DataExtensions+ZBase32Encoding.swift} | 2 +- 7 files changed, 20 insertions(+), 15 deletions(-) rename FlowCryptCommon/Extensions/{CodableExntensions.swift => CodableExtensions.swift} (70%) rename FlowCryptCommon/Extensions/Data/{DataExntensions+ZBase32Encoding.swift => DataExtensions+ZBase32Encoding.swift} (99%) diff --git a/Core/source/mobile-interface/endpoints.ts b/Core/source/mobile-interface/endpoints.ts index 3cd2dfcb0..e5c7813c2 100644 --- a/Core/source/mobile-interface/endpoints.ts +++ b/Core/source/mobile-interface/endpoints.ts @@ -65,7 +65,7 @@ export class Endpoints { encryptedAtts.push(new Att({ name: `${att.name}.pgp`, type: 'application/pgp-encrypted', data: encryptedAtt.message.packets.write() })) } const signingPrv = await getSigningPrv(req); - const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, signingPrv, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; + const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, signingPrv, pwd: req.pwd, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': encrypted.data }, mimeHeaders, encryptedAtts))); } else { throw new Error(`Unknown format: ${req.format}`); diff --git a/Core/source/mobile-interface/validate-input.ts b/Core/source/mobile-interface/validate-input.ts index 4e2b27545..f92912da2 100644 --- a/Core/source/mobile-interface/validate-input.ts +++ b/Core/source/mobile-interface/validate-input.ts @@ -11,7 +11,7 @@ export namespace NodeRequest { type Attachment = { name: string; type: string; base64: string }; interface composeEmailBase { text: string, to: string[], cc: string[], bcc: string[], from: string, subject: string, replyToMimeMsg: string, atts?: Attachment[] }; export interface composeEmailPlain extends composeEmailBase { format: 'plain' }; - export interface composeEmailEncrypted extends composeEmailBase { format: 'encrypt-inline' | 'encrypt-pgpmime', pubKeys: string[], signingPrv: PrvKeyInfo | undefined }; + export interface composeEmailEncrypted extends composeEmailBase { format: 'encrypt-inline' | 'encrypt-pgpmime', pubKeys: string[], signingPrv: PrvKeyInfo | undefined, pwd?: string }; export type generateKey = { passphrase: string, variant: 'rsa2048' | 'rsa4096' | 'curve25519', userIds: { name: string, email: string }[] }; export type composeEmail = composeEmailPlain | composeEmailEncrypted; @@ -50,7 +50,7 @@ export class ValidateInput { if (!hasProp(v, 'atts', 'Attachment[]?')) { throw new Error('Wrong atts structure for NodeRequest.composeEmail, need: {name, type, base64}'); } - if (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?') && v.pubKeys.length && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { + if ((hasProp(v, 'pwd', 'string' || (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?')) && v.pubKeys.length)) && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { return v as NodeRequest.composeEmailEncrypted; } if (!v.pubKeys && v.format === 'plain') { diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 799a2ca97..7a763f5d8 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -29,7 +29,7 @@ 21C7DF09266C0D8F00C44800 /* EnterpriseServerApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7DF08266C0D8F00C44800 /* EnterpriseServerApi.swift */; }; 21CE25E62650070300ADFF4B /* WkdUrlConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CE25E52650070300ADFF4B /* WkdUrlConstructor.swift */; }; 21EA3B592656611D00691848 /* ClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21EA3B15265647C400691848 /* ClientConfiguration.swift */; }; - 21F836B62652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */; }; + 21F836B62652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836B52652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift */; }; 2C03CC16275BB53400887EEB /* EnterpriseServerApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C03CC15275BB53400887EEB /* EnterpriseServerApiTests.swift */; }; 2C08F6BE273FA7B900EE1610 /* Version5SchemaMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C08F6BD273FA7B900EE1610 /* Version5SchemaMigration.swift */; }; 2C124DB42728809100A2EFA6 /* ApiCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C124DB32728809100A2EFA6 /* ApiCall.swift */; }; @@ -341,7 +341,7 @@ D2CDC3CD2402CCD7002B045F /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8277952373732000E19C07 /* UIImageExtensions.swift */; }; D2CDC3CE2402CDB4002B045F /* DispatchTimeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCABF1508B0C08DEDE2059 /* DispatchTimeExtensions.swift */; }; D2CDC3D32402D4FE002B045F /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */; }; - D2CDC3D42402D50A002B045F /* CodableExntensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA38E87F2B7196E0E1F1F /* CodableExntensions.swift */; }; + D2CDC3D42402D50A002B045F /* CodableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA38E87F2B7196E0E1F1F /* CodableExtensions.swift */; }; D2CDC3D72404704D002B045F /* RecipientEmailsCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24ABA6223FDB4FF002EE9DD /* RecipientEmailsCellNode.swift */; }; D2CDC3D824047066002B045F /* RecipientEmailNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2531F3523FFEDA2007E5198 /* RecipientEmailNode.swift */; }; D2CDC3DD24052D50002B045F /* FlowCryptUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D204DB9E23FB35700083B9D6 /* FlowCryptUI.framework */; }; @@ -444,7 +444,7 @@ 21EA3B2E26565B7400691848 /* client_configuraion.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = client_configuraion.json; sourceTree = ""; }; 21EA3B3526565B8100691848 /* client_configuraion_empty.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = client_configuraion_empty.json; sourceTree = ""; }; 21EA3B3C26565B9800691848 /* client_configuraion_partly_empty.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = client_configuraion_partly_empty.json; sourceTree = ""; }; - 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataExntensions+ZBase32Encoding.swift"; sourceTree = ""; }; + 21F836B52652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataExtensions+ZBase32Encoding.swift"; sourceTree = ""; }; 21F836CB2652A38700B2448C /* ZBase32EncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZBase32EncodingTests.swift; sourceTree = ""; }; 21F836D22652A46E00B2448C /* WKDURLsConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKDURLsConstructorTests.swift; sourceTree = ""; }; 2C03CC15275BB53400887EEB /* EnterpriseServerApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterpriseServerApiTests.swift; sourceTree = ""; }; @@ -466,7 +466,7 @@ 32DCA0C3D34A69851A238E87 /* Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; }; 32DCA0E63F2F0473D0A8EDB0 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 32DCA377D22F4D67A8FA05EB /* Imap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Imap.swift; sourceTree = ""; }; - 32DCA38E87F2B7196E0E1F1F /* CodableExntensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableExntensions.swift; sourceTree = ""; }; + 32DCA38E87F2B7196E0E1F1F /* CodableExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableExtensions.swift; sourceTree = ""; }; 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppErr.swift; sourceTree = ""; }; 32DCA55C094E9745AA1FD210 /* Imap+msg.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Imap+msg.swift"; sourceTree = ""; }; 32DCA63656CB3323C26BC084 /* UIVIewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIVIewExtensions.swift; sourceTree = ""; }; @@ -949,7 +949,7 @@ isa = PBXGroup; children = ( 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */, - 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */, + 21F836B52652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift */, ); path = Data; sourceTree = ""; @@ -1871,7 +1871,7 @@ D2D27B78248A8694007346FA /* BigIntExtensions.swift */, E26D5E20275AA417007B8802 /* BundleExtensions.swift */, 21C7DEFB26669A3700C44800 /* CalendarExtensions.swift */, - 32DCA38E87F2B7196E0E1F1F /* CodableExntensions.swift */, + 32DCA38E87F2B7196E0E1F1F /* CodableExtensions.swift */, D2531F452402C62D007E5198 /* CollectionExtensions.swift */, E26D5E1E275AA295007B8802 /* CommandLineExtensions.swift */, 9F0C3C2723194E8500299985 /* CommonExtensions.swift */, @@ -2833,7 +2833,7 @@ 9F67998D277B3E4D00AFE5BE /* CommandLineExtensions.swift in Sources */, 9F67998C277B3E4000AFE5BE /* BundleExtensions.swift in Sources */, D2CDC3CD2402CCD7002B045F /* UIImageExtensions.swift in Sources */, - 21F836B62652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift in Sources */, + 21F836B62652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift in Sources */, D29AFFF6240939AE00C1387D /* Then.swift in Sources */, 9FBD69EE27775086002FC602 /* ErrorExtensions.swift in Sources */, 9F6F5F6C26A2F66B00C625C7 /* Logger.swift in Sources */, @@ -2849,7 +2849,7 @@ 21C7DEFE26669CE100C44800 /* DateFormattingExtensions.swift in Sources */, 9F8076D927762515008E5874 /* BigIntExtensions.swift in Sources */, 9FBD69EC27775086002FC602 /* UIApplicationExtensions.swift in Sources */, - D2CDC3D42402D50A002B045F /* CodableExntensions.swift in Sources */, + D2CDC3D42402D50A002B045F /* CodableExtensions.swift in Sources */, D2531F3D24000E37007E5198 /* UIVIewExtensions.swift in Sources */, D2531F3723FFF043007E5198 /* CommonExtensions.swift in Sources */, D2CDC3D32402D4FE002B045F /* DataExtensions.swift in Sources */, diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index ee61b0bfa..c5e405b04 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -119,7 +119,7 @@ struct UserId: Encodable { } struct SendableMsg: Equatable { - struct Attachment: Equatable { + struct Attachment: Equatable, Encodable { let name: String let type: String let base64: String diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 8a510a62e..1a820c359 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -185,10 +185,10 @@ class EnterpriseServerApi: EnterpriseServerApiType { throw EnterpriseServerApiError.noActiveFesUrl } + var headers = headers if withAuthorization { let idToken = try await getIdToken(email: email) let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") - var headers = headers headers.append(authorizationHeader) } diff --git a/FlowCryptCommon/Extensions/CodableExntensions.swift b/FlowCryptCommon/Extensions/CodableExtensions.swift similarity index 70% rename from FlowCryptCommon/Extensions/CodableExntensions.swift rename to FlowCryptCommon/Extensions/CodableExtensions.swift index e96982b85..ea65b6447 100644 --- a/FlowCryptCommon/Extensions/CodableExntensions.swift +++ b/FlowCryptCommon/Extensions/CodableExtensions.swift @@ -10,10 +10,15 @@ public extension Encodable { } func toJsonEncodedDict() throws -> [String: Any] { - let data = try JSONEncoder().encode(self) + let data = try self.toJsonData() guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else { throw NSError() } return dictionary } + + func toJsonString() throws -> String? { + let data = try self.toJsonData() + return String(data: data, encoding: .utf8) + } } diff --git a/FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift b/FlowCryptCommon/Extensions/Data/DataExtensions+ZBase32Encoding.swift similarity index 99% rename from FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift rename to FlowCryptCommon/Extensions/Data/DataExtensions+ZBase32Encoding.swift index 46eb0ce16..da51e4b74 100644 --- a/FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift +++ b/FlowCryptCommon/Extensions/Data/DataExtensions+ZBase32Encoding.swift @@ -1,5 +1,5 @@ // -// DataExntensions+Encoding.swift +// DataExtensions+Encoding.swift // FlowCryptCommon // // Created by Yevhen Kyivskyi on 17.05.2021. From ebe6569afd5f8d456acfeb53beb3a3a19e069f64 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 5 Jan 2022 16:01:29 +0200 Subject: [PATCH 02/17] #1254 add encryptMsgWithPwd --- Core/source/mobile-interface/endpoints.ts | 11 +++++++++- .../source/mobile-interface/validate-input.ts | 13 ++++++++++-- .../Compose/ComposeViewController.swift | 4 +++- FlowCrypt/Core/Core.swift | 21 +++++++++++++++++-- FlowCrypt/Core/CoreTypes.swift | 1 + .../EnterpriseServerApi.swift | 1 + .../Models/MessageUploadDetails.swift | 2 +- .../Backup Services/BackupService.swift | 3 ++- .../Functionality/Services/GlobalRouter.swift | 2 +- FlowCrypt/Resources/flowcrypt-ios-prod.js.txt | 15 ++++++++++++- .../Extensions/CodableExtensions.swift | 5 ----- 11 files changed, 63 insertions(+), 15 deletions(-) diff --git a/Core/source/mobile-interface/endpoints.ts b/Core/source/mobile-interface/endpoints.ts index e5c7813c2..eed08cde8 100644 --- a/Core/source/mobile-interface/endpoints.ts +++ b/Core/source/mobile-interface/endpoints.ts @@ -36,6 +36,14 @@ export class Endpoints { return fmtRes({}, Buf.fromUtfStr(encrypted.data)); } + public encryptMsgWithPwd = async (uncheckedReq: any): Promise => { + const req = ValidateInput.encryptMsgWithPwd(uncheckedReq); + const mimeHeaders: RichHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc }; + const mime = await Mime.encode({ 'text/plain': req.text }, mimeHeaders); + const encrypted = await PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: Buf.fromUtfStr(mime), armor: true }) as OpenPGP.EncryptArmorResult; + return fmtRes({}, Buf.fromUtfStr(encrypted.data)); + } + public generateKey = async (uncheckedReq: any): Promise => { Store.keyCacheWipe(); // generateKey may be used when changing major settings, wipe cache to prevent dated results const { passphrase, userIds, variant } = ValidateInput.generateKey(uncheckedReq); @@ -64,8 +72,9 @@ export class Endpoints { const encryptedAtt = await PgpMsg.encrypt({ pubkeys: req.pubKeys, data: Buf.fromBase64Str(att.base64), filename: att.name, armor: false }) as OpenPGP.EncryptBinaryResult; encryptedAtts.push(new Att({ name: `${att.name}.pgp`, type: 'application/pgp-encrypted', data: encryptedAtt.message.packets.write() })) } + const signingPrv = await getSigningPrv(req); - const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, signingPrv, pwd: req.pwd, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; + const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, signingPrv, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': encrypted.data }, mimeHeaders, encryptedAtts))); } else { throw new Error(`Unknown format: ${req.format}`); diff --git a/Core/source/mobile-interface/validate-input.ts b/Core/source/mobile-interface/validate-input.ts index f92912da2..33606105c 100644 --- a/Core/source/mobile-interface/validate-input.ts +++ b/Core/source/mobile-interface/validate-input.ts @@ -11,11 +11,12 @@ export namespace NodeRequest { type Attachment = { name: string; type: string; base64: string }; interface composeEmailBase { text: string, to: string[], cc: string[], bcc: string[], from: string, subject: string, replyToMimeMsg: string, atts?: Attachment[] }; export interface composeEmailPlain extends composeEmailBase { format: 'plain' }; - export interface composeEmailEncrypted extends composeEmailBase { format: 'encrypt-inline' | 'encrypt-pgpmime', pubKeys: string[], signingPrv: PrvKeyInfo | undefined, pwd?: string }; + export interface composeEmailEncrypted extends composeEmailBase { format: 'encrypt-inline' | 'encrypt-pgpmime', pubKeys: string[], signingPrv: PrvKeyInfo | undefined }; export type generateKey = { passphrase: string, variant: 'rsa2048' | 'rsa4096' | 'curve25519', userIds: { name: string, email: string }[] }; export type composeEmail = composeEmailPlain | composeEmailEncrypted; export type encryptMsg = { pubKeys: string[] }; + export type encryptMsgWithPwd = { msgPwd: string, text: string, to: string[], cc: string[], bcc: string[], from: string, subject: string, replyToMimeMsg: string, atts?: Attachment[] }; export type encryptFile = { pubKeys: string[], name: string }; export type parseDecryptMsg = { keys: PrvKeyInfo[], msgPwd?: string, isEmail?: boolean, verificationPubkeys?: string[] }; export type decryptFile = { keys: PrvKeyInfo[], msgPwd?: string }; @@ -43,6 +44,14 @@ export class ValidateInput { throw new Error('Wrong request structure for NodeRequest.encryptMsg'); } + public static encryptMsgWithPwd = (v: any): NodeRequest.encryptMsgWithPwd => { + if ((isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]') && hasProp(v, 'msgPwd', 'string'))) { + return v as NodeRequest.encryptMsgWithPwd; + } + throw new Error('Wrong request structure for NodeRequest.encryptMsgWithPwd'); + } + + public static composeEmail = (v: any): NodeRequest.composeEmail => { if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { throw new Error('Wrong request structure for NodeRequest.composeEmail, need: text,from,subject,to,cc,bcc,atts (can use empty arr for cc/bcc, and can skip atts)'); @@ -50,7 +59,7 @@ export class ValidateInput { if (!hasProp(v, 'atts', 'Attachment[]?')) { throw new Error('Wrong atts structure for NodeRequest.composeEmail, need: {name, type, base64}'); } - if ((hasProp(v, 'pwd', 'string' || (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?')) && v.pubKeys.length)) && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { + if (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?') && v.pubKeys.length && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { return v as NodeRequest.composeEmailEncrypted; } if (!v.pubKeys && v.format === 'plain') { diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index e6aa0ba6b..201444d83 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -497,11 +497,13 @@ extension ComposeViewController { // TODO: - fix for spinner // https://github.com/FlowCrypt/flowcrypt-ios/issues/291 try await Task.sleep(nanoseconds: 100 * 1_000_000) // 100ms + let sendableMsg = try await self.composeMessageService.validateAndProduceSendableMsg( input: self.input, contextToSend: self.contextToSend, email: self.email, - signingPrv: signingKey + signingPrv: signingKey, + isMessagePasswordSupported: isMessagePasswordSupported ) UIApplication.shared.isIdleTimerDisabled = true try await service.encryptAndSend( diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 14d91ce96..247bf811a 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -122,6 +122,23 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { return CoreRes.EncryptFile(encryptedFile: encrypted.data) } + // MARK: - Messages + public func encryptMsg(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail { + let r = try await call("encryptMsgWithPwd", jsonDict: [ + "text": msg.text, + "to": msg.to, + "cc": msg.cc, + "bcc": msg.bcc, + "from": msg.from, + "subject": msg.subject, + "replyToMimeMsg": msg.replyToMimeMsg, + "atts": msg.atts.map { att in ["name": att.name, "type": att.type, "base64": att.base64] }, + "format": fmt.rawValue, + "msgPwd": msg.password + ], data: nil) + return CoreRes.ComposeEmail(mimeEncoded: r.data) + } + func parseDecryptMsg( encrypted: Data, keys: [PrvKeyInfo], @@ -144,6 +161,7 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { jsonDict: json, data: encrypted ) + let meta = try parsed.json.decodeJson(as: ParseDecryptMsgRaw.self) let blocks = parsed.data @@ -183,8 +201,7 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { "atts": msg.atts.map { att in ["name": att.name, "type": att.type, "base64": att.base64] }, "format": fmt.rawValue, "pubKeys": msg.pubKeys, - "signingPrv": signingPrv, - "pwd": msg.password + "signingPrv": signingPrv ], data: nil) return CoreRes.ComposeEmail(mimeEncoded: r.data) } diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index c5e405b04..392318871 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -136,6 +136,7 @@ struct SendableMsg: Equatable { let pubKeys: [String]? let signingPrv: PrvKeyInfo? let password: String? + let replyToken: String? } struct DecryptErr: Decodable { diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 1a820c359..58febfafd 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -186,6 +186,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { } var headers = headers + if withAuthorization { let idToken = try await getIdToken(email: email) let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift index c2613a638..5082128a6 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift @@ -5,7 +5,7 @@ // Created by Roma Sosnovsky on 30/12/21 // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // - + import Foundation struct MessageUploadDetails: Encodable { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index e004e38fc..138245117 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -63,7 +63,8 @@ extension BackupService: BackupServiceType { atts: attachments, pubKeys: nil, signingPrv: nil, - password: nil) + password: nil, + replyToken: nil) let t = try await core.composeEmail(msg: message, fmt: .plain) try await messageSender.sendMail(input: MessageGatewayInput(mime: t.mimeEncoded, threadId: nil), diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index ff18f847d..6d0667ef5 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -64,7 +64,7 @@ extension GlobalRouter: GlobalRouterType { switch route { case .gmailLogin(let viewController): viewController.showSpinner() - + let googleService = GoogleUserService( currentUserEmail: appContext.dataService.currentUser?.email, appDelegateGoogleSessionContainer: UIApplication.shared.delegate as? AppDelegate diff --git a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt index af3ae350c..824090ade 100644 --- a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt +++ b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt @@ -86486,6 +86486,13 @@ class Endpoints { const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: req.pubKeys, data: buf_1.Buf.concat(data), armor: true }); return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(encrypted.data)); }; + this.encryptMsgWithPwd = async (uncheckedReq) => { + const req = validate_input_1.ValidateInput.encryptMsgWithPwd(uncheckedReq); + const mimeHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc }; + const mime = await mime_1.Mime.encode({ 'text/plain': req.text }, mimeHeaders); + const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: buf_1.Buf.fromUtfStr(mime), armor: true }); + return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(encrypted.data)); + }; this.generateKey = async (uncheckedReq) => { store_1.Store.keyCacheWipe(); // generateKey may be used when changing major settings, wipe cache to prevent dated results const { passphrase, userIds, variant } = validate_input_1.ValidateInput.generateKey(uncheckedReq); @@ -111776,7 +111783,7 @@ elliptic.eddsa = __webpack_require__(170); /* 144 */ /***/ (function(module) { -module.exports = JSON.parse("{\"name\":\"elliptic\",\"version\":\"6.5.4\",\"description\":\"EC cryptography\",\"main\":\"lib/elliptic.js\",\"files\":[\"lib\"],\"scripts\":{\"lint\":\"eslint lib test\",\"lint:fix\":\"npm run lint -- --fix\",\"unit\":\"istanbul test _mocha --reporter=spec test/index.js\",\"test\":\"npm run lint && npm run unit\",\"version\":\"grunt dist && git add dist/\"},\"repository\":{\"type\":\"git\",\"url\":\"git@github.com:indutny/elliptic\"},\"keywords\":[\"EC\",\"Elliptic\",\"curve\",\"Cryptography\"],\"author\":\"Fedor Indutny \",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/indutny/elliptic/issues\"},\"homepage\":\"https://github.com/indutny/elliptic\",\"devDependencies\":{\"brfs\":\"^2.0.2\",\"coveralls\":\"^3.1.0\",\"eslint\":\"^7.6.0\",\"grunt\":\"^1.2.1\",\"grunt-browserify\":\"^5.3.0\",\"grunt-cli\":\"^1.3.2\",\"grunt-contrib-connect\":\"^3.0.0\",\"grunt-contrib-copy\":\"^1.0.0\",\"grunt-contrib-uglify\":\"^5.0.0\",\"grunt-mocha-istanbul\":\"^5.0.2\",\"grunt-saucelabs\":\"^9.0.1\",\"istanbul\":\"^0.4.5\",\"mocha\":\"^8.0.1\"},\"dependencies\":{\"bn.js\":\"^4.11.9\",\"brorand\":\"^1.1.0\",\"hash.js\":\"^1.0.0\",\"hmac-drbg\":\"^1.0.1\",\"inherits\":\"^2.0.4\",\"minimalistic-assert\":\"^1.0.1\",\"minimalistic-crypto-utils\":\"^1.0.1\"}}"); +module.exports = JSON.parse("{\"_args\":[[\"elliptic@6.5.4\",\"/Users/romasosnovsky/Projects/flowcrypt-ios/Core\"]],\"_development\":true,\"_from\":\"elliptic@6.5.4\",\"_id\":\"elliptic@6.5.4\",\"_inBundle\":false,\"_integrity\":\"sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==\",\"_location\":\"/elliptic\",\"_phantomChildren\":{},\"_requested\":{\"type\":\"version\",\"registry\":true,\"raw\":\"elliptic@6.5.4\",\"name\":\"elliptic\",\"escapedName\":\"elliptic\",\"rawSpec\":\"6.5.4\",\"saveSpec\":null,\"fetchSpec\":\"6.5.4\"},\"_requiredBy\":[\"/browserify-sign\",\"/create-ecdh\"],\"_resolved\":\"https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz\",\"_spec\":\"6.5.4\",\"_where\":\"/Users/romasosnovsky/Projects/flowcrypt-ios/Core\",\"author\":{\"name\":\"Fedor Indutny\",\"email\":\"fedor@indutny.com\"},\"bugs\":{\"url\":\"https://github.com/indutny/elliptic/issues\"},\"dependencies\":{\"bn.js\":\"^4.11.9\",\"brorand\":\"^1.1.0\",\"hash.js\":\"^1.0.0\",\"hmac-drbg\":\"^1.0.1\",\"inherits\":\"^2.0.4\",\"minimalistic-assert\":\"^1.0.1\",\"minimalistic-crypto-utils\":\"^1.0.1\"},\"description\":\"EC cryptography\",\"devDependencies\":{\"brfs\":\"^2.0.2\",\"coveralls\":\"^3.1.0\",\"eslint\":\"^7.6.0\",\"grunt\":\"^1.2.1\",\"grunt-browserify\":\"^5.3.0\",\"grunt-cli\":\"^1.3.2\",\"grunt-contrib-connect\":\"^3.0.0\",\"grunt-contrib-copy\":\"^1.0.0\",\"grunt-contrib-uglify\":\"^5.0.0\",\"grunt-mocha-istanbul\":\"^5.0.2\",\"grunt-saucelabs\":\"^9.0.1\",\"istanbul\":\"^0.4.5\",\"mocha\":\"^8.0.1\"},\"files\":[\"lib\"],\"homepage\":\"https://github.com/indutny/elliptic\",\"keywords\":[\"EC\",\"Elliptic\",\"curve\",\"Cryptography\"],\"license\":\"MIT\",\"main\":\"lib/elliptic.js\",\"name\":\"elliptic\",\"repository\":{\"type\":\"git\",\"url\":\"git+ssh://git@github.com/indutny/elliptic.git\"},\"scripts\":{\"lint\":\"eslint lib test\",\"lint:fix\":\"npm run lint -- --fix\",\"test\":\"npm run lint && npm run unit\",\"unit\":\"istanbul test _mocha --reporter=spec test/index.js\",\"version\":\"grunt dist && git add dist/\"},\"version\":\"6.5.4\"}"); /***/ }), /* 145 */ @@ -170855,6 +170862,12 @@ ValidateInput.encryptMsg = (v) => { } throw new Error('Wrong request structure for NodeRequest.encryptMsg'); }; +ValidateInput.encryptMsgWithPwd = (v) => { + if ((isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]') && hasProp(v, 'msgPwd', 'string'))) { + return v; + } + throw new Error('Wrong request structure for NodeRequest.encryptMsgWithPwd'); +}; ValidateInput.composeEmail = (v) => { if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { throw new Error('Wrong request structure for NodeRequest.composeEmail, need: text,from,subject,to,cc,bcc,atts (can use empty arr for cc/bcc, and can skip atts)'); diff --git a/FlowCryptCommon/Extensions/CodableExtensions.swift b/FlowCryptCommon/Extensions/CodableExtensions.swift index ea65b6447..1f4dbc541 100644 --- a/FlowCryptCommon/Extensions/CodableExtensions.swift +++ b/FlowCryptCommon/Extensions/CodableExtensions.swift @@ -16,9 +16,4 @@ public extension Encodable { } return dictionary } - - func toJsonString() throws -> String? { - let data = try self.toJsonData() - return String(data: data, encoding: .utf8) - } } From 0aa5e0e4000046bb6c5415f5bccb32108a6c16ef Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 6 Jan 2022 23:40:35 +0200 Subject: [PATCH 03/17] #1254 update password message structure --- Core/source/mobile-interface/endpoints.ts | 6 +- .../source/mobile-interface/validate-input.ts | 6 +- FlowCrypt/Core/Core.swift | 1 + FlowCrypt/Core/CoreTypes.swift | 25 +++- .../EnterpriseServerApi.swift | 2 +- .../Models/MessageUploadDetails.swift | 10 ++ .../Backup Services/BackupService.swift | 5 +- .../ComposeMessageService.swift | 120 +++++++++++++++--- FlowCrypt/Resources/flowcrypt-ios-prod.js.txt | 12 +- 9 files changed, 151 insertions(+), 36 deletions(-) diff --git a/Core/source/mobile-interface/endpoints.ts b/Core/source/mobile-interface/endpoints.ts index eed08cde8..cecb27d0f 100644 --- a/Core/source/mobile-interface/endpoints.ts +++ b/Core/source/mobile-interface/endpoints.ts @@ -38,9 +38,7 @@ export class Endpoints { public encryptMsgWithPwd = async (uncheckedReq: any): Promise => { const req = ValidateInput.encryptMsgWithPwd(uncheckedReq); - const mimeHeaders: RichHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc }; - const mime = await Mime.encode({ 'text/plain': req.text }, mimeHeaders); - const encrypted = await PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: Buf.fromUtfStr(mime), armor: true }) as OpenPGP.EncryptArmorResult; + const encrypted = await PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; return fmtRes({}, Buf.fromUtfStr(encrypted.data)); } @@ -65,7 +63,7 @@ export class Endpoints { } if (req.format === 'plain') { const atts = (req.atts || []).map(({ name, type, base64 }) => new Att({ name, type, data: Buf.fromBase64Str(base64) })); - return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': req.text }, mimeHeaders, atts))); + return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': req.text, 'text/html': req.html }, mimeHeaders, atts))); } else if (req.format === 'encrypt-inline') { const encryptedAtts: Att[] = []; for (const att of req.atts || []) { diff --git a/Core/source/mobile-interface/validate-input.ts b/Core/source/mobile-interface/validate-input.ts index 33606105c..085498520 100644 --- a/Core/source/mobile-interface/validate-input.ts +++ b/Core/source/mobile-interface/validate-input.ts @@ -9,7 +9,7 @@ type Obj = { [k: string]: any }; export namespace NodeRequest { type PrvKeyInfo = { private: string; longid: string, passphrase: string | undefined }; type Attachment = { name: string; type: string; base64: string }; - interface composeEmailBase { text: string, to: string[], cc: string[], bcc: string[], from: string, subject: string, replyToMimeMsg: string, atts?: Attachment[] }; + interface composeEmailBase { text: string, html?: string, to: string[], cc: string[], bcc: string[], from: string, subject: string, replyToMimeMsg: string, atts?: Attachment[] }; export interface composeEmailPlain extends composeEmailBase { format: 'plain' }; export interface composeEmailEncrypted extends composeEmailBase { format: 'encrypt-inline' | 'encrypt-pgpmime', pubKeys: string[], signingPrv: PrvKeyInfo | undefined }; @@ -53,7 +53,7 @@ export class ValidateInput { public static composeEmail = (v: any): NodeRequest.composeEmail => { - if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { + if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'html', 'string?') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { throw new Error('Wrong request structure for NodeRequest.composeEmail, need: text,from,subject,to,cc,bcc,atts (can use empty arr for cc/bcc, and can skip atts)'); } if (!hasProp(v, 'atts', 'Attachment[]?')) { @@ -62,7 +62,7 @@ export class ValidateInput { if (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?') && v.pubKeys.length && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { return v as NodeRequest.composeEmailEncrypted; } - if (!v.pubKeys && v.format === 'plain') { + if ((!hasProp(v, 'pubKeys', 'string[]') || !v.pubKeys.length) && v.format === 'plain') { return v as NodeRequest.composeEmailPlain; } throw new Error('Wrong choice of pubKeys and format. Either pubKeys:[..]+format:encrypt-inline OR format:plain allowed'); diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 247bf811a..459240e00 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -192,6 +192,7 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { let r = try await call("composeEmail", jsonDict: [ "text": msg.text, + "html": msg.html, "to": msg.to, "cc": msg.cc, "bcc": msg.bcc, diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index 392318871..642cc4cd5 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -126,6 +126,7 @@ struct SendableMsg: Equatable { } let text: String + let html: String? let to: [String] let cc: [String] let bcc: [String] @@ -136,7 +137,29 @@ struct SendableMsg: Equatable { let pubKeys: [String]? let signingPrv: PrvKeyInfo? let password: String? - let replyToken: String? +} + +extension SendableMsg { + func copy(text: String? = nil, + html: String? = nil, + pubKeys: [String]? = nil, + signingPrv: PrvKeyInfo? = nil, + password: String? = nil) -> SendableMsg { + SendableMsg( + text: text ?? self.text, + html: html ?? self.html, + to: self.to, + cc: self.cc, + bcc: self.bcc, + from: self.from, + subject: self.subject, + replyToMimeMsg: self.replyToMimeMsg, + atts: self.atts, + pubKeys: pubKeys ?? self.pubKeys, + signingPrv: signingPrv ?? self.signingPrv, + password: password ?? self.password + ) + } } struct DecryptErr: Decodable { diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 58febfafd..fd497c12e 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -186,7 +186,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { } var headers = headers - + if withAuthorization { let idToken = try await getIdToken(email: email) let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift index 5082128a6..35e3bee14 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift @@ -15,3 +15,13 @@ struct MessageUploadDetails: Encodable { let cc: [String] let bcc: [String] } + +extension MessageUploadDetails { + init(from message: SendableMsg, replyToken: String) { + self.associateReplyToken = replyToken + self.from = message.from + self.to = message.to + self.cc = message.cc + self.bcc = message.bcc + } +} diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 138245117..0f7f2c7f2 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -54,6 +54,7 @@ extension BackupService: BackupServiceType { let attachments = [SendableMsg.Attachment(name: filename, type: "text/plain", base64: privateKeyData)] let message = SendableMsg( text: "setup_backup_email".localized, + html: nil, to: [userId.toMime], cc: [], bcc: [], @@ -63,8 +64,8 @@ extension BackupService: BackupServiceType { atts: attachments, pubKeys: nil, signingPrv: nil, - password: nil, - replyToken: nil) + password: nil + ) let t = try await core.composeEmail(msg: message, fmt: .plain) try await messageSender.sendMail(input: MessageGatewayInput(mime: t.mimeEncoded, threadId: nil), diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index b324aaff1..a503a12a5 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -15,6 +15,8 @@ typealias RecipientState = RecipientEmailsCellNode.Input.State protocol CoreComposeMessageType { func composeEmail(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail + func encryptMsg(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail + func encryptFile(pubKeys: [String]?, fileData: Data, name: String) async throws -> CoreRes.EncryptFile } final class ComposeMessageService { @@ -23,16 +25,25 @@ final class ComposeMessageService { private let storage: EncryptedStorageType private let contactsService: ContactsServiceType private let core: CoreComposeMessageType & KeyParser + private let enterpriseServer: EnterpriseServerApiType private let draftGateway: DraftGateway? private lazy var logger: Logger = Logger.nested(Self.self) + private struct ReplyInfo: Encodable { + let sender: String + let recipient: [String] + let subject: String + let token: String + } + init( clientConfiguration: ClientConfiguration, encryptedStorage: EncryptedStorageType, messageGateway: MessageGateway, draftGateway: DraftGateway? = nil, contactsService: ContactsServiceType? = nil, - core: CoreComposeMessageType & KeyParser = Core.shared + core: CoreComposeMessageType & KeyParser = Core.shared, + enterpriseServer: EnterpriseServerApiType = EnterpriseServerApi() ) { self.messageGateway = messageGateway self.draftGateway = draftGateway @@ -42,6 +53,7 @@ final class ComposeMessageService { clientConfiguration: clientConfiguration ) self.core = core + self.enterpriseServer = enterpriseServer self.logger = Logger.nested(in: Self.self, with: "ComposeMessageService") } @@ -55,7 +67,8 @@ final class ComposeMessageService { contextToSend: ComposeMessageContext, email: String, includeAttachments: Bool = true, - signingPrv: PrvKeyInfo? + signingPrv: PrvKeyInfo?, + isMessagePasswordSupported: Bool = false ) async throws -> SendableMsg { onStateChanged?(.validatingMessage) @@ -103,6 +116,7 @@ final class ComposeMessageService { return SendableMsg( text: text, + html: text, to: recipients.map(\.email), cc: [], bcc: [], @@ -116,12 +130,6 @@ final class ComposeMessageService { ) } - private func isMessagePasswordSupported(for email: String) -> Bool { - guard let senderDomain = email.emailParts?.domain else { return false } - let senderDomainsWithMessagePasswordSupport = ["flowcrypt.com"] - return senderDomainsWithMessagePasswordSupport.contains(senderDomain) - } - private func getRecipientKeys(for recipients: [ComposeMessageRecipient]) async throws -> [RecipientWithSortedPubKeys] { var recipientsWithKeys: [RecipientWithSortedPubKeys] = [] for recipient in recipients { @@ -160,7 +168,7 @@ final class ComposeMessageService { do { let r = try await core.composeEmail( msg: message, - fmt: MsgFmt.encryptInline + fmt: .encryptInline ) draft = try await draftGateway?.saveDraft(input: MessageGatewayInput(mime: r.mimeEncoded, threadId: threadId), draft: draft) } catch { @@ -172,17 +180,57 @@ final class ComposeMessageService { func encryptAndSend(message: SendableMsg, threadId: String?) async throws { do { onStateChanged?(.startComposing) - let r = try await core.composeEmail( - msg: message, - fmt: MsgFmt.encryptInline - ) - try await messageGateway.sendMail( - input: MessageGatewayInput(mime: r.mimeEncoded, threadId: threadId), - progressHandler: { [weak self] progress in - self?.onStateChanged?(.progressChanged(progress)) - } - ) + if let password = message.password, password.isNotEmpty { + let replyToken = try await enterpriseServer.getReplyToken(for: message.from) + + let url = try await prepareAndUploadPwdEncryptedMsg( + message: message, + replyToken: replyToken + ) + + let encryptedTextFile = try await core.encryptFile( + pubKeys: message.pubKeys, + fileData: message.text.data(), + name: "mail" + ) + + let html = generatePasswordMessageHtml(sender: message.from, url: url) + + let newMessage = SendableMsg( + text: html, + html: html, + to: message.to, + cc: message.cc, + bcc: message.bcc, + from: message.from, + subject: message.subject, + replyToMimeMsg: message.replyToMimeMsg, + atts: message.atts, + pubKeys: nil, + signingPrv: nil, + password: nil + ) + let formattedMessage = try await core.composeEmail(msg: newMessage, fmt: .plain) + try await messageGateway.sendMail( + input: MessageGatewayInput(mime: formattedMessage.mimeEncoded, threadId: threadId), + progressHandler: { [weak self] progress in + self?.onStateChanged?(.progressChanged(progress)) + } + ) + } else { + let r = try await core.composeEmail( + msg: message, + fmt: MsgFmt.encryptInline + ) + + try await messageGateway.sendMail( + input: MessageGatewayInput(mime: r.mimeEncoded, threadId: threadId), + progressHandler: { [weak self] progress in + self?.onStateChanged?(.progressChanged(progress)) + } + ) + } // cleaning any draft saved/created/fetched during editing if let draftId = draft?.identifier { @@ -193,4 +241,38 @@ final class ComposeMessageService { throw ComposeMessageError.gatewayError(error) } } + + func generateMsgInfoDiv(for message: SendableMsg, replyToken: String) throws -> String { + let replyInfo = ReplyInfo( + sender: message.from, + recipient: message.to, + subject: message.subject, + token: replyToken + ) + let replyInfoJsonString = try replyInfo.toJsonData().base64EncodedString() + return "\n\n
" + } + + func generatePasswordMessageHtml(sender: String, url: String) -> String { + let aStyle = "padding: 2px 6px; background: #2199e8; color: #fff; display: inline-block; text-decoration: none;" + return """ + \(sender) has sent you a password-encrypted email Click here to Open Message +

+ Alternatively copy and paste the following link: \(url) + """ + } + + func prepareAndUploadPwdEncryptedMsg(message: SendableMsg, replyToken: String) async throws -> String { + let infoDiv = try generateMsgInfoDiv(for: message, replyToken: replyToken) + let updatedText = message.text + infoDiv + + let messageWithInfoDiv = message.copy(text: updatedText, pubKeys: [], signingPrv: nil) + let formatted = try await core.composeEmail(msg: messageWithInfoDiv, fmt: .plain) + + let formattedMessage = messageWithInfoDiv.copy(text: formatted.mimeEncoded.toStr()) + let encoded = try await core.encryptMsg(msg: formattedMessage, fmt: .encryptInline) + + let details = MessageUploadDetails(from: message, replyToken: replyToken) + return try await enterpriseServer.upload(message: encoded.mimeEncoded, details: details) + } } diff --git a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt index 824090ade..a19d147cf 100644 --- a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt +++ b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt @@ -86488,9 +86488,9 @@ class Endpoints { }; this.encryptMsgWithPwd = async (uncheckedReq) => { const req = validate_input_1.ValidateInput.encryptMsgWithPwd(uncheckedReq); - const mimeHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc }; - const mime = await mime_1.Mime.encode({ 'text/plain': req.text }, mimeHeaders); - const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: buf_1.Buf.fromUtfStr(mime), armor: true }); + // const mimeHeaders: RichHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc }; + // const mime = await Mime.encode({ 'text/plain': req.text }, mimeHeaders); + const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: buf_1.Buf.fromUtfStr(req.text), armor: true }); return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(encrypted.data)); }; this.generateKey = async (uncheckedReq) => { @@ -86513,7 +86513,7 @@ class Endpoints { } if (req.format === 'plain') { const atts = (req.atts || []).map(({ name, type, base64 }) => new att_1.Att({ name, type, data: buf_1.Buf.fromBase64Str(base64) })); - return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(await mime_1.Mime.encode({ 'text/plain': req.text }, mimeHeaders, atts))); + return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(await mime_1.Mime.encode({ 'text/plain': req.text, 'text/html': req.html }, mimeHeaders, atts))); } else if (req.format === 'encrypt-inline') { const encryptedAtts = []; @@ -170869,7 +170869,7 @@ ValidateInput.encryptMsgWithPwd = (v) => { throw new Error('Wrong request structure for NodeRequest.encryptMsgWithPwd'); }; ValidateInput.composeEmail = (v) => { - if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { + if (!(isObj(v) && hasProp(v, 'text', 'string') && hasProp(v, 'html', 'string?') && hasProp(v, 'from', 'string') && hasProp(v, 'subject', 'string') && hasProp(v, 'to', 'string[]') && hasProp(v, 'cc', 'string[]') && hasProp(v, 'bcc', 'string[]'))) { throw new Error('Wrong request structure for NodeRequest.composeEmail, need: text,from,subject,to,cc,bcc,atts (can use empty arr for cc/bcc, and can skip atts)'); } if (!hasProp(v, 'atts', 'Attachment[]?')) { @@ -170878,7 +170878,7 @@ ValidateInput.composeEmail = (v) => { if (hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'signingPrv', 'PrvKeyInfo?') && v.pubKeys.length && (v.format === 'encrypt-inline' || v.format === 'encrypt-pgpmime')) { return v; } - if (!v.pubKeys && v.format === 'plain') { + if ((!hasProp(v, 'pubKeys', 'string[]') || !v.pubKeys.length) && v.format === 'plain') { return v; } throw new Error('Wrong choice of pubKeys and format. Either pubKeys:[..]+format:encrypt-inline OR format:plain allowed'); From bc22167a3ddc9456bf4b6bcaf7ad2db8df146dd6 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 10 Jan 2022 12:19:59 +0200 Subject: [PATCH 04/17] update msg password encryption --- Core/source/mobile-interface/endpoints.ts | 14 +- Core/tooling/build.sh | 2 +- FlowCrypt.xcodeproj/project.pbxproj | 19 +- .../xcschemes/Debug FlowCrypt.xcscheme | 2 +- .../xcschemes/Enterprise FlowCrypt.xcscheme | 2 +- .../xcshareddata/xcschemes/FlowCrypt.xcscheme | 2 +- .../xcschemes/FlowCryptAppTests.xcscheme | 2 +- .../xcschemes/FlowCryptCommon.xcscheme | 2 +- .../xcschemes/FlowCryptUI.xcscheme | 2 +- .../xcschemes/FlowCryptUIApplication.xcscheme | 2 +- FlowCrypt/Core/CoreHost.swift | 2 +- FlowCrypt/Core/CoreTypes.swift | 4 +- .../EnterpriseServerApi.swift | 4 +- .../ComposeMessageService.swift | 19 +- FlowCrypt/Resources/flowcrypt-ios-prod.js.txt | 70523 +++++++++------- 15 files changed, 39312 insertions(+), 31289 deletions(-) diff --git a/Core/source/mobile-interface/endpoints.ts b/Core/source/mobile-interface/endpoints.ts index cecb27d0f..3f29309b0 100644 --- a/Core/source/mobile-interface/endpoints.ts +++ b/Core/source/mobile-interface/endpoints.ts @@ -38,7 +38,7 @@ export class Endpoints { public encryptMsgWithPwd = async (uncheckedReq: any): Promise => { const req = ValidateInput.encryptMsgWithPwd(uncheckedReq); - const encrypted = await PgpMsg.encrypt({ pubkeys: [], pwd: req.msgPwd, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; + const encrypted = await PgpMsg.encrypt({ pubkeys: [], signingPrv: undefined, pwd: req.msgPwd, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; return fmtRes({}, Buf.fromUtfStr(encrypted.data)); } @@ -312,3 +312,15 @@ export const getSigningPrv = async (req: NodeRequest.composeEmailEncrypted): Pro } } +// export const getSigningPrvForPwd = async (req: NodeRequest.encryptMsgWithPwd): Promise => { +// if (!req.signingPrv) { +// return undefined; +// } +// const key = await readArmoredKeyOrThrow(req.signingPrv.private); +// if (await PgpKey.decrypt(key, req.signingPrv.passphrase || '')) { +// return key; +// } else { +// throw new Error(`Fail to decrypt signing key`); +// } +// } + diff --git a/Core/tooling/build.sh b/Core/tooling/build.sh index 8eb243ad1..5634bee66 100755 --- a/Core/tooling/build.sh +++ b/Core/tooling/build.sh @@ -16,7 +16,7 @@ mkdir -p build/final node tooling/fix-bundles.js # concatenate external deps into one bundle -( cd build/bundles && cat bare-html-sanitize-bundle.js bare-emailjs-bundle.js bare-openpgp-bundle.js bare-zxcvbn-bundle.js bare-encoding-japanese.js > bare-deps-bundle.js ) # bare deps +( cd build/bundles && cat bare-emailjs-bundle.js bare-openpgp-bundle.js bare-zxcvbn-bundle.js bare-encoding-japanese.js > bare-deps-bundle.js ) # bare deps # create final builds for dev, ios, android node tooling/build-final.js diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 6741bdb45..b1e2d0392 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -162,7 +162,7 @@ 9F65B0B9278346F8005CD19C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F65B0B8278346F8005CD19C /* UIConstants.swift */; }; 9F67998B277B222B00AFE5BE /* BigInt in Frameworks */ = {isa = PBXBuildFile; productRef = 9F67998A277B222B00AFE5BE /* BigInt */; }; 9F67998C277B3E4000AFE5BE /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26D5E20275AA417007B8802 /* BundleExtensions.swift */; }; - 9F67998D277B3E4D00AFE5BE /* CommandLineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26D5E1E275AA295007B8802 /* CommandLineExtensions.swift */; }; + 9F67998D277B3E4D00AFE5BE /* (null) in Sources */ = {isa = PBXBuildFile; }; 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6EE1542597399D0059BA51 /* BackupProvider.swift */; }; 9F6EE17B2598F9FA0059BA51 /* Gmail+Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6EE17A2598F9FA0059BA51 /* Gmail+Backup.swift */; }; 9F6F3BEE26ADF5DE005BD9C6 /* ComposeMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6F3BEC26ADF5DE005BD9C6 /* ComposeMessageService.swift */; }; @@ -370,8 +370,6 @@ D2FD0F692453245E00259FF0 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FD0F682453245E00259FF0 /* Either.swift */; }; D2FF6966243115EC007182F0 /* SetupImapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6965243115EC007182F0 /* SetupImapViewController.swift */; }; D2FF6968243115F9007182F0 /* SetupImapViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6967243115F9007182F0 /* SetupImapViewDecorator.swift */; }; - E26D5E21275AA417007B8802 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26D5E20275AA417007B8802 /* BundleExtensions.swift */; }; - E26D5E22275AA421007B8802 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26D5E20275AA417007B8802 /* BundleExtensions.swift */; }; F191F621272511790053833E /* BlurViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F191F620272511790053833E /* BlurViewController.swift */; }; F80E95362720B6640093F243 /* DraftsListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80E95352720B6640093F243 /* DraftsListProvider.swift */; }; F8678DCC2722143300BB1710 /* GmailService+draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8678DCB2722143300BB1710 /* GmailService+draft.swift */; }; @@ -1029,15 +1027,6 @@ 517C2E312779F91300FECF32 /* Models */ = { isa = PBXGroup; children = ( - D2D27B78248A8694007346FA /* BigIntExtension.swift */, - E26D5E20275AA417007B8802 /* BundleExtensions.swift */, - 51E4F0B427348E310017DABB /* Error+Extension.swift */, - 51B0C7702729861C00124663 /* String+Extension.swift */, - 518389C92726D8F700131B2C /* UIApplicationExtension.swift */, - 9F31AB9D232BF2A600CF87EA /* UIColorExtension.swift */, - 518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */, - 9FEED1B7230C08D700700F8E /* UIViewControllerExtensions.swift */, - 32DCA7E0AFE19FACB0F233ED /* URLSessionExtension.swift */, 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */, 5152F195277E5AED00BE8A5B /* MessageUploadDetails.swift */, 517C2E2F2779F90700FECF32 /* MultipartDataRequest.swift */, @@ -1888,7 +1877,6 @@ 21C7DEFB26669A3700C44800 /* CalendarExtensions.swift */, 32DCA38E87F2B7196E0E1F1F /* CodableExtensions.swift */, D2531F452402C62D007E5198 /* CollectionExtensions.swift */, - E26D5E1E275AA295007B8802 /* CommandLineExtensions.swift */, 9F0C3C2723194E8500299985 /* CommonExtensions.swift */, 9F56BD3723438C7000A7371A /* DateFormattingExtensions.swift */, 32DCABF1508B0C08DEDE2059 /* DispatchTimeExtensions.swift */, @@ -2268,7 +2256,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = "FlowCrypt Limited"; TargetAttributes = { 9F2AC5C5267BE99E00F6149B = { @@ -2847,7 +2835,7 @@ D2531F3F24000E57007E5198 /* StringExtensions.swift in Sources */, D2CDC3CE2402CDB4002B045F /* DispatchTimeExtensions.swift in Sources */, 9F44971626430710003A9FE9 /* Trace.swift in Sources */, - 9F67998D277B3E4D00AFE5BE /* CommandLineExtensions.swift in Sources */, + 9F67998D277B3E4D00AFE5BE /* (null) in Sources */, 9F67998C277B3E4000AFE5BE /* BundleExtensions.swift in Sources */, D2CDC3CD2402CCD7002B045F /* UIImageExtensions.swift in Sources */, 21F836B62652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift in Sources */, @@ -2867,7 +2855,6 @@ 9F8076D927762515008E5874 /* BigIntExtensions.swift in Sources */, 9FBD69EC27775086002FC602 /* UIApplicationExtensions.swift in Sources */, D2CDC3D42402D50A002B045F /* CodableExtensions.swift in Sources */, - D2CDC3D42402D50A002B045F /* CodableExntensions.swift in Sources */, 9FD505272785C2CD00FAA82F /* UIDeviceExtensions.swift in Sources */, D2531F3D24000E37007E5198 /* UIVIewExtensions.swift in Sources */, D2531F3723FFF043007E5198 /* CommonExtensions.swift in Sources */, diff --git a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/Debug FlowCrypt.xcscheme b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/Debug FlowCrypt.xcscheme index 688d5b1eb..06a61ff0b 100644 --- a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/Debug FlowCrypt.xcscheme +++ b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/Debug FlowCrypt.xcscheme @@ -1,6 +1,6 @@ Bool { if Bundle.isEnterprise() { return false // FlowCrypt Enterprise Server (FES) required on enterprise bundle @@ -192,7 +192,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { return false // do not tolertate the error } } - + private func doesTheInternetWork() async -> Bool { // this API is mentioned here: // https://www.chromium.org/chromium-os/chromiumos-design-docs/network-portal-detection diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index a503a12a5..4d5a49cf2 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -189,15 +189,15 @@ final class ComposeMessageService { replyToken: replyToken ) - let encryptedTextFile = try await core.encryptFile( - pubKeys: message.pubKeys, - fileData: message.text.data(), - name: "mail" + let bodyAttachment = SendableMsg.Attachment( + name: "encrypted.asc", + type: "text", + base64: message.text.data().base64EncodedString() ) let html = generatePasswordMessageHtml(sender: message.from, url: url) - let newMessage = SendableMsg( + let passwordMessage = SendableMsg( text: html, html: html, to: message.to, @@ -206,12 +206,12 @@ final class ComposeMessageService { from: message.from, subject: message.subject, replyToMimeMsg: message.replyToMimeMsg, - atts: message.atts, + atts: [bodyAttachment] + message.atts, pubKeys: nil, signingPrv: nil, password: nil ) - let formattedMessage = try await core.composeEmail(msg: newMessage, fmt: .plain) + let formattedMessage = try await core.composeEmail(msg: passwordMessage, fmt: .plain) try await messageGateway.sendMail( input: MessageGatewayInput(mime: formattedMessage.mimeEncoded, threadId: threadId), progressHandler: { [weak self] progress in @@ -266,10 +266,11 @@ final class ComposeMessageService { let infoDiv = try generateMsgInfoDiv(for: message, replyToken: replyToken) let updatedText = message.text + infoDiv - let messageWithInfoDiv = message.copy(text: updatedText, pubKeys: [], signingPrv: nil) + let messageWithInfoDiv = message.copy(text: updatedText) let formatted = try await core.composeEmail(msg: messageWithInfoDiv, fmt: .plain) - let formattedMessage = messageWithInfoDiv.copy(text: formatted.mimeEncoded.toStr()) + let formattedMessage = SendableMsg(text: formatted.mimeEncoded.toStr(), html: formatted.mimeEncoded.toStr(), to: message.to, cc: message.cc, bcc: message.bcc, from: message.from, subject: message.subject, replyToMimeMsg: message.replyToMimeMsg, atts: message.atts, pubKeys: nil, signingPrv: nil, password: message.password) + let encoded = try await core.encryptMsg(msg: formattedMessage, fmt: .encryptInline) let details = MessageUploadDetails(from: message, replyToken: replyToken) diff --git a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt index a19d147cf..3786f6d32 100644 --- a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt +++ b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt @@ -219,5717 +219,7 @@ const hostRsaDecryption = async (ASN1, BN, c_encrypted, n, e, d, p, q) => { return new BN.default(openpgp.util.b64_to_Uint8Array(decryptedBase64)).toArrayLike(Uint8Array, 'be', n.byteLength()); }; - "use strict";function _typeof(obj){if(typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"){_typeof=function _typeof(obj){return typeof obj;};}else{_typeof=function _typeof(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};}return _typeof(obj);}(function(f){if((typeof exports==="undefined"?"undefined":_typeof(exports))==="object"&&typeof module!=="undefined"){module.exports=f();}else if(typeof define==="function"&&define.amd){define([],f);}else{var g;if(typeof window!=="undefined"){g=window;}else if(typeof global!=="undefined"){g=global;}else if(typeof self!=="undefined"){g=self;}else{g=this;}g.sanitizeHtml=f();}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"==typeof require&&require,i=0;i0){throw new Error('Invalid string. Length must be a multiple of 4');}// Trim off extra bytes after placeholder bytes are found -// See: https://github.com/beatgammit/base64-js/issues/42 -var validLen=b64.indexOf('=');if(validLen===-1)validLen=len;var placeHoldersLen=validLen===len?0:4-validLen%4;return[validLen,placeHoldersLen];}// base64 is 4/3 + up to two characters of the original data -function byteLength(b64){var lens=getLens(b64);var validLen=lens[0];var placeHoldersLen=lens[1];return(validLen+placeHoldersLen)*3/4-placeHoldersLen;}function _byteLength(b64,validLen,placeHoldersLen){return(validLen+placeHoldersLen)*3/4-placeHoldersLen;}function toByteArray(b64){var tmp;var lens=getLens(b64);var validLen=lens[0];var placeHoldersLen=lens[1];var arr=new Arr(_byteLength(b64,validLen,placeHoldersLen));var curByte=0;// if there are placeholders, only get up to the last complete 4 chars -var len=placeHoldersLen>0?validLen-4:validLen;var i;for(i=0;i>16&0xFF;arr[curByte++]=tmp>>8&0xFF;arr[curByte++]=tmp&0xFF;}if(placeHoldersLen===2){tmp=revLookup[b64.charCodeAt(i)]<<2|revLookup[b64.charCodeAt(i+1)]>>4;arr[curByte++]=tmp&0xFF;}if(placeHoldersLen===1){tmp=revLookup[b64.charCodeAt(i)]<<10|revLookup[b64.charCodeAt(i+1)]<<4|revLookup[b64.charCodeAt(i+2)]>>2;arr[curByte++]=tmp>>8&0xFF;arr[curByte++]=tmp&0xFF;}return arr;}function tripletToBase64(num){return lookup[num>>18&0x3F]+lookup[num>>12&0x3F]+lookup[num>>6&0x3F]+lookup[num&0x3F];}function encodeChunk(uint8,start,end){var tmp;var output=[];for(var i=start;ilen2?len2:i+maxChunkLength));}// pad the end with zeros, but make sure to not forget the extra bytes -if(extraBytes===1){tmp=uint8[len-1];parts.push(lookup[tmp>>2]+lookup[tmp<<4&0x3F]+'==');}else if(extraBytes===2){tmp=(uint8[len-2]<<8)+uint8[len-1];parts.push(lookup[tmp>>10]+lookup[tmp>>4&0x3F]+lookup[tmp<<2&0x3F]+'=');}return parts.join('');}},{}],3:[function(require,module,exports){},{}],4:[function(require,module,exports){(function(Buffer){/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh - * @license MIT - */ /* eslint-disable no-proto */'use strict';var base64=require('base64-js');var ieee754=require('ieee754');var customInspectSymbol=typeof Symbol==='function'&&typeof Symbol["for"]==='function'?Symbol["for"]('nodejs.util.inspect.custom'):null;exports.Buffer=Buffer;exports.SlowBuffer=SlowBuffer;exports.INSPECT_MAX_BYTES=50;var K_MAX_LENGTH=0x7fffffff;exports.kMaxLength=K_MAX_LENGTH;/** - * If `Buffer.TYPED_ARRAY_SUPPORT`: - * === true Use Uint8Array implementation (fastest) - * === false Print warning and recommend using `buffer` v4.x which has an Object - * implementation (most compatible, even IE6) - * - * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, - * Opera 11.6+, iOS 4.2+. - * - * We report that the browser does not support typed arrays if the are not subclassable - * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` - * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support - * for __proto__ and has a buggy typed array implementation. - */Buffer.TYPED_ARRAY_SUPPORT=typedArraySupport();if(!Buffer.TYPED_ARRAY_SUPPORT&&typeof console!=='undefined'&&typeof console.error==='function'){console.error('This browser lacks typed array (Uint8Array) support which is required by '+'`buffer` v5.x. Use `buffer` v4.x if you require old browser support.');}function typedArraySupport(){// Can typed array instances can be augmented? -try{var arr=new Uint8Array(1);var proto={foo:function foo(){return 42;}};Object.setPrototypeOf(proto,Uint8Array.prototype);Object.setPrototypeOf(arr,proto);return arr.foo()===42;}catch(e){return false;}}Object.defineProperty(Buffer.prototype,'parent',{enumerable:true,get:function get(){if(!Buffer.isBuffer(this))return undefined;return this.buffer;}});Object.defineProperty(Buffer.prototype,'offset',{enumerable:true,get:function get(){if(!Buffer.isBuffer(this))return undefined;return this.byteOffset;}});function createBuffer(length){if(length>K_MAX_LENGTH){throw new RangeError('The value "'+length+'" is invalid for option "size"');}// Return an augmented `Uint8Array` instance -var buf=new Uint8Array(length);Object.setPrototypeOf(buf,Buffer.prototype);return buf;}/** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. - */function Buffer(arg,encodingOrOffset,length){// Common case. -if(typeof arg==='number'){if(typeof encodingOrOffset==='string'){throw new TypeError('The "string" argument must be of type string. Received type number');}return allocUnsafe(arg);}return from(arg,encodingOrOffset,length);}// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 -if(typeof Symbol!=='undefined'&&Symbol.species!=null&&Buffer[Symbol.species]===Buffer){Object.defineProperty(Buffer,Symbol.species,{value:null,configurable:true,enumerable:false,writable:false});}Buffer.poolSize=8192;// not used by this implementation -function from(value,encodingOrOffset,length){if(typeof value==='string'){return fromString(value,encodingOrOffset);}if(ArrayBuffer.isView(value)){return fromArrayLike(value);}if(value==null){throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, '+'or Array-like Object. Received type '+_typeof(value));}if(isInstance(value,ArrayBuffer)||value&&isInstance(value.buffer,ArrayBuffer)){return fromArrayBuffer(value,encodingOrOffset,length);}if(typeof value==='number'){throw new TypeError('The "value" argument must not be of type number. Received type number');}var valueOf=value.valueOf&&value.valueOf();if(valueOf!=null&&valueOf!==value){return Buffer.from(valueOf,encodingOrOffset,length);}var b=fromObject(value);if(b)return b;if(typeof Symbol!=='undefined'&&Symbol.toPrimitive!=null&&typeof value[Symbol.toPrimitive]==='function'){return Buffer.from(value[Symbol.toPrimitive]('string'),encodingOrOffset,length);}throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, '+'or Array-like Object. Received type '+_typeof(value));}/** - * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError - * if value is a number. - * Buffer.from(str[, encoding]) - * Buffer.from(array) - * Buffer.from(buffer) - * Buffer.from(arrayBuffer[, byteOffset[, length]]) - **/Buffer.from=function(value,encodingOrOffset,length){return from(value,encodingOrOffset,length);};// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: -// https://github.com/feross/buffer/pull/148 -Object.setPrototypeOf(Buffer.prototype,Uint8Array.prototype);Object.setPrototypeOf(Buffer,Uint8Array);function assertSize(size){if(typeof size!=='number'){throw new TypeError('"size" argument must be of type number');}else if(size<0){throw new RangeError('The value "'+size+'" is invalid for option "size"');}}function alloc(size,fill,encoding){assertSize(size);if(size<=0){return createBuffer(size);}if(fill!==undefined){// Only pay attention to encoding if it's a string. This -// prevents accidentally sending in a number that would -// be interpretted as a start offset. -return typeof encoding==='string'?createBuffer(size).fill(fill,encoding):createBuffer(size).fill(fill);}return createBuffer(size);}/** - * Creates a new filled Buffer instance. - * alloc(size[, fill[, encoding]]) - **/Buffer.alloc=function(size,fill,encoding){return alloc(size,fill,encoding);};function allocUnsafe(size){assertSize(size);return createBuffer(size<0?0:checked(size)|0);}/** - * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. - * */Buffer.allocUnsafe=function(size){return allocUnsafe(size);};/** - * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. - */Buffer.allocUnsafeSlow=function(size){return allocUnsafe(size);};function fromString(string,encoding){if(typeof encoding!=='string'||encoding===''){encoding='utf8';}if(!Buffer.isEncoding(encoding)){throw new TypeError('Unknown encoding: '+encoding);}var length=byteLength(string,encoding)|0;var buf=createBuffer(length);var actual=buf.write(string,encoding);if(actual!==length){// Writing a hex string, for example, that contains invalid characters will -// cause everything after the first invalid character to be ignored. (e.g. -// 'abxxcd' will be treated as 'ab') -buf=buf.slice(0,actual);}return buf;}function fromArrayLike(array){var length=array.length<0?0:checked(array.length)|0;var buf=createBuffer(length);for(var i=0;i=K_MAX_LENGTH){throw new RangeError('Attempt to allocate Buffer larger than maximum '+'size: 0x'+K_MAX_LENGTH.toString(16)+' bytes');}return length|0;}function SlowBuffer(length){if(+length!=length){// eslint-disable-line eqeqeq -length=0;}return Buffer.alloc(+length);}Buffer.isBuffer=function isBuffer(b){return b!=null&&b._isBuffer===true&&b!==Buffer.prototype;// so Buffer.isBuffer(Buffer.prototype) will be false -};Buffer.compare=function compare(a,b){if(isInstance(a,Uint8Array))a=Buffer.from(a,a.offset,a.byteLength);if(isInstance(b,Uint8Array))b=Buffer.from(b,b.offset,b.byteLength);if(!Buffer.isBuffer(a)||!Buffer.isBuffer(b)){throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');}if(a===b)return 0;var x=a.length;var y=b.length;for(var i=0,len=Math.min(x,y);i2&&arguments[2]===true;if(!mustMatch&&len===0)return 0;// Use a for loop to avoid recursion -var loweredCase=false;for(;;){switch(encoding){case'ascii':case'latin1':case'binary':return len;case'utf8':case'utf-8':return utf8ToBytes(string).length;case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':return len*2;case'hex':return len>>>1;case'base64':return base64ToBytes(string).length;default:if(loweredCase){return mustMatch?-1:utf8ToBytes(string).length;// assume utf8 -}encoding=(''+encoding).toLowerCase();loweredCase=true;}}}Buffer.byteLength=byteLength;function slowToString(encoding,start,end){var loweredCase=false;// No need to verify that "this.length <= MAX_UINT32" since it's a read-only -// property of a typed array. -// This behaves neither like String nor Uint8Array in that we set start/end -// to their upper/lower bounds if the value passed is out of range. -// undefined is handled specially as per ECMA-262 6th Edition, -// Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. -if(start===undefined||start<0){start=0;}// Return early if start > this.length. Done here to prevent potential uint32 -// coercion fail below. -if(start>this.length){return'';}if(end===undefined||end>this.length){end=this.length;}if(end<=0){return'';}// Force coersion to uint32. This will also coerce falsey/NaN values to 0. -end>>>=0;start>>>=0;if(end<=start){return'';}if(!encoding)encoding='utf8';while(true){switch(encoding){case'hex':return hexSlice(this,start,end);case'utf8':case'utf-8':return utf8Slice(this,start,end);case'ascii':return asciiSlice(this,start,end);case'latin1':case'binary':return latin1Slice(this,start,end);case'base64':return base64Slice(this,start,end);case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':return utf16leSlice(this,start,end);default:if(loweredCase)throw new TypeError('Unknown encoding: '+encoding);encoding=(encoding+'').toLowerCase();loweredCase=true;}}}// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) -// to detect a Buffer instance. It's not possible to use `instanceof Buffer` -// reliably in a browserify context because there could be multiple different -// copies of the 'buffer' package in use. This method works even for Buffer -// instances that were created from another copy of the `buffer` package. -// See: https://github.com/feross/buffer/issues/154 -Buffer.prototype._isBuffer=true;function swap(b,n,m){var i=b[n];b[n]=b[m];b[m]=i;}Buffer.prototype.swap16=function swap16(){var len=this.length;if(len%2!==0){throw new RangeError('Buffer size must be a multiple of 16-bits');}for(var i=0;imax)str+=' ... ';return'';};if(customInspectSymbol){Buffer.prototype[customInspectSymbol]=Buffer.prototype.inspect;}Buffer.prototype.compare=function compare(target,start,end,thisStart,thisEnd){if(isInstance(target,Uint8Array)){target=Buffer.from(target,target.offset,target.byteLength);}if(!Buffer.isBuffer(target)){throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. '+'Received type '+_typeof(target));}if(start===undefined){start=0;}if(end===undefined){end=target?target.length:0;}if(thisStart===undefined){thisStart=0;}if(thisEnd===undefined){thisEnd=this.length;}if(start<0||end>target.length||thisStart<0||thisEnd>this.length){throw new RangeError('out of range index');}if(thisStart>=thisEnd&&start>=end){return 0;}if(thisStart>=thisEnd){return-1;}if(start>=end){return 1;}start>>>=0;end>>>=0;thisStart>>>=0;thisEnd>>>=0;if(this===target)return 0;var x=thisEnd-thisStart;var y=end-start;var len=Math.min(x,y);var thisCopy=this.slice(thisStart,thisEnd);var targetCopy=target.slice(start,end);for(var i=0;i= `byteOffset`, -// OR the last index of `val` in `buffer` at offset <= `byteOffset`. -// -// Arguments: -// - buffer - a Buffer to search -// - val - a string, Buffer, or number -// - byteOffset - an index into `buffer`; will be clamped to an int32 -// - encoding - an optional encoding, relevant is val is a string -// - dir - true for indexOf, false for lastIndexOf -function bidirectionalIndexOf(buffer,val,byteOffset,encoding,dir){// Empty buffer means no match -if(buffer.length===0)return-1;// Normalize byteOffset -if(typeof byteOffset==='string'){encoding=byteOffset;byteOffset=0;}else if(byteOffset>0x7fffffff){byteOffset=0x7fffffff;}else if(byteOffset<-0x80000000){byteOffset=-0x80000000;}byteOffset=+byteOffset;// Coerce to Number. -if(numberIsNaN(byteOffset)){// byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer -byteOffset=dir?0:buffer.length-1;}// Normalize byteOffset: negative offsets start from the end of the buffer -if(byteOffset<0)byteOffset=buffer.length+byteOffset;if(byteOffset>=buffer.length){if(dir)return-1;else byteOffset=buffer.length-1;}else if(byteOffset<0){if(dir)byteOffset=0;else return-1;}// Normalize val -if(typeof val==='string'){val=Buffer.from(val,encoding);}// Finally, search either indexOf (if dir is true) or lastIndexOf -if(Buffer.isBuffer(val)){// Special case: looking for empty string/buffer always fails -if(val.length===0){return-1;}return arrayIndexOf(buffer,val,byteOffset,encoding,dir);}else if(typeof val==='number'){val=val&0xFF;// Search for a byte value [0-255] -if(typeof Uint8Array.prototype.indexOf==='function'){if(dir){return Uint8Array.prototype.indexOf.call(buffer,val,byteOffset);}else{return Uint8Array.prototype.lastIndexOf.call(buffer,val,byteOffset);}}return arrayIndexOf(buffer,[val],byteOffset,encoding,dir);}throw new TypeError('val must be string, number or Buffer');}function arrayIndexOf(arr,val,byteOffset,encoding,dir){var indexSize=1;var arrLength=arr.length;var valLength=val.length;if(encoding!==undefined){encoding=String(encoding).toLowerCase();if(encoding==='ucs2'||encoding==='ucs-2'||encoding==='utf16le'||encoding==='utf-16le'){if(arr.length<2||val.length<2){return-1;}indexSize=2;arrLength/=2;valLength/=2;byteOffset/=2;}}function read(buf,i){if(indexSize===1){return buf[i];}else{return buf.readUInt16BE(i*indexSize);}}var i;if(dir){var foundIndex=-1;for(i=byteOffset;iarrLength)byteOffset=arrLength-valLength;for(i=byteOffset;i>=0;i--){var found=true;for(var j=0;jremaining){length=remaining;}}var strLen=string.length;if(length>strLen/2){length=strLen/2;}for(var i=0;i>>0;if(isFinite(length)){length=length>>>0;if(encoding===undefined)encoding='utf8';}else{encoding=length;length=undefined;}}else{throw new Error('Buffer.write(string, encoding, offset[, length]) is no longer supported');}var remaining=this.length-offset;if(length===undefined||length>remaining)length=remaining;if(string.length>0&&(length<0||offset<0)||offset>this.length){throw new RangeError('Attempt to write outside buffer bounds');}if(!encoding)encoding='utf8';var loweredCase=false;for(;;){switch(encoding){case'hex':return hexWrite(this,string,offset,length);case'utf8':case'utf-8':return utf8Write(this,string,offset,length);case'ascii':return asciiWrite(this,string,offset,length);case'latin1':case'binary':return latin1Write(this,string,offset,length);case'base64':// Warning: maxLength not taken into account in base64Write -return base64Write(this,string,offset,length);case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':return ucs2Write(this,string,offset,length);default:if(loweredCase)throw new TypeError('Unknown encoding: '+encoding);encoding=(''+encoding).toLowerCase();loweredCase=true;}}};Buffer.prototype.toJSON=function toJSON(){return{type:'Buffer',data:Array.prototype.slice.call(this._arr||this,0)};};function base64Slice(buf,start,end){if(start===0&&end===buf.length){return base64.fromByteArray(buf);}else{return base64.fromByteArray(buf.slice(start,end));}}function utf8Slice(buf,start,end){end=Math.min(buf.length,end);var res=[];var i=start;while(i0xEF?4:firstByte>0xDF?3:firstByte>0xBF?2:1;if(i+bytesPerSequence<=end){var secondByte,thirdByte,fourthByte,tempCodePoint;switch(bytesPerSequence){case 1:if(firstByte<0x80){codePoint=firstByte;}break;case 2:secondByte=buf[i+1];if((secondByte&0xC0)===0x80){tempCodePoint=(firstByte&0x1F)<<0x6|secondByte&0x3F;if(tempCodePoint>0x7F){codePoint=tempCodePoint;}}break;case 3:secondByte=buf[i+1];thirdByte=buf[i+2];if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80){tempCodePoint=(firstByte&0xF)<<0xC|(secondByte&0x3F)<<0x6|thirdByte&0x3F;if(tempCodePoint>0x7FF&&(tempCodePoint<0xD800||tempCodePoint>0xDFFF)){codePoint=tempCodePoint;}}break;case 4:secondByte=buf[i+1];thirdByte=buf[i+2];fourthByte=buf[i+3];if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80&&(fourthByte&0xC0)===0x80){tempCodePoint=(firstByte&0xF)<<0x12|(secondByte&0x3F)<<0xC|(thirdByte&0x3F)<<0x6|fourthByte&0x3F;if(tempCodePoint>0xFFFF&&tempCodePoint<0x110000){codePoint=tempCodePoint;}}}}if(codePoint===null){// we did not generate a valid codePoint so insert a -// replacement char (U+FFFD) and advance only 1 byte -codePoint=0xFFFD;bytesPerSequence=1;}else if(codePoint>0xFFFF){// encode to utf16 (surrogate pair dance) -codePoint-=0x10000;res.push(codePoint>>>10&0x3FF|0xD800);codePoint=0xDC00|codePoint&0x3FF;}res.push(codePoint);i+=bytesPerSequence;}return decodeCodePointsArray(res);}// Based on http://stackoverflow.com/a/22747272/680742, the browser with -// the lowest limit is Chrome, with 0x10000 args. -// We go 1 magnitude less, for safety -var MAX_ARGUMENTS_LENGTH=0x1000;function decodeCodePointsArray(codePoints){var len=codePoints.length;if(len<=MAX_ARGUMENTS_LENGTH){return String.fromCharCode.apply(String,codePoints);// avoid extra slice() -}// Decode in chunks to avoid "call stack size exceeded". -var res='';var i=0;while(ilen)end=len;var out='';for(var i=start;ilen){start=len;}if(end<0){end+=len;if(end<0)end=0;}else if(end>len){end=len;}if(endlength)throw new RangeError('Trying to access beyond buffer length');}Buffer.prototype.readUIntLE=function readUIntLE(offset,byteLength,noAssert){offset=offset>>>0;byteLength=byteLength>>>0;if(!noAssert)checkOffset(offset,byteLength,this.length);var val=this[offset];var mul=1;var i=0;while(++i>>0;byteLength=byteLength>>>0;if(!noAssert){checkOffset(offset,byteLength,this.length);}var val=this[offset+--byteLength];var mul=1;while(byteLength>0&&(mul*=0x100)){val+=this[offset+--byteLength]*mul;}return val;};Buffer.prototype.readUInt8=function readUInt8(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,1,this.length);return this[offset];};Buffer.prototype.readUInt16LE=function readUInt16LE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,2,this.length);return this[offset]|this[offset+1]<<8;};Buffer.prototype.readUInt16BE=function readUInt16BE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,2,this.length);return this[offset]<<8|this[offset+1];};Buffer.prototype.readUInt32LE=function readUInt32LE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return(this[offset]|this[offset+1]<<8|this[offset+2]<<16)+this[offset+3]*0x1000000;};Buffer.prototype.readUInt32BE=function readUInt32BE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return this[offset]*0x1000000+(this[offset+1]<<16|this[offset+2]<<8|this[offset+3]);};Buffer.prototype.readIntLE=function readIntLE(offset,byteLength,noAssert){offset=offset>>>0;byteLength=byteLength>>>0;if(!noAssert)checkOffset(offset,byteLength,this.length);var val=this[offset];var mul=1;var i=0;while(++i=mul)val-=Math.pow(2,8*byteLength);return val;};Buffer.prototype.readIntBE=function readIntBE(offset,byteLength,noAssert){offset=offset>>>0;byteLength=byteLength>>>0;if(!noAssert)checkOffset(offset,byteLength,this.length);var i=byteLength;var mul=1;var val=this[offset+--i];while(i>0&&(mul*=0x100)){val+=this[offset+--i]*mul;}mul*=0x80;if(val>=mul)val-=Math.pow(2,8*byteLength);return val;};Buffer.prototype.readInt8=function readInt8(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,1,this.length);if(!(this[offset]&0x80))return this[offset];return(0xff-this[offset]+1)*-1;};Buffer.prototype.readInt16LE=function readInt16LE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,2,this.length);var val=this[offset]|this[offset+1]<<8;return val&0x8000?val|0xFFFF0000:val;};Buffer.prototype.readInt16BE=function readInt16BE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,2,this.length);var val=this[offset+1]|this[offset]<<8;return val&0x8000?val|0xFFFF0000:val;};Buffer.prototype.readInt32LE=function readInt32LE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return this[offset]|this[offset+1]<<8|this[offset+2]<<16|this[offset+3]<<24;};Buffer.prototype.readInt32BE=function readInt32BE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return this[offset]<<24|this[offset+1]<<16|this[offset+2]<<8|this[offset+3];};Buffer.prototype.readFloatLE=function readFloatLE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return ieee754.read(this,offset,true,23,4);};Buffer.prototype.readFloatBE=function readFloatBE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,4,this.length);return ieee754.read(this,offset,false,23,4);};Buffer.prototype.readDoubleLE=function readDoubleLE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,8,this.length);return ieee754.read(this,offset,true,52,8);};Buffer.prototype.readDoubleBE=function readDoubleBE(offset,noAssert){offset=offset>>>0;if(!noAssert)checkOffset(offset,8,this.length);return ieee754.read(this,offset,false,52,8);};function checkInt(buf,value,offset,ext,max,min){if(!Buffer.isBuffer(buf))throw new TypeError('"buffer" argument must be a Buffer instance');if(value>max||valuebuf.length)throw new RangeError('Index out of range');}Buffer.prototype.writeUIntLE=function writeUIntLE(value,offset,byteLength,noAssert){value=+value;offset=offset>>>0;byteLength=byteLength>>>0;if(!noAssert){var maxBytes=Math.pow(2,8*byteLength)-1;checkInt(this,value,offset,byteLength,maxBytes,0);}var mul=1;var i=0;this[offset]=value&0xFF;while(++i>>0;byteLength=byteLength>>>0;if(!noAssert){var maxBytes=Math.pow(2,8*byteLength)-1;checkInt(this,value,offset,byteLength,maxBytes,0);}var i=byteLength-1;var mul=1;this[offset+i]=value&0xFF;while(--i>=0&&(mul*=0x100)){this[offset+i]=value/mul&0xFF;}return offset+byteLength;};Buffer.prototype.writeUInt8=function writeUInt8(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,1,0xff,0);this[offset]=value&0xff;return offset+1;};Buffer.prototype.writeUInt16LE=function writeUInt16LE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,2,0xffff,0);this[offset]=value&0xff;this[offset+1]=value>>>8;return offset+2;};Buffer.prototype.writeUInt16BE=function writeUInt16BE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,2,0xffff,0);this[offset]=value>>>8;this[offset+1]=value&0xff;return offset+2;};Buffer.prototype.writeUInt32LE=function writeUInt32LE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0);this[offset+3]=value>>>24;this[offset+2]=value>>>16;this[offset+1]=value>>>8;this[offset]=value&0xff;return offset+4;};Buffer.prototype.writeUInt32BE=function writeUInt32BE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0);this[offset]=value>>>24;this[offset+1]=value>>>16;this[offset+2]=value>>>8;this[offset+3]=value&0xff;return offset+4;};Buffer.prototype.writeIntLE=function writeIntLE(value,offset,byteLength,noAssert){value=+value;offset=offset>>>0;if(!noAssert){var limit=Math.pow(2,8*byteLength-1);checkInt(this,value,offset,byteLength,limit-1,-limit);}var i=0;var mul=1;var sub=0;this[offset]=value&0xFF;while(++i>0)-sub&0xFF;}return offset+byteLength;};Buffer.prototype.writeIntBE=function writeIntBE(value,offset,byteLength,noAssert){value=+value;offset=offset>>>0;if(!noAssert){var limit=Math.pow(2,8*byteLength-1);checkInt(this,value,offset,byteLength,limit-1,-limit);}var i=byteLength-1;var mul=1;var sub=0;this[offset+i]=value&0xFF;while(--i>=0&&(mul*=0x100)){if(value<0&&sub===0&&this[offset+i+1]!==0){sub=1;}this[offset+i]=(value/mul>>0)-sub&0xFF;}return offset+byteLength;};Buffer.prototype.writeInt8=function writeInt8(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,1,0x7f,-0x80);if(value<0)value=0xff+value+1;this[offset]=value&0xff;return offset+1;};Buffer.prototype.writeInt16LE=function writeInt16LE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000);this[offset]=value&0xff;this[offset+1]=value>>>8;return offset+2;};Buffer.prototype.writeInt16BE=function writeInt16BE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000);this[offset]=value>>>8;this[offset+1]=value&0xff;return offset+2;};Buffer.prototype.writeInt32LE=function writeInt32LE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000);this[offset]=value&0xff;this[offset+1]=value>>>8;this[offset+2]=value>>>16;this[offset+3]=value>>>24;return offset+4;};Buffer.prototype.writeInt32BE=function writeInt32BE(value,offset,noAssert){value=+value;offset=offset>>>0;if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000);if(value<0)value=0xffffffff+value+1;this[offset]=value>>>24;this[offset+1]=value>>>16;this[offset+2]=value>>>8;this[offset+3]=value&0xff;return offset+4;};function checkIEEE754(buf,value,offset,ext,max,min){if(offset+ext>buf.length)throw new RangeError('Index out of range');if(offset<0)throw new RangeError('Index out of range');}function writeFloat(buf,value,offset,littleEndian,noAssert){value=+value;offset=offset>>>0;if(!noAssert){checkIEEE754(buf,value,offset,4,3.4028234663852886e+38,-3.4028234663852886e+38);}ieee754.write(buf,value,offset,littleEndian,23,4);return offset+4;}Buffer.prototype.writeFloatLE=function writeFloatLE(value,offset,noAssert){return writeFloat(this,value,offset,true,noAssert);};Buffer.prototype.writeFloatBE=function writeFloatBE(value,offset,noAssert){return writeFloat(this,value,offset,false,noAssert);};function writeDouble(buf,value,offset,littleEndian,noAssert){value=+value;offset=offset>>>0;if(!noAssert){checkIEEE754(buf,value,offset,8,1.7976931348623157E+308,-1.7976931348623157E+308);}ieee754.write(buf,value,offset,littleEndian,52,8);return offset+8;}Buffer.prototype.writeDoubleLE=function writeDoubleLE(value,offset,noAssert){return writeDouble(this,value,offset,true,noAssert);};Buffer.prototype.writeDoubleBE=function writeDoubleBE(value,offset,noAssert){return writeDouble(this,value,offset,false,noAssert);};// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) -Buffer.prototype.copy=function copy(target,targetStart,start,end){if(!Buffer.isBuffer(target))throw new TypeError('argument should be a Buffer');if(!start)start=0;if(!end&&end!==0)end=this.length;if(targetStart>=target.length)targetStart=target.length;if(!targetStart)targetStart=0;if(end>0&&end=this.length)throw new RangeError('Index out of range');if(end<0)throw new RangeError('sourceEnd out of bounds');// Are we oob? -if(end>this.length)end=this.length;if(target.length-targetStart=0;--i){target[i+targetStart]=this[i+start];}}else{Uint8Array.prototype.set.call(target,this.subarray(start,end),targetStart);}return len;};// Usage: -// buffer.fill(number[, offset[, end]]) -// buffer.fill(buffer[, offset[, end]]) -// buffer.fill(string[, offset[, end]][, encoding]) -Buffer.prototype.fill=function fill(val,start,end,encoding){// Handle string cases: -if(typeof val==='string'){if(typeof start==='string'){encoding=start;start=0;end=this.length;}else if(typeof end==='string'){encoding=end;end=this.length;}if(encoding!==undefined&&typeof encoding!=='string'){throw new TypeError('encoding must be a string');}if(typeof encoding==='string'&&!Buffer.isEncoding(encoding)){throw new TypeError('Unknown encoding: '+encoding);}if(val.length===1){var code=val.charCodeAt(0);if(encoding==='utf8'&&code<128||encoding==='latin1'){// Fast path: If `val` fits into a single byte, use that numeric value. -val=code;}}}else if(typeof val==='number'){val=val&255;}else if(typeof val==='boolean'){val=Number(val);}// Invalid ranges are not set to a default, so can range check early. -if(start<0||this.length>>0;end=end===undefined?this.length:end>>>0;if(!val)val=0;var i;if(typeof val==='number'){for(i=start;i0xD7FF&&codePoint<0xE000){// last char was a lead -if(!leadSurrogate){// no lead yet -if(codePoint>0xDBFF){// unexpected trail -if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);continue;}else if(i+1===length){// unpaired lead -if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);continue;}// valid lead -leadSurrogate=codePoint;continue;}// 2 leads in a row -if(codePoint<0xDC00){if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);leadSurrogate=codePoint;continue;}// valid surrogate pair -codePoint=(leadSurrogate-0xD800<<10|codePoint-0xDC00)+0x10000;}else if(leadSurrogate){// valid bmp char, but last char was a lead -if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD);}leadSurrogate=null;// encode utf8 -if(codePoint<0x80){if((units-=1)<0)break;bytes.push(codePoint);}else if(codePoint<0x800){if((units-=2)<0)break;bytes.push(codePoint>>0x6|0xC0,codePoint&0x3F|0x80);}else if(codePoint<0x10000){if((units-=3)<0)break;bytes.push(codePoint>>0xC|0xE0,codePoint>>0x6&0x3F|0x80,codePoint&0x3F|0x80);}else if(codePoint<0x110000){if((units-=4)<0)break;bytes.push(codePoint>>0x12|0xF0,codePoint>>0xC&0x3F|0x80,codePoint>>0x6&0x3F|0x80,codePoint&0x3F|0x80);}else{throw new Error('Invalid code point');}}return bytes;}function asciiToBytes(str){var byteArray=[];for(var i=0;i>8;lo=c%256;byteArray.push(lo);byteArray.push(hi);}return byteArray;}function base64ToBytes(str){return base64.toByteArray(base64clean(str));}function blitBuffer(src,dst,offset,length){for(var i=0;i=dst.length||i>=src.length)break;dst[i+offset]=src[i];}return i;}// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass -// the `instanceof` check but they should be treated as of that type. -// See: https://github.com/feross/buffer/issues/166 -function isInstance(obj,type){return obj instanceof type||obj!=null&&obj.constructor!=null&&obj.constructor.name!=null&&obj.constructor.name===type.name;}function numberIsNaN(obj){// For IE11 support -return obj!==obj;// eslint-disable-line no-self-compare -}// Create lookup table for `toString('hex')` -// See: https://github.com/feross/buffer/issues/219 -var hexSliceLookupTable=function(){var alphabet='0123456789abcdef';var table=new Array(256);for(var i=0;i<16;++i){var i16=i*16;for(var j=0;j<16;++j){table[i16+j]=alphabet[i]+alphabet[j];}}return table;}();}).call(this,require("buffer").Buffer);},{"base64-js":2,"buffer":4,"ieee754":41}],5:[function(require,module,exports){module.exports={"elementNames":{"altglyph":"altGlyph","altglyphdef":"altGlyphDef","altglyphitem":"altGlyphItem","animatecolor":"animateColor","animatemotion":"animateMotion","animatetransform":"animateTransform","clippath":"clipPath","feblend":"feBlend","fecolormatrix":"feColorMatrix","fecomponenttransfer":"feComponentTransfer","fecomposite":"feComposite","feconvolvematrix":"feConvolveMatrix","fediffuselighting":"feDiffuseLighting","fedisplacementmap":"feDisplacementMap","fedistantlight":"feDistantLight","fedropshadow":"feDropShadow","feflood":"feFlood","fefunca":"feFuncA","fefuncb":"feFuncB","fefuncg":"feFuncG","fefuncr":"feFuncR","fegaussianblur":"feGaussianBlur","feimage":"feImage","femerge":"feMerge","femergenode":"feMergeNode","femorphology":"feMorphology","feoffset":"feOffset","fepointlight":"fePointLight","fespecularlighting":"feSpecularLighting","fespotlight":"feSpotLight","fetile":"feTile","feturbulence":"feTurbulence","foreignobject":"foreignObject","glyphref":"glyphRef","lineargradient":"linearGradient","radialgradient":"radialGradient","textpath":"textPath"},"attributeNames":{"definitionurl":"definitionURL","attributename":"attributeName","attributetype":"attributeType","basefrequency":"baseFrequency","baseprofile":"baseProfile","calcmode":"calcMode","clippathunits":"clipPathUnits","diffuseconstant":"diffuseConstant","edgemode":"edgeMode","filterunits":"filterUnits","glyphref":"glyphRef","gradienttransform":"gradientTransform","gradientunits":"gradientUnits","kernelmatrix":"kernelMatrix","kernelunitlength":"kernelUnitLength","keypoints":"keyPoints","keysplines":"keySplines","keytimes":"keyTimes","lengthadjust":"lengthAdjust","limitingconeangle":"limitingConeAngle","markerheight":"markerHeight","markerunits":"markerUnits","markerwidth":"markerWidth","maskcontentunits":"maskContentUnits","maskunits":"maskUnits","numoctaves":"numOctaves","pathlength":"pathLength","patterncontentunits":"patternContentUnits","patterntransform":"patternTransform","patternunits":"patternUnits","pointsatx":"pointsAtX","pointsaty":"pointsAtY","pointsatz":"pointsAtZ","preservealpha":"preserveAlpha","preserveaspectratio":"preserveAspectRatio","primitiveunits":"primitiveUnits","refx":"refX","refy":"refY","repeatcount":"repeatCount","repeatdur":"repeatDur","requiredextensions":"requiredExtensions","requiredfeatures":"requiredFeatures","specularconstant":"specularConstant","specularexponent":"specularExponent","spreadmethod":"spreadMethod","startoffset":"startOffset","stddeviation":"stdDeviation","stitchtiles":"stitchTiles","surfacescale":"surfaceScale","systemlanguage":"systemLanguage","tablevalues":"tableValues","targetx":"targetX","targety":"targetY","textlength":"textLength","viewbox":"viewBox","viewtarget":"viewTarget","xchannelselector":"xChannelSelector","ychannelselector":"yChannelSelector","zoomandpan":"zoomAndPan"}};},{}],6:[function(require,module,exports){/* - Module dependencies -*/var ElementType=require('domelementtype');var entities=require('entities');/* mixed-case SVG and MathML tags & attributes - recognized by the HTML parser, see - https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign -*/var foreignNames=require('./foreignNames.json');foreignNames.elementNames.__proto__=null;/* use as a simple dictionary */foreignNames.attributeNames.__proto__=null;var unencodedElements={__proto__:null,style:true,script:true,xmp:true,iframe:true,noembed:true,noframes:true,plaintext:true,noscript:true};/* - Format attributes -*/function formatAttrs(attributes,opts){if(!attributes)return;var output='';var value;// Loop through the attributes -for(var key in attributes){value=attributes[key];if(output){output+=' ';}if(opts.xmlMode==='foreign'){/* fix up mixed-case attribute names */key=foreignNames.attributeNames[key]||key;}output+=key;if(value!==null&&value!==''||opts.xmlMode){output+='="'+(opts.decodeEntities?entities.encodeXML(value):value.replace(/\"/g,'"'))+'"';}}return output;}/* - Self-enclosing tags (stolen from node-htmlparser) -*/var singleTag={__proto__:null,area:true,base:true,basefont:true,br:true,col:true,command:true,embed:true,frame:true,hr:true,img:true,input:true,isindex:true,keygen:true,link:true,meta:true,param:true,source:true,track:true,wbr:true};var render=module.exports=function(dom,opts){if(!Array.isArray(dom)&&!dom.cheerio)dom=[dom];opts=opts||{};var output='';for(var i=0;i=0)opts=Object.assign({},opts,{xmlMode:false});}if(!opts.xmlMode&&['svg','math'].indexOf(elem.name)>=0){opts=Object.assign({},opts,{xmlMode:'foreign'});}var tag='<'+elem.name;var attribs=formatAttrs(elem.attribs,opts);if(attribs){tag+=' '+attribs;}if(opts.xmlMode&&(!elem.children||elem.children.length===0)){tag+='/>';}else{tag+='>';if(elem.children){tag+=render(elem.children,opts);}if(!singleTag[elem.name]||opts.xmlMode){tag+='';}}return tag;}function renderDirective(elem){return'<'+elem.data+'>';}function renderText(elem,opts){var data=elem.data||'';// if entities weren't decoded, no need to encode them back -if(opts.decodeEntities&&!(elem.parent&&elem.parent.name in unencodedElements)){data=entities.encodeXML(data);}return data;}function renderCdata(elem){return'';}function renderComment(elem){return'';}},{"./foreignNames.json":5,"domelementtype":7,"entities":11}],7:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});/** - * Tests whether an element is a tag or not. - * - * @param elem Element to test - */function isTag(elem){return elem.type==="tag"/* Tag */||elem.type==="script"/* Script */||elem.type==="style"/* Style */;}exports.isTag=isTag;// Exports for backwards compatibility -exports.Text="text"/* Text */;//Text -exports.Directive="directive"/* Directive */;// -exports.Comment="comment"/* Comment */;// -exports.Script="script"/* Script */;//