diff --git a/Core/source/core/pgp-msg.ts b/Core/source/core/pgp-msg.ts index 7919c62ed..55fa03807 100644 --- a/Core/source/core/pgp-msg.ts +++ b/Core/source/core/pgp-msg.ts @@ -11,7 +11,6 @@ import { Catch } from '../platform/catch'; import { FcAttLinkData } from './att'; import { MsgBlockParser } from './msg-block-parser'; import { PgpArmor } from './pgp-armor'; -import { PgpHash } from './pgp-hash'; import { Store } from '../platform/store'; import { openpgp } from './pgp'; @@ -222,8 +221,8 @@ export class PgpMsg { public static encrypt: PgpMsgMethod.Encrypt = async ({ pubkeys, signingPrv, pwd, data, filename, armor, date }) => { const message = openpgp.message.fromBinary(data, filename, date); + const options: OpenPGP.EncryptOptions = { armor, message, date }; - let usedChallenge = false; if (pubkeys) { options.publicKeys = []; for (const armoredPubkey of pubkeys) { @@ -232,10 +231,9 @@ export class PgpMsg { } } if (pwd) { - options.passwords = [await PgpHash.challengeAnswer(pwd)]; - usedChallenge = true; + options.passwords = [pwd]; } - if (!pubkeys && !usedChallenge) { + if (!pubkeys && !pwd) { throw new Error('no-pubkeys-no-challenge'); } if (signingPrv && typeof signingPrv.isPrivate !== 'undefined' && signingPrv.isPrivate()) { // tslint:disable-line:no-unbound-method - only testing if exists diff --git a/Core/source/mobile-interface/endpoints.ts b/Core/source/mobile-interface/endpoints.ts index 3cd2dfcb0..e3290847a 100644 --- a/Core/source/mobile-interface/endpoints.ts +++ b/Core/source/mobile-interface/endpoints.ts @@ -30,12 +30,6 @@ export class Endpoints { return fmtRes({ app_version: VERSION }); } - public encryptMsg = async (uncheckedReq: any, data: Buffers): Promise => { - const req = ValidateInput.encryptMsg(uncheckedReq); - const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, data: Buf.concat(data), 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); @@ -57,13 +51,14 @@ 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 || []) { 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, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult; return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': encrypted.data }, mimeHeaders, encryptedAtts))); @@ -72,6 +67,12 @@ export class Endpoints { } } + public encryptMsg = async (uncheckedReq: any, data: Buffers): Promise => { + const req = ValidateInput.encryptMsg(uncheckedReq); + const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, pwd: req.msgPwd, data: Buf.concat(data), armor: true }) as OpenPGP.EncryptArmorResult; + return fmtRes({}, Buf.fromUtfStr(encrypted.data)); + } + public encryptFile = async (uncheckedReq: any, data: Buffers): Promise => { const req = ValidateInput.encryptFile(uncheckedReq); const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, data: Buf.concat(data), filename: req.name, armor: false }) as OpenPGP.EncryptBinaryResult; @@ -304,4 +305,3 @@ export const getSigningPrv = async (req: NodeRequest.composeEmailEncrypted): Pro throw new Error(`Fail to decrypt signing key`); } } - diff --git a/Core/source/mobile-interface/validate-input.ts b/Core/source/mobile-interface/validate-input.ts index 4e2b27545..87a8f8407 100644 --- a/Core/source/mobile-interface/validate-input.ts +++ b/Core/source/mobile-interface/validate-input.ts @@ -9,13 +9,13 @@ 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 }; 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 encryptMsg = { pubKeys: string[], msgPwd?: string }; 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 }; @@ -37,14 +37,14 @@ export class ValidateInput { } public static encryptMsg = (v: any): NodeRequest.encryptMsg => { - if (isObj(v) && hasProp(v, 'pubKeys', 'string[]')) { + if (isObj(v) && hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'msgPwd', 'string?')) { return v as NodeRequest.encryptMsg; } throw new Error('Wrong request structure for NodeRequest.encryptMsg'); } 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[]?')) { diff --git a/Core/source/test.ts b/Core/source/test.ts index d1bce9582..ac1afaf0f 100644 --- a/Core/source/test.ts +++ b/Core/source/test.ts @@ -57,6 +57,18 @@ for (const keypairName of allKeypairNames.filter(name => name != 'expired' && na }); } +ava.default(`encryptMsg -> parseDecryptMsg (with password)`, async t => { + const content = 'hello\nwrld'; + const msgPwd = '123'; + const { data: encryptedMsg, json: encryptJson } = parseResponse(await endpoints.encryptMsg({ pubKeys: [], msgPwd: msgPwd }, [Buffer.from(content, 'utf8')])); + expectEmptyJson(encryptJson); + expectData(encryptedMsg, 'armoredMsg'); + const { data: blocks, json: decryptJson } = parseResponse(await endpoints.parseDecryptMsg({ keys: [], msgPwd: msgPwd }, [encryptedMsg])); + expect(decryptJson).to.deep.equal({ text: content, replyType: 'encrypted' }); + expectData(blocks, 'msgBlocks', [{ rendered: true, frameColor: 'green', htmlContent: content.replace(/\n/g, '
') }]); + t.pass(); +}); + ava.default('composeEmail format:plain -> parseDecryptMsg', async t => { const content = 'hello\nwrld'; const { keys } = getKeypairs('rsa1'); diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 1457e4199..f31bb4643 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 */; }; @@ -345,7 +345,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 */; }; @@ -448,7 +448,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 = ""; }; @@ -470,7 +470,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 = ""; }; @@ -956,7 +956,7 @@ isa = PBXGroup; children = ( 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */, - 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */, + 21F836B52652A26B00B2448C /* DataExtensions+ZBase32Encoding.swift */, ); path = Data; sourceTree = ""; @@ -1879,7 +1879,7 @@ D2D27B78248A8694007346FA /* BigIntExtensions.swift */, E26D5E20275AA417007B8802 /* BundleExtensions.swift */, 21C7DEFB26669A3700C44800 /* CalendarExtensions.swift */, - 32DCA38E87F2B7196E0E1F1F /* CodableExntensions.swift */, + 32DCA38E87F2B7196E0E1F1F /* CodableExtensions.swift */, D2531F452402C62D007E5198 /* CollectionExtensions.swift */, 9F0C3C2723194E8500299985 /* CommonExtensions.swift */, 9F56BD3723438C7000A7371A /* DateFormattingExtensions.swift */, @@ -2262,7 +2262,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = "FlowCrypt Limited"; TargetAttributes = { 9F2AC5C5267BE99E00F6149B = { @@ -2845,7 +2845,7 @@ 9F67998C277B3E4000AFE5BE /* BundleExtensions.swift in Sources */, 9FD5052B278B2C8600FAA82F /* UIPopoverPresentationControllerExtensions.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 */, @@ -2862,7 +2862,7 @@ 9FD5052927889D8200FAA82F /* UIAlertControllerExtensions.swift in Sources */, 9F8076D927762515008E5874 /* BigIntExtensions.swift in Sources */, 9FBD69EC27775086002FC602 /* UIApplicationExtensions.swift in Sources */, - D2CDC3D42402D50A002B045F /* CodableExntensions.swift in Sources */, + D2CDC3D42402D50A002B045F /* CodableExtensions.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 @@ CoreRes.DecryptFile { + func decryptFile(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?) async throws -> CoreRes.DecryptFile { struct DecryptFileRaw: Decodable { let decryptSuccess: DecryptSuccess? let decryptErr: DecryptErr? @@ -108,7 +108,7 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { throw AppErr.unexpected("decryptFile: both decryptErr and decryptSuccess were nil") } - public func encryptFile(pubKeys: [String]?, fileData: Data, name: String) async throws -> CoreRes.EncryptFile { + func encrypt(file: Data, name: String, pubKeys: [String]?) async throws -> Data { let json: [String: Any?]? = [ "pubKeys": pubKeys, "name": name @@ -117,9 +117,25 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { let encrypted = try await call( "encryptFile", jsonDict: json, - data: fileData + data: file ) - return CoreRes.EncryptFile(encryptedFile: encrypted.data) + return encrypted.data + } + + // MARK: - Messages + func encrypt(data: Data, pubKeys: [String]?, password: String?) async throws -> Data { + let jsonDict: [String: Any?] = [ + "pubKeys": pubKeys, + "msgPwd": password + ] + + let encryptedMessage = try await call( + "encryptMsg", + jsonDict: jsonDict, + data: data + ) + + return encryptedMessage.data } func parseDecryptMsg( @@ -139,11 +155,13 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { "msgPwd": msgPwd, "verificationPubkeys": verificationPubKeys ] + let parsed = try await call( "parseDecryptMsg", jsonDict: json, data: encrypted ) + let meta = try parsed.json.decodeJson(as: ParseDecryptMsgRaw.self) let blocks = parsed.data @@ -164,16 +182,9 @@ actor Core: KeyDecrypter, KeyParser, CoreComposeMessageType { } func composeEmail(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail { - let signingPrv = msg.signingPrv.map { value in - [ - "private": value.`private`, - "longid": value.longid, - "passphrase": value.passphrase - ] - } - let r = try await call("composeEmail", jsonDict: [ "text": msg.text, + "html": msg.html, "to": msg.to, "cc": msg.cc, "bcc": msg.bcc, @@ -183,8 +194,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": msg.signingPrv?.jsonDict ], data: nil) return CoreRes.ComposeEmail(mimeEncoded: r.data) } diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index ee61b0bfa..ff00cb3df 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -51,10 +51,6 @@ struct CoreRes { let data: Data } } - - struct EncryptFile: Decodable { - let encryptedFile: Data - } struct Error: Decodable { struct ErrorWithOptionalStack: Decodable { @@ -126,6 +122,7 @@ struct SendableMsg: Equatable { } let text: String + let html: String? let to: [String] let cc: [String] let bcc: [String] @@ -138,6 +135,30 @@ struct SendableMsg: Equatable { let password: String? } +extension SendableMsg { + func copy(body: SendableMsgBody, atts: [Attachment], pubKeys: [String]?) -> SendableMsg { + SendableMsg( + text: body.text, + html: body.html, + to: self.to, + cc: self.cc, + bcc: self.bcc, + from: self.from, + subject: self.subject, + replyToMimeMsg: self.replyToMimeMsg, + atts: atts, + pubKeys: pubKeys, + signingPrv: self.signingPrv, + password: self.password + ) + } +} + +struct SendableMsgBody { + let text: String + let html: String? +} + struct DecryptErr: Decodable { let error: Error let longids: Longids diff --git a/FlowCrypt/Core/Models/PrvKeyInfo.swift b/FlowCrypt/Core/Models/PrvKeyInfo.swift index 311aa9242..d3b578ede 100644 --- a/FlowCrypt/Core/Models/PrvKeyInfo.swift +++ b/FlowCrypt/Core/Models/PrvKeyInfo.swift @@ -22,6 +22,12 @@ extension PrvKeyInfo { self.passphrase = keypair.passphrase ?? passphrase self.fingerprints = keypair.allFingerprints } +} + +extension PrvKeyInfo { + var jsonDict: [String: String?] { + ["private": `private`, "longid": longid, "passphrase": passphrase] + } func copy(with passphrase: String) -> PrvKeyInfo { PrvKeyInfo(private: self.private, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 669da7303..5270dfe7f 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -6,19 +6,18 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import MailCore import FlowCryptCommon protocol EnterpriseServerApiType { func getActiveFesUrl(for email: String) async throws -> String? func getClientConfiguration(for email: String) async throws -> RawClientConfiguration func getReplyToken(for email: String) async throws -> String - func upload(message: Data, details: MessageUploadDetails) async throws -> String + func upload(message: Data, details: MessageUploadDetails, progressHandler: ((Float) -> Void)?) async throws -> String } /// server run by individual enterprise customers, serves client configuration /// https://flowcrypt.com/docs/technical/enterprise/email-deployment-overview.html -class EnterpriseServerApi: EnterpriseServerApiType { +class EnterpriseServerApi: NSObject, EnterpriseServerApiType { static let publicEmailProviderDomains = ["gmail.com", "googlemail.com", "outlook.com"] @@ -56,6 +55,8 @@ class EnterpriseServerApi: EnterpriseServerApiType { return "https://fes.\(emailDomain)" // live } + private var messageUploadProgressHandler: ((Float) -> Void)? + func getActiveFesUrl(for email: String) async throws -> String? { do { guard let userDomain = email.emailParts?.domain, @@ -123,7 +124,9 @@ class EnterpriseServerApi: EnterpriseServerApiType { return response.replyToken } - func upload(message: Data, details: MessageUploadDetails) async throws -> String { + func upload(message: Data, details: MessageUploadDetails, progressHandler: ((Float) -> Void)?) async throws -> String { + self.messageUploadProgressHandler = progressHandler + let detailsData = try details.toJsonData() let detailsDataItem = MultipartDataItem( @@ -149,7 +152,8 @@ class EnterpriseServerApi: EnterpriseServerApiType { url: "/api/v1/message", headers: [contentTypeHeader], method: .post, - body: request.httpBody as Data + body: request.httpBody as Data, + delegate: self ) return response.url @@ -171,7 +175,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { guard let service = responseDictionary["service"] as? String else { return false } return service == "enterprise-server" } - + private func shouldTolerateWhenCallingOpportunistically(_ error: Error) async -> Bool { if Bundle.isEnterprise() { return false // FlowCrypt Enterprise Server (FES) required on enterprise bundle @@ -192,7 +196,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 @@ -215,16 +219,18 @@ class EnterpriseServerApi: EnterpriseServerApiType { headers: [URLHeader] = [], method: HTTPMethod = .post, body: Data? = nil, - withAuthorization: Bool = true + withAuthorization: Bool = true, + delegate: URLSessionTaskDelegate? = nil ) async throws -> T { guard let fesUrl = try await getActiveFesUrl(for: email) else { 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) } @@ -233,7 +239,8 @@ class EnterpriseServerApi: EnterpriseServerApiType { url: "\(fesUrl)\(url)", method: method, body: body, - headers: headers + headers: headers, + delegate: delegate ) let safeResponse = try await ApiCall.call(request) @@ -244,3 +251,15 @@ class EnterpriseServerApi: EnterpriseServerApiType { return data } } + +extension EnterpriseServerApi: URLSessionTaskDelegate { + func urlSession( + _ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64 + ) { + messageUploadProgressHandler?(Float(task.progress.fractionCompleted)) + } +} diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift index c2613a638..35e3bee14 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 { @@ -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/ApiCall.swift b/FlowCrypt/Functionality/Services/ApiCall.swift index aaa17455f..d53d93b14 100644 --- a/FlowCrypt/Functionality/Services/ApiCall.swift +++ b/FlowCrypt/Functionality/Services/ApiCall.swift @@ -20,6 +20,7 @@ extension ApiCall { var headers: [URLHeader] = [] var timeout: TimeInterval = 60.0 var tolerateStatus: [Int]? + weak var delegate: URLSessionTaskDelegate? } } @@ -44,7 +45,8 @@ extension ApiCall { do { let result = try await URLSession.shared.call( urlRequest, - tolerateStatus: request.tolerateStatus + tolerateStatus: request.tolerateStatus, + delegate: request.delegate ) return result } catch { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index fcb9a56ae..e193c638a 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,7 +64,8 @@ extension BackupService: BackupServiceType { atts: attachments, pubKeys: nil, signingPrv: nil, - password: nil) + password: nil + ) let t = try await core.composeEmail(msg: message, fmt: .plain) try await messageSender.sendMail( diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index b324aaff1..b36bca7e4 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 encrypt(data: Data, pubKeys: [String]?, password: String?) async throws -> Data + func encrypt(file: Data, name: String, pubKeys: [String]?) async throws -> Data } 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") } @@ -50,6 +62,7 @@ final class ComposeMessageService { self.onStateChanged = completion } + // MARK: - Validation func validateAndProduceSendableMsg( input: ComposeMessageInput, contextToSend: ComposeMessageContext, @@ -103,6 +116,7 @@ final class ComposeMessageService { return SendableMsg( text: text, + html: nil, 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 { @@ -155,12 +163,13 @@ final class ComposeMessageService { return recipients.flatMap(\.activePubKeys).map(\.armored) } + // MARK: - Drafts private var draft: GTLRGmail_Draft? func encryptAndSaveDraft(message: SendableMsg, threadId: String?) async throws { 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,15 +181,29 @@ 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 + + let hasPassword = (message.password ?? "").isNotEmpty + let composedEmail: CoreRes.ComposeEmail + + if hasPassword { + composedEmail = try await composePasswordMessage(from: message) + } else { + composedEmail = try await core.composeEmail( + msg: message, + fmt: .encryptInline + ) + } + + let input = MessageGatewayInput( + mime: composedEmail.mimeEncoded, + threadId: threadId ) try await messageGateway.sendMail( - input: MessageGatewayInput(mime: r.mimeEncoded, threadId: threadId), + input: input, progressHandler: { [weak self] progress in - self?.onStateChanged?(.progressChanged(progress)) + let progressToShow = hasPassword ? 0.5 + progress / 2 : progress + self?.onStateChanged?(.progressChanged(progressToShow)) } ) @@ -188,9 +211,131 @@ final class ComposeMessageService { if let draftId = draft?.identifier { await draftGateway?.deleteDraft(with: draftId) } + onStateChanged?(.messageSent) } catch { throw ComposeMessageError.gatewayError(error) } } } + +// MARK: - Message password +extension ComposeMessageService { + private func composePasswordMessage(from message: SendableMsg) async throws -> CoreRes.ComposeEmail { + let messageUrl = try await prepareAndUploadPwdEncryptedMsg(message: message) + let messageBody = createMessageBodyWithPasswordLink(sender: message.from, url: messageUrl) + + let encryptedBodyAttachment = try await encryptBodyWithoutAttachments(message: message) + let encryptedAttachments = try await encryptAttachments(message: message) + + let sendableMsg = message.copy( + body: messageBody, + atts: [encryptedBodyAttachment] + encryptedAttachments, + pubKeys: nil + ) + + return try await core.composeEmail(msg: sendableMsg, fmt: .plain) + } + + private func encryptBodyWithoutAttachments(message: SendableMsg) async throws -> SendableMsg.Attachment { + let pubEncryptedNoAttachments = try await core.encrypt( + data: message.text.data(), + pubKeys: message.pubKeys, + password: nil + ) + + return SendableMsg.Attachment( + name: "encrypted.asc", + type: "application/pgp-encrypted", + base64: pubEncryptedNoAttachments.base64EncodedString() + ) + } + + private func encryptAttachments(message: SendableMsg) async throws -> [SendableMsg.Attachment] { + var encryptedAttachments: [SendableMsg.Attachment] = [] + + for attachment in message.atts { + guard let data = Data(base64Encoded: attachment.base64) else { continue } + + let encryptedFile = try await core.encrypt( + file: data, + name: attachment.name, + pubKeys: message.pubKeys + ) + let encryptedAttachment = SendableMsg.Attachment( + name: "\(attachment.name).pgp", + type: "application/pgp-encrypted", + base64: encryptedFile.base64EncodedString() + ) + encryptedAttachments.append(encryptedAttachment) + } + + return encryptedAttachments + } + + private func prepareAndUploadPwdEncryptedMsg(message: SendableMsg) async throws -> String { + let replyToken = try await enterpriseServer.getReplyToken(for: message.from) + + let bodyWithReplyToken = try getPwdMsgBodyWithReplyToken( + message: message, + replyToken: replyToken + ) + let msgWithReplyToken = message.copy( + body: bodyWithReplyToken, + atts: message.atts, + pubKeys: nil + ) + let pgpMimeWithAttachments = try await core.composeEmail( + msg: msgWithReplyToken, + fmt: .plain + ).mimeEncoded + + let pwdEncryptedWithAttachments = try await core.encrypt( + data: pgpMimeWithAttachments, + pubKeys: [], + password: message.password + ) + let details = MessageUploadDetails(from: msgWithReplyToken, replyToken: replyToken) + + return try await enterpriseServer.upload( + message: pwdEncryptedWithAttachments, + details: details, + progressHandler: { [weak self] progress in + self?.onStateChanged?(.progressChanged(progress / 2)) + } + ) + } + + private func getPwdMsgBodyWithReplyToken(message: SendableMsg, replyToken: String) throws -> SendableMsgBody { + let replyInfoDiv = try createReplyInfoDiv(for: message, replyToken: replyToken) + + let text = [message.text, "/n/n", replyInfoDiv].joined() + let html = [message.text, "

", replyInfoDiv].joined() + + return SendableMsgBody(text: text, html: html) + } + + private func createReplyInfoDiv(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 "
" + } + + private func createMessageBodyWithPasswordLink(sender: String, url: String) -> SendableMsgBody { + let text = "\(sender) has sent you a password-encrypted email.\n\nTo open message copy and paste the following link: \(url)" + + let aStyle = "padding: 2px 6px; background: #2199e8; color: #fff; display: inline-block; text-decoration: none;" + let html = """ + \(sender) has sent you a password-encrypted email Click here to Open Message +

+ Alternatively copy and paste the following link: \(url) + """ + + return SendableMsgBody(text: text, html: html) + } +} 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 1d25f5ab4..fe25d9b17 100644 --- a/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt +++ b/FlowCrypt/Resources/flowcrypt-ios-prod.js.txt @@ -303,7 +303,7 @@ const hostRsaDecryption = async (ASN1, BN, c_encrypted, n, e, d, p, q) => { /******/ /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 232); +/******/ return __webpack_require__(__webpack_require__.s = 231); /******/ }) /************************************************************************/ /******/ ([ @@ -2804,16 +2804,15 @@ process.umask = function() { return 0; }; /* 228 */, /* 229 */, /* 230 */, -/* 231 */, -/* 232 */ +/* 231 */ /***/ (function(module, exports, __webpack_require__) { -const htmlparser = __webpack_require__(233); -const escapeStringRegexp = __webpack_require__(258); -const { isPlainObject } = __webpack_require__(259); -const deepmerge = __webpack_require__(260); -const parseSrcset = __webpack_require__(261); -const { parse: postcssParse } = __webpack_require__(262); +const htmlparser = __webpack_require__(232); +const escapeStringRegexp = __webpack_require__(257); +const { isPlainObject } = __webpack_require__(258); +const deepmerge = __webpack_require__(259); +const parseSrcset = __webpack_require__(260); +const { parse: postcssParse } = __webpack_require__(261); // Tags that can conceivably represent stand-alone media. const mediaTags = [ 'img', 'audio', 'video', 'picture', 'svg', @@ -3628,7 +3627,7 @@ sanitizeHtml.simpleTransform = function(newTagName, newAttribs, merge) { /***/ }), -/* 233 */ +/* 232 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3660,9 +3659,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RssHandler = exports.DefaultHandler = exports.DomUtils = exports.ElementType = exports.Tokenizer = exports.createDomStream = exports.parseDOM = exports.parseDocument = exports.DomHandler = exports.Parser = void 0; -var Parser_1 = __webpack_require__(234); +var Parser_1 = __webpack_require__(233); Object.defineProperty(exports, "Parser", { enumerable: true, get: function () { return Parser_1.Parser; } }); -var domhandler_1 = __webpack_require__(241); +var domhandler_1 = __webpack_require__(240); Object.defineProperty(exports, "DomHandler", { enumerable: true, get: function () { return domhandler_1.DomHandler; } }); Object.defineProperty(exports, "DefaultHandler", { enumerable: true, get: function () { return domhandler_1.DomHandler; } }); // Helper methods @@ -3704,22 +3703,22 @@ function createDomStream(cb, options, elementCb) { return new Parser_1.Parser(handler, options); } exports.createDomStream = createDomStream; -var Tokenizer_1 = __webpack_require__(235); +var Tokenizer_1 = __webpack_require__(234); Object.defineProperty(exports, "Tokenizer", { enumerable: true, get: function () { return __importDefault(Tokenizer_1).default; } }); -var ElementType = __importStar(__webpack_require__(242)); +var ElementType = __importStar(__webpack_require__(241)); exports.ElementType = ElementType; /* * All of the following exports exist for backwards-compatibility. * They should probably be removed eventually. */ -__exportStar(__webpack_require__(244), exports); -exports.DomUtils = __importStar(__webpack_require__(245)); -var FeedHandler_1 = __webpack_require__(244); +__exportStar(__webpack_require__(243), exports); +exports.DomUtils = __importStar(__webpack_require__(244)); +var FeedHandler_1 = __webpack_require__(243); Object.defineProperty(exports, "RssHandler", { enumerable: true, get: function () { return FeedHandler_1.FeedHandler; } }); /***/ }), -/* 234 */ +/* 233 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3729,7 +3728,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; -var Tokenizer_1 = __importDefault(__webpack_require__(235)); +var Tokenizer_1 = __importDefault(__webpack_require__(234)); var formTags = new Set([ "input", "option", @@ -4107,7 +4106,7 @@ exports.Parser = Parser; /***/ }), -/* 235 */ +/* 234 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4116,10 +4115,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -var decode_codepoint_1 = __importDefault(__webpack_require__(236)); -var entities_json_1 = __importDefault(__webpack_require__(238)); -var legacy_json_1 = __importDefault(__webpack_require__(239)); -var xml_json_1 = __importDefault(__webpack_require__(240)); +var decode_codepoint_1 = __importDefault(__webpack_require__(235)); +var entities_json_1 = __importDefault(__webpack_require__(237)); +var legacy_json_1 = __importDefault(__webpack_require__(238)); +var xml_json_1 = __importDefault(__webpack_require__(239)); function whitespace(c) { return c === " " || c === "\n" || c === "\t" || c === "\f" || c === "\r"; } @@ -5023,7 +5022,7 @@ exports.default = Tokenizer; /***/ }), -/* 236 */ +/* 235 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5032,7 +5031,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -var decode_json_1 = __importDefault(__webpack_require__(237)); +var decode_json_1 = __importDefault(__webpack_require__(236)); // Adapted from https://github.com/mathiasbynens/he/blob/master/src/he.js#L94-L119 var fromCodePoint = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -5060,31 +5059,31 @@ exports.default = decodeCodePoint; /***/ }), -/* 237 */ +/* 236 */ /***/ (function(module) { module.exports = JSON.parse("{\"0\":65533,\"128\":8364,\"130\":8218,\"131\":402,\"132\":8222,\"133\":8230,\"134\":8224,\"135\":8225,\"136\":710,\"137\":8240,\"138\":352,\"139\":8249,\"140\":338,\"142\":381,\"145\":8216,\"146\":8217,\"147\":8220,\"148\":8221,\"149\":8226,\"150\":8211,\"151\":8212,\"152\":732,\"153\":8482,\"154\":353,\"155\":8250,\"156\":339,\"158\":382,\"159\":376}"); /***/ }), -/* 238 */ +/* 237 */ /***/ (function(module) { module.exports = JSON.parse("{\"Aacute\":\"Á\",\"aacute\":\"á\",\"Abreve\":\"Ă\",\"abreve\":\"ă\",\"ac\":\"∾\",\"acd\":\"∿\",\"acE\":\"∾̳\",\"Acirc\":\"Â\",\"acirc\":\"â\",\"acute\":\"´\",\"Acy\":\"А\",\"acy\":\"а\",\"AElig\":\"Æ\",\"aelig\":\"æ\",\"af\":\"⁡\",\"Afr\":\"𝔄\",\"afr\":\"𝔞\",\"Agrave\":\"À\",\"agrave\":\"à\",\"alefsym\":\"ℵ\",\"aleph\":\"ℵ\",\"Alpha\":\"Α\",\"alpha\":\"α\",\"Amacr\":\"Ā\",\"amacr\":\"ā\",\"amalg\":\"⨿\",\"amp\":\"&\",\"AMP\":\"&\",\"andand\":\"⩕\",\"And\":\"⩓\",\"and\":\"∧\",\"andd\":\"⩜\",\"andslope\":\"⩘\",\"andv\":\"⩚\",\"ang\":\"∠\",\"ange\":\"⦤\",\"angle\":\"∠\",\"angmsdaa\":\"⦨\",\"angmsdab\":\"⦩\",\"angmsdac\":\"⦪\",\"angmsdad\":\"⦫\",\"angmsdae\":\"⦬\",\"angmsdaf\":\"⦭\",\"angmsdag\":\"⦮\",\"angmsdah\":\"⦯\",\"angmsd\":\"∡\",\"angrt\":\"∟\",\"angrtvb\":\"⊾\",\"angrtvbd\":\"⦝\",\"angsph\":\"∢\",\"angst\":\"Å\",\"angzarr\":\"⍼\",\"Aogon\":\"Ą\",\"aogon\":\"ą\",\"Aopf\":\"𝔸\",\"aopf\":\"𝕒\",\"apacir\":\"⩯\",\"ap\":\"≈\",\"apE\":\"⩰\",\"ape\":\"≊\",\"apid\":\"≋\",\"apos\":\"'\",\"ApplyFunction\":\"⁡\",\"approx\":\"≈\",\"approxeq\":\"≊\",\"Aring\":\"Å\",\"aring\":\"å\",\"Ascr\":\"𝒜\",\"ascr\":\"𝒶\",\"Assign\":\"≔\",\"ast\":\"*\",\"asymp\":\"≈\",\"asympeq\":\"≍\",\"Atilde\":\"Ã\",\"atilde\":\"ã\",\"Auml\":\"Ä\",\"auml\":\"ä\",\"awconint\":\"∳\",\"awint\":\"⨑\",\"backcong\":\"≌\",\"backepsilon\":\"϶\",\"backprime\":\"‵\",\"backsim\":\"∽\",\"backsimeq\":\"⋍\",\"Backslash\":\"∖\",\"Barv\":\"⫧\",\"barvee\":\"⊽\",\"barwed\":\"⌅\",\"Barwed\":\"⌆\",\"barwedge\":\"⌅\",\"bbrk\":\"⎵\",\"bbrktbrk\":\"⎶\",\"bcong\":\"≌\",\"Bcy\":\"Б\",\"bcy\":\"б\",\"bdquo\":\"„\",\"becaus\":\"∵\",\"because\":\"∵\",\"Because\":\"∵\",\"bemptyv\":\"⦰\",\"bepsi\":\"϶\",\"bernou\":\"ℬ\",\"Bernoullis\":\"ℬ\",\"Beta\":\"Β\",\"beta\":\"β\",\"beth\":\"ℶ\",\"between\":\"≬\",\"Bfr\":\"𝔅\",\"bfr\":\"𝔟\",\"bigcap\":\"⋂\",\"bigcirc\":\"◯\",\"bigcup\":\"⋃\",\"bigodot\":\"⨀\",\"bigoplus\":\"⨁\",\"bigotimes\":\"⨂\",\"bigsqcup\":\"⨆\",\"bigstar\":\"★\",\"bigtriangledown\":\"▽\",\"bigtriangleup\":\"△\",\"biguplus\":\"⨄\",\"bigvee\":\"⋁\",\"bigwedge\":\"⋀\",\"bkarow\":\"⤍\",\"blacklozenge\":\"⧫\",\"blacksquare\":\"▪\",\"blacktriangle\":\"▴\",\"blacktriangledown\":\"▾\",\"blacktriangleleft\":\"◂\",\"blacktriangleright\":\"▸\",\"blank\":\"␣\",\"blk12\":\"▒\",\"blk14\":\"░\",\"blk34\":\"▓\",\"block\":\"█\",\"bne\":\"=⃥\",\"bnequiv\":\"≡⃥\",\"bNot\":\"⫭\",\"bnot\":\"⌐\",\"Bopf\":\"𝔹\",\"bopf\":\"𝕓\",\"bot\":\"⊥\",\"bottom\":\"⊥\",\"bowtie\":\"⋈\",\"boxbox\":\"⧉\",\"boxdl\":\"┐\",\"boxdL\":\"╕\",\"boxDl\":\"╖\",\"boxDL\":\"╗\",\"boxdr\":\"┌\",\"boxdR\":\"╒\",\"boxDr\":\"╓\",\"boxDR\":\"╔\",\"boxh\":\"─\",\"boxH\":\"═\",\"boxhd\":\"┬\",\"boxHd\":\"╤\",\"boxhD\":\"╥\",\"boxHD\":\"╦\",\"boxhu\":\"┴\",\"boxHu\":\"╧\",\"boxhU\":\"╨\",\"boxHU\":\"╩\",\"boxminus\":\"⊟\",\"boxplus\":\"⊞\",\"boxtimes\":\"⊠\",\"boxul\":\"┘\",\"boxuL\":\"╛\",\"boxUl\":\"╜\",\"boxUL\":\"╝\",\"boxur\":\"└\",\"boxuR\":\"╘\",\"boxUr\":\"╙\",\"boxUR\":\"╚\",\"boxv\":\"│\",\"boxV\":\"║\",\"boxvh\":\"┼\",\"boxvH\":\"╪\",\"boxVh\":\"╫\",\"boxVH\":\"╬\",\"boxvl\":\"┤\",\"boxvL\":\"╡\",\"boxVl\":\"╢\",\"boxVL\":\"╣\",\"boxvr\":\"├\",\"boxvR\":\"╞\",\"boxVr\":\"╟\",\"boxVR\":\"╠\",\"bprime\":\"‵\",\"breve\":\"˘\",\"Breve\":\"˘\",\"brvbar\":\"¦\",\"bscr\":\"𝒷\",\"Bscr\":\"ℬ\",\"bsemi\":\"⁏\",\"bsim\":\"∽\",\"bsime\":\"⋍\",\"bsolb\":\"⧅\",\"bsol\":\"\\\\\",\"bsolhsub\":\"⟈\",\"bull\":\"•\",\"bullet\":\"•\",\"bump\":\"≎\",\"bumpE\":\"⪮\",\"bumpe\":\"≏\",\"Bumpeq\":\"≎\",\"bumpeq\":\"≏\",\"Cacute\":\"Ć\",\"cacute\":\"ć\",\"capand\":\"⩄\",\"capbrcup\":\"⩉\",\"capcap\":\"⩋\",\"cap\":\"∩\",\"Cap\":\"⋒\",\"capcup\":\"⩇\",\"capdot\":\"⩀\",\"CapitalDifferentialD\":\"ⅅ\",\"caps\":\"∩︀\",\"caret\":\"⁁\",\"caron\":\"ˇ\",\"Cayleys\":\"ℭ\",\"ccaps\":\"⩍\",\"Ccaron\":\"Č\",\"ccaron\":\"č\",\"Ccedil\":\"Ç\",\"ccedil\":\"ç\",\"Ccirc\":\"Ĉ\",\"ccirc\":\"ĉ\",\"Cconint\":\"∰\",\"ccups\":\"⩌\",\"ccupssm\":\"⩐\",\"Cdot\":\"Ċ\",\"cdot\":\"ċ\",\"cedil\":\"¸\",\"Cedilla\":\"¸\",\"cemptyv\":\"⦲\",\"cent\":\"¢\",\"centerdot\":\"·\",\"CenterDot\":\"·\",\"cfr\":\"𝔠\",\"Cfr\":\"ℭ\",\"CHcy\":\"Ч\",\"chcy\":\"ч\",\"check\":\"✓\",\"checkmark\":\"✓\",\"Chi\":\"Χ\",\"chi\":\"χ\",\"circ\":\"ˆ\",\"circeq\":\"≗\",\"circlearrowleft\":\"↺\",\"circlearrowright\":\"↻\",\"circledast\":\"⊛\",\"circledcirc\":\"⊚\",\"circleddash\":\"⊝\",\"CircleDot\":\"⊙\",\"circledR\":\"®\",\"circledS\":\"Ⓢ\",\"CircleMinus\":\"⊖\",\"CirclePlus\":\"⊕\",\"CircleTimes\":\"⊗\",\"cir\":\"○\",\"cirE\":\"⧃\",\"cire\":\"≗\",\"cirfnint\":\"⨐\",\"cirmid\":\"⫯\",\"cirscir\":\"⧂\",\"ClockwiseContourIntegral\":\"∲\",\"CloseCurlyDoubleQuote\":\"”\",\"CloseCurlyQuote\":\"’\",\"clubs\":\"♣\",\"clubsuit\":\"♣\",\"colon\":\":\",\"Colon\":\"∷\",\"Colone\":\"⩴\",\"colone\":\"≔\",\"coloneq\":\"≔\",\"comma\":\",\",\"commat\":\"@\",\"comp\":\"∁\",\"compfn\":\"∘\",\"complement\":\"∁\",\"complexes\":\"ℂ\",\"cong\":\"≅\",\"congdot\":\"⩭\",\"Congruent\":\"≡\",\"conint\":\"∮\",\"Conint\":\"∯\",\"ContourIntegral\":\"∮\",\"copf\":\"𝕔\",\"Copf\":\"ℂ\",\"coprod\":\"∐\",\"Coproduct\":\"∐\",\"copy\":\"©\",\"COPY\":\"©\",\"copysr\":\"℗\",\"CounterClockwiseContourIntegral\":\"∳\",\"crarr\":\"↵\",\"cross\":\"✗\",\"Cross\":\"⨯\",\"Cscr\":\"𝒞\",\"cscr\":\"𝒸\",\"csub\":\"⫏\",\"csube\":\"⫑\",\"csup\":\"⫐\",\"csupe\":\"⫒\",\"ctdot\":\"⋯\",\"cudarrl\":\"⤸\",\"cudarrr\":\"⤵\",\"cuepr\":\"⋞\",\"cuesc\":\"⋟\",\"cularr\":\"↶\",\"cularrp\":\"⤽\",\"cupbrcap\":\"⩈\",\"cupcap\":\"⩆\",\"CupCap\":\"≍\",\"cup\":\"∪\",\"Cup\":\"⋓\",\"cupcup\":\"⩊\",\"cupdot\":\"⊍\",\"cupor\":\"⩅\",\"cups\":\"∪︀\",\"curarr\":\"↷\",\"curarrm\":\"⤼\",\"curlyeqprec\":\"⋞\",\"curlyeqsucc\":\"⋟\",\"curlyvee\":\"⋎\",\"curlywedge\":\"⋏\",\"curren\":\"¤\",\"curvearrowleft\":\"↶\",\"curvearrowright\":\"↷\",\"cuvee\":\"⋎\",\"cuwed\":\"⋏\",\"cwconint\":\"∲\",\"cwint\":\"∱\",\"cylcty\":\"⌭\",\"dagger\":\"†\",\"Dagger\":\"‡\",\"daleth\":\"ℸ\",\"darr\":\"↓\",\"Darr\":\"↡\",\"dArr\":\"⇓\",\"dash\":\"‐\",\"Dashv\":\"⫤\",\"dashv\":\"⊣\",\"dbkarow\":\"⤏\",\"dblac\":\"˝\",\"Dcaron\":\"Ď\",\"dcaron\":\"ď\",\"Dcy\":\"Д\",\"dcy\":\"д\",\"ddagger\":\"‡\",\"ddarr\":\"⇊\",\"DD\":\"ⅅ\",\"dd\":\"ⅆ\",\"DDotrahd\":\"⤑\",\"ddotseq\":\"⩷\",\"deg\":\"°\",\"Del\":\"∇\",\"Delta\":\"Δ\",\"delta\":\"δ\",\"demptyv\":\"⦱\",\"dfisht\":\"⥿\",\"Dfr\":\"𝔇\",\"dfr\":\"𝔡\",\"dHar\":\"⥥\",\"dharl\":\"⇃\",\"dharr\":\"⇂\",\"DiacriticalAcute\":\"´\",\"DiacriticalDot\":\"˙\",\"DiacriticalDoubleAcute\":\"˝\",\"DiacriticalGrave\":\"`\",\"DiacriticalTilde\":\"˜\",\"diam\":\"⋄\",\"diamond\":\"⋄\",\"Diamond\":\"⋄\",\"diamondsuit\":\"♦\",\"diams\":\"♦\",\"die\":\"¨\",\"DifferentialD\":\"ⅆ\",\"digamma\":\"ϝ\",\"disin\":\"⋲\",\"div\":\"÷\",\"divide\":\"÷\",\"divideontimes\":\"⋇\",\"divonx\":\"⋇\",\"DJcy\":\"Ђ\",\"djcy\":\"ђ\",\"dlcorn\":\"⌞\",\"dlcrop\":\"⌍\",\"dollar\":\"$\",\"Dopf\":\"𝔻\",\"dopf\":\"𝕕\",\"Dot\":\"¨\",\"dot\":\"˙\",\"DotDot\":\"⃜\",\"doteq\":\"≐\",\"doteqdot\":\"≑\",\"DotEqual\":\"≐\",\"dotminus\":\"∸\",\"dotplus\":\"∔\",\"dotsquare\":\"⊡\",\"doublebarwedge\":\"⌆\",\"DoubleContourIntegral\":\"∯\",\"DoubleDot\":\"¨\",\"DoubleDownArrow\":\"⇓\",\"DoubleLeftArrow\":\"⇐\",\"DoubleLeftRightArrow\":\"⇔\",\"DoubleLeftTee\":\"⫤\",\"DoubleLongLeftArrow\":\"⟸\",\"DoubleLongLeftRightArrow\":\"⟺\",\"DoubleLongRightArrow\":\"⟹\",\"DoubleRightArrow\":\"⇒\",\"DoubleRightTee\":\"⊨\",\"DoubleUpArrow\":\"⇑\",\"DoubleUpDownArrow\":\"⇕\",\"DoubleVerticalBar\":\"∥\",\"DownArrowBar\":\"⤓\",\"downarrow\":\"↓\",\"DownArrow\":\"↓\",\"Downarrow\":\"⇓\",\"DownArrowUpArrow\":\"⇵\",\"DownBreve\":\"̑\",\"downdownarrows\":\"⇊\",\"downharpoonleft\":\"⇃\",\"downharpoonright\":\"⇂\",\"DownLeftRightVector\":\"⥐\",\"DownLeftTeeVector\":\"⥞\",\"DownLeftVectorBar\":\"⥖\",\"DownLeftVector\":\"↽\",\"DownRightTeeVector\":\"⥟\",\"DownRightVectorBar\":\"⥗\",\"DownRightVector\":\"⇁\",\"DownTeeArrow\":\"↧\",\"DownTee\":\"⊤\",\"drbkarow\":\"⤐\",\"drcorn\":\"⌟\",\"drcrop\":\"⌌\",\"Dscr\":\"𝒟\",\"dscr\":\"𝒹\",\"DScy\":\"Ѕ\",\"dscy\":\"ѕ\",\"dsol\":\"⧶\",\"Dstrok\":\"Đ\",\"dstrok\":\"đ\",\"dtdot\":\"⋱\",\"dtri\":\"▿\",\"dtrif\":\"▾\",\"duarr\":\"⇵\",\"duhar\":\"⥯\",\"dwangle\":\"⦦\",\"DZcy\":\"Џ\",\"dzcy\":\"џ\",\"dzigrarr\":\"⟿\",\"Eacute\":\"É\",\"eacute\":\"é\",\"easter\":\"⩮\",\"Ecaron\":\"Ě\",\"ecaron\":\"ě\",\"Ecirc\":\"Ê\",\"ecirc\":\"ê\",\"ecir\":\"≖\",\"ecolon\":\"≕\",\"Ecy\":\"Э\",\"ecy\":\"э\",\"eDDot\":\"⩷\",\"Edot\":\"Ė\",\"edot\":\"ė\",\"eDot\":\"≑\",\"ee\":\"ⅇ\",\"efDot\":\"≒\",\"Efr\":\"𝔈\",\"efr\":\"𝔢\",\"eg\":\"⪚\",\"Egrave\":\"È\",\"egrave\":\"è\",\"egs\":\"⪖\",\"egsdot\":\"⪘\",\"el\":\"⪙\",\"Element\":\"∈\",\"elinters\":\"⏧\",\"ell\":\"ℓ\",\"els\":\"⪕\",\"elsdot\":\"⪗\",\"Emacr\":\"Ē\",\"emacr\":\"ē\",\"empty\":\"∅\",\"emptyset\":\"∅\",\"EmptySmallSquare\":\"◻\",\"emptyv\":\"∅\",\"EmptyVerySmallSquare\":\"▫\",\"emsp13\":\" \",\"emsp14\":\" \",\"emsp\":\" \",\"ENG\":\"Ŋ\",\"eng\":\"ŋ\",\"ensp\":\" \",\"Eogon\":\"Ę\",\"eogon\":\"ę\",\"Eopf\":\"𝔼\",\"eopf\":\"𝕖\",\"epar\":\"⋕\",\"eparsl\":\"⧣\",\"eplus\":\"⩱\",\"epsi\":\"ε\",\"Epsilon\":\"Ε\",\"epsilon\":\"ε\",\"epsiv\":\"ϵ\",\"eqcirc\":\"≖\",\"eqcolon\":\"≕\",\"eqsim\":\"≂\",\"eqslantgtr\":\"⪖\",\"eqslantless\":\"⪕\",\"Equal\":\"⩵\",\"equals\":\"=\",\"EqualTilde\":\"≂\",\"equest\":\"≟\",\"Equilibrium\":\"⇌\",\"equiv\":\"≡\",\"equivDD\":\"⩸\",\"eqvparsl\":\"⧥\",\"erarr\":\"⥱\",\"erDot\":\"≓\",\"escr\":\"ℯ\",\"Escr\":\"ℰ\",\"esdot\":\"≐\",\"Esim\":\"⩳\",\"esim\":\"≂\",\"Eta\":\"Η\",\"eta\":\"η\",\"ETH\":\"Ð\",\"eth\":\"ð\",\"Euml\":\"Ë\",\"euml\":\"ë\",\"euro\":\"€\",\"excl\":\"!\",\"exist\":\"∃\",\"Exists\":\"∃\",\"expectation\":\"ℰ\",\"exponentiale\":\"ⅇ\",\"ExponentialE\":\"ⅇ\",\"fallingdotseq\":\"≒\",\"Fcy\":\"Ф\",\"fcy\":\"ф\",\"female\":\"♀\",\"ffilig\":\"ffi\",\"fflig\":\"ff\",\"ffllig\":\"ffl\",\"Ffr\":\"𝔉\",\"ffr\":\"𝔣\",\"filig\":\"fi\",\"FilledSmallSquare\":\"◼\",\"FilledVerySmallSquare\":\"▪\",\"fjlig\":\"fj\",\"flat\":\"♭\",\"fllig\":\"fl\",\"fltns\":\"▱\",\"fnof\":\"ƒ\",\"Fopf\":\"𝔽\",\"fopf\":\"𝕗\",\"forall\":\"∀\",\"ForAll\":\"∀\",\"fork\":\"⋔\",\"forkv\":\"⫙\",\"Fouriertrf\":\"ℱ\",\"fpartint\":\"⨍\",\"frac12\":\"½\",\"frac13\":\"⅓\",\"frac14\":\"¼\",\"frac15\":\"⅕\",\"frac16\":\"⅙\",\"frac18\":\"⅛\",\"frac23\":\"⅔\",\"frac25\":\"⅖\",\"frac34\":\"¾\",\"frac35\":\"⅗\",\"frac38\":\"⅜\",\"frac45\":\"⅘\",\"frac56\":\"⅚\",\"frac58\":\"⅝\",\"frac78\":\"⅞\",\"frasl\":\"⁄\",\"frown\":\"⌢\",\"fscr\":\"𝒻\",\"Fscr\":\"ℱ\",\"gacute\":\"ǵ\",\"Gamma\":\"Γ\",\"gamma\":\"γ\",\"Gammad\":\"Ϝ\",\"gammad\":\"ϝ\",\"gap\":\"⪆\",\"Gbreve\":\"Ğ\",\"gbreve\":\"ğ\",\"Gcedil\":\"Ģ\",\"Gcirc\":\"Ĝ\",\"gcirc\":\"ĝ\",\"Gcy\":\"Г\",\"gcy\":\"г\",\"Gdot\":\"Ġ\",\"gdot\":\"ġ\",\"ge\":\"≥\",\"gE\":\"≧\",\"gEl\":\"⪌\",\"gel\":\"⋛\",\"geq\":\"≥\",\"geqq\":\"≧\",\"geqslant\":\"⩾\",\"gescc\":\"⪩\",\"ges\":\"⩾\",\"gesdot\":\"⪀\",\"gesdoto\":\"⪂\",\"gesdotol\":\"⪄\",\"gesl\":\"⋛︀\",\"gesles\":\"⪔\",\"Gfr\":\"𝔊\",\"gfr\":\"𝔤\",\"gg\":\"≫\",\"Gg\":\"⋙\",\"ggg\":\"⋙\",\"gimel\":\"ℷ\",\"GJcy\":\"Ѓ\",\"gjcy\":\"ѓ\",\"gla\":\"⪥\",\"gl\":\"≷\",\"glE\":\"⪒\",\"glj\":\"⪤\",\"gnap\":\"⪊\",\"gnapprox\":\"⪊\",\"gne\":\"⪈\",\"gnE\":\"≩\",\"gneq\":\"⪈\",\"gneqq\":\"≩\",\"gnsim\":\"⋧\",\"Gopf\":\"𝔾\",\"gopf\":\"𝕘\",\"grave\":\"`\",\"GreaterEqual\":\"≥\",\"GreaterEqualLess\":\"⋛\",\"GreaterFullEqual\":\"≧\",\"GreaterGreater\":\"⪢\",\"GreaterLess\":\"≷\",\"GreaterSlantEqual\":\"⩾\",\"GreaterTilde\":\"≳\",\"Gscr\":\"𝒢\",\"gscr\":\"ℊ\",\"gsim\":\"≳\",\"gsime\":\"⪎\",\"gsiml\":\"⪐\",\"gtcc\":\"⪧\",\"gtcir\":\"⩺\",\"gt\":\">\",\"GT\":\">\",\"Gt\":\"≫\",\"gtdot\":\"⋗\",\"gtlPar\":\"⦕\",\"gtquest\":\"⩼\",\"gtrapprox\":\"⪆\",\"gtrarr\":\"⥸\",\"gtrdot\":\"⋗\",\"gtreqless\":\"⋛\",\"gtreqqless\":\"⪌\",\"gtrless\":\"≷\",\"gtrsim\":\"≳\",\"gvertneqq\":\"≩︀\",\"gvnE\":\"≩︀\",\"Hacek\":\"ˇ\",\"hairsp\":\" \",\"half\":\"½\",\"hamilt\":\"ℋ\",\"HARDcy\":\"Ъ\",\"hardcy\":\"ъ\",\"harrcir\":\"⥈\",\"harr\":\"↔\",\"hArr\":\"⇔\",\"harrw\":\"↭\",\"Hat\":\"^\",\"hbar\":\"ℏ\",\"Hcirc\":\"Ĥ\",\"hcirc\":\"ĥ\",\"hearts\":\"♥\",\"heartsuit\":\"♥\",\"hellip\":\"…\",\"hercon\":\"⊹\",\"hfr\":\"𝔥\",\"Hfr\":\"ℌ\",\"HilbertSpace\":\"ℋ\",\"hksearow\":\"⤥\",\"hkswarow\":\"⤦\",\"hoarr\":\"⇿\",\"homtht\":\"∻\",\"hookleftarrow\":\"↩\",\"hookrightarrow\":\"↪\",\"hopf\":\"𝕙\",\"Hopf\":\"ℍ\",\"horbar\":\"―\",\"HorizontalLine\":\"─\",\"hscr\":\"𝒽\",\"Hscr\":\"ℋ\",\"hslash\":\"ℏ\",\"Hstrok\":\"Ħ\",\"hstrok\":\"ħ\",\"HumpDownHump\":\"≎\",\"HumpEqual\":\"≏\",\"hybull\":\"⁃\",\"hyphen\":\"‐\",\"Iacute\":\"Í\",\"iacute\":\"í\",\"ic\":\"⁣\",\"Icirc\":\"Î\",\"icirc\":\"î\",\"Icy\":\"И\",\"icy\":\"и\",\"Idot\":\"İ\",\"IEcy\":\"Е\",\"iecy\":\"е\",\"iexcl\":\"¡\",\"iff\":\"⇔\",\"ifr\":\"𝔦\",\"Ifr\":\"ℑ\",\"Igrave\":\"Ì\",\"igrave\":\"ì\",\"ii\":\"ⅈ\",\"iiiint\":\"⨌\",\"iiint\":\"∭\",\"iinfin\":\"⧜\",\"iiota\":\"℩\",\"IJlig\":\"IJ\",\"ijlig\":\"ij\",\"Imacr\":\"Ī\",\"imacr\":\"ī\",\"image\":\"ℑ\",\"ImaginaryI\":\"ⅈ\",\"imagline\":\"ℐ\",\"imagpart\":\"ℑ\",\"imath\":\"ı\",\"Im\":\"ℑ\",\"imof\":\"⊷\",\"imped\":\"Ƶ\",\"Implies\":\"⇒\",\"incare\":\"℅\",\"in\":\"∈\",\"infin\":\"∞\",\"infintie\":\"⧝\",\"inodot\":\"ı\",\"intcal\":\"⊺\",\"int\":\"∫\",\"Int\":\"∬\",\"integers\":\"ℤ\",\"Integral\":\"∫\",\"intercal\":\"⊺\",\"Intersection\":\"⋂\",\"intlarhk\":\"⨗\",\"intprod\":\"⨼\",\"InvisibleComma\":\"⁣\",\"InvisibleTimes\":\"⁢\",\"IOcy\":\"Ё\",\"iocy\":\"ё\",\"Iogon\":\"Į\",\"iogon\":\"į\",\"Iopf\":\"𝕀\",\"iopf\":\"𝕚\",\"Iota\":\"Ι\",\"iota\":\"ι\",\"iprod\":\"⨼\",\"iquest\":\"¿\",\"iscr\":\"𝒾\",\"Iscr\":\"ℐ\",\"isin\":\"∈\",\"isindot\":\"⋵\",\"isinE\":\"⋹\",\"isins\":\"⋴\",\"isinsv\":\"⋳\",\"isinv\":\"∈\",\"it\":\"⁢\",\"Itilde\":\"Ĩ\",\"itilde\":\"ĩ\",\"Iukcy\":\"І\",\"iukcy\":\"і\",\"Iuml\":\"Ï\",\"iuml\":\"ï\",\"Jcirc\":\"Ĵ\",\"jcirc\":\"ĵ\",\"Jcy\":\"Й\",\"jcy\":\"й\",\"Jfr\":\"𝔍\",\"jfr\":\"𝔧\",\"jmath\":\"ȷ\",\"Jopf\":\"𝕁\",\"jopf\":\"𝕛\",\"Jscr\":\"𝒥\",\"jscr\":\"𝒿\",\"Jsercy\":\"Ј\",\"jsercy\":\"ј\",\"Jukcy\":\"Є\",\"jukcy\":\"є\",\"Kappa\":\"Κ\",\"kappa\":\"κ\",\"kappav\":\"ϰ\",\"Kcedil\":\"Ķ\",\"kcedil\":\"ķ\",\"Kcy\":\"К\",\"kcy\":\"к\",\"Kfr\":\"𝔎\",\"kfr\":\"𝔨\",\"kgreen\":\"ĸ\",\"KHcy\":\"Х\",\"khcy\":\"х\",\"KJcy\":\"Ќ\",\"kjcy\":\"ќ\",\"Kopf\":\"𝕂\",\"kopf\":\"𝕜\",\"Kscr\":\"𝒦\",\"kscr\":\"𝓀\",\"lAarr\":\"⇚\",\"Lacute\":\"Ĺ\",\"lacute\":\"ĺ\",\"laemptyv\":\"⦴\",\"lagran\":\"ℒ\",\"Lambda\":\"Λ\",\"lambda\":\"λ\",\"lang\":\"⟨\",\"Lang\":\"⟪\",\"langd\":\"⦑\",\"langle\":\"⟨\",\"lap\":\"⪅\",\"Laplacetrf\":\"ℒ\",\"laquo\":\"«\",\"larrb\":\"⇤\",\"larrbfs\":\"⤟\",\"larr\":\"←\",\"Larr\":\"↞\",\"lArr\":\"⇐\",\"larrfs\":\"⤝\",\"larrhk\":\"↩\",\"larrlp\":\"↫\",\"larrpl\":\"⤹\",\"larrsim\":\"⥳\",\"larrtl\":\"↢\",\"latail\":\"⤙\",\"lAtail\":\"⤛\",\"lat\":\"⪫\",\"late\":\"⪭\",\"lates\":\"⪭︀\",\"lbarr\":\"⤌\",\"lBarr\":\"⤎\",\"lbbrk\":\"❲\",\"lbrace\":\"{\",\"lbrack\":\"[\",\"lbrke\":\"⦋\",\"lbrksld\":\"⦏\",\"lbrkslu\":\"⦍\",\"Lcaron\":\"Ľ\",\"lcaron\":\"ľ\",\"Lcedil\":\"Ļ\",\"lcedil\":\"ļ\",\"lceil\":\"⌈\",\"lcub\":\"{\",\"Lcy\":\"Л\",\"lcy\":\"л\",\"ldca\":\"⤶\",\"ldquo\":\"“\",\"ldquor\":\"„\",\"ldrdhar\":\"⥧\",\"ldrushar\":\"⥋\",\"ldsh\":\"↲\",\"le\":\"≤\",\"lE\":\"≦\",\"LeftAngleBracket\":\"⟨\",\"LeftArrowBar\":\"⇤\",\"leftarrow\":\"←\",\"LeftArrow\":\"←\",\"Leftarrow\":\"⇐\",\"LeftArrowRightArrow\":\"⇆\",\"leftarrowtail\":\"↢\",\"LeftCeiling\":\"⌈\",\"LeftDoubleBracket\":\"⟦\",\"LeftDownTeeVector\":\"⥡\",\"LeftDownVectorBar\":\"⥙\",\"LeftDownVector\":\"⇃\",\"LeftFloor\":\"⌊\",\"leftharpoondown\":\"↽\",\"leftharpoonup\":\"↼\",\"leftleftarrows\":\"⇇\",\"leftrightarrow\":\"↔\",\"LeftRightArrow\":\"↔\",\"Leftrightarrow\":\"⇔\",\"leftrightarrows\":\"⇆\",\"leftrightharpoons\":\"⇋\",\"leftrightsquigarrow\":\"↭\",\"LeftRightVector\":\"⥎\",\"LeftTeeArrow\":\"↤\",\"LeftTee\":\"⊣\",\"LeftTeeVector\":\"⥚\",\"leftthreetimes\":\"⋋\",\"LeftTriangleBar\":\"⧏\",\"LeftTriangle\":\"⊲\",\"LeftTriangleEqual\":\"⊴\",\"LeftUpDownVector\":\"⥑\",\"LeftUpTeeVector\":\"⥠\",\"LeftUpVectorBar\":\"⥘\",\"LeftUpVector\":\"↿\",\"LeftVectorBar\":\"⥒\",\"LeftVector\":\"↼\",\"lEg\":\"⪋\",\"leg\":\"⋚\",\"leq\":\"≤\",\"leqq\":\"≦\",\"leqslant\":\"⩽\",\"lescc\":\"⪨\",\"les\":\"⩽\",\"lesdot\":\"⩿\",\"lesdoto\":\"⪁\",\"lesdotor\":\"⪃\",\"lesg\":\"⋚︀\",\"lesges\":\"⪓\",\"lessapprox\":\"⪅\",\"lessdot\":\"⋖\",\"lesseqgtr\":\"⋚\",\"lesseqqgtr\":\"⪋\",\"LessEqualGreater\":\"⋚\",\"LessFullEqual\":\"≦\",\"LessGreater\":\"≶\",\"lessgtr\":\"≶\",\"LessLess\":\"⪡\",\"lesssim\":\"≲\",\"LessSlantEqual\":\"⩽\",\"LessTilde\":\"≲\",\"lfisht\":\"⥼\",\"lfloor\":\"⌊\",\"Lfr\":\"𝔏\",\"lfr\":\"𝔩\",\"lg\":\"≶\",\"lgE\":\"⪑\",\"lHar\":\"⥢\",\"lhard\":\"↽\",\"lharu\":\"↼\",\"lharul\":\"⥪\",\"lhblk\":\"▄\",\"LJcy\":\"Љ\",\"ljcy\":\"љ\",\"llarr\":\"⇇\",\"ll\":\"≪\",\"Ll\":\"⋘\",\"llcorner\":\"⌞\",\"Lleftarrow\":\"⇚\",\"llhard\":\"⥫\",\"lltri\":\"◺\",\"Lmidot\":\"Ŀ\",\"lmidot\":\"ŀ\",\"lmoustache\":\"⎰\",\"lmoust\":\"⎰\",\"lnap\":\"⪉\",\"lnapprox\":\"⪉\",\"lne\":\"⪇\",\"lnE\":\"≨\",\"lneq\":\"⪇\",\"lneqq\":\"≨\",\"lnsim\":\"⋦\",\"loang\":\"⟬\",\"loarr\":\"⇽\",\"lobrk\":\"⟦\",\"longleftarrow\":\"⟵\",\"LongLeftArrow\":\"⟵\",\"Longleftarrow\":\"⟸\",\"longleftrightarrow\":\"⟷\",\"LongLeftRightArrow\":\"⟷\",\"Longleftrightarrow\":\"⟺\",\"longmapsto\":\"⟼\",\"longrightarrow\":\"⟶\",\"LongRightArrow\":\"⟶\",\"Longrightarrow\":\"⟹\",\"looparrowleft\":\"↫\",\"looparrowright\":\"↬\",\"lopar\":\"⦅\",\"Lopf\":\"𝕃\",\"lopf\":\"𝕝\",\"loplus\":\"⨭\",\"lotimes\":\"⨴\",\"lowast\":\"∗\",\"lowbar\":\"_\",\"LowerLeftArrow\":\"↙\",\"LowerRightArrow\":\"↘\",\"loz\":\"◊\",\"lozenge\":\"◊\",\"lozf\":\"⧫\",\"lpar\":\"(\",\"lparlt\":\"⦓\",\"lrarr\":\"⇆\",\"lrcorner\":\"⌟\",\"lrhar\":\"⇋\",\"lrhard\":\"⥭\",\"lrm\":\"‎\",\"lrtri\":\"⊿\",\"lsaquo\":\"‹\",\"lscr\":\"𝓁\",\"Lscr\":\"ℒ\",\"lsh\":\"↰\",\"Lsh\":\"↰\",\"lsim\":\"≲\",\"lsime\":\"⪍\",\"lsimg\":\"⪏\",\"lsqb\":\"[\",\"lsquo\":\"‘\",\"lsquor\":\"‚\",\"Lstrok\":\"Ł\",\"lstrok\":\"ł\",\"ltcc\":\"⪦\",\"ltcir\":\"⩹\",\"lt\":\"<\",\"LT\":\"<\",\"Lt\":\"≪\",\"ltdot\":\"⋖\",\"lthree\":\"⋋\",\"ltimes\":\"⋉\",\"ltlarr\":\"⥶\",\"ltquest\":\"⩻\",\"ltri\":\"◃\",\"ltrie\":\"⊴\",\"ltrif\":\"◂\",\"ltrPar\":\"⦖\",\"lurdshar\":\"⥊\",\"luruhar\":\"⥦\",\"lvertneqq\":\"≨︀\",\"lvnE\":\"≨︀\",\"macr\":\"¯\",\"male\":\"♂\",\"malt\":\"✠\",\"maltese\":\"✠\",\"Map\":\"⤅\",\"map\":\"↦\",\"mapsto\":\"↦\",\"mapstodown\":\"↧\",\"mapstoleft\":\"↤\",\"mapstoup\":\"↥\",\"marker\":\"▮\",\"mcomma\":\"⨩\",\"Mcy\":\"М\",\"mcy\":\"м\",\"mdash\":\"—\",\"mDDot\":\"∺\",\"measuredangle\":\"∡\",\"MediumSpace\":\" \",\"Mellintrf\":\"ℳ\",\"Mfr\":\"𝔐\",\"mfr\":\"𝔪\",\"mho\":\"℧\",\"micro\":\"µ\",\"midast\":\"*\",\"midcir\":\"⫰\",\"mid\":\"∣\",\"middot\":\"·\",\"minusb\":\"⊟\",\"minus\":\"−\",\"minusd\":\"∸\",\"minusdu\":\"⨪\",\"MinusPlus\":\"∓\",\"mlcp\":\"⫛\",\"mldr\":\"…\",\"mnplus\":\"∓\",\"models\":\"⊧\",\"Mopf\":\"𝕄\",\"mopf\":\"𝕞\",\"mp\":\"∓\",\"mscr\":\"𝓂\",\"Mscr\":\"ℳ\",\"mstpos\":\"∾\",\"Mu\":\"Μ\",\"mu\":\"μ\",\"multimap\":\"⊸\",\"mumap\":\"⊸\",\"nabla\":\"∇\",\"Nacute\":\"Ń\",\"nacute\":\"ń\",\"nang\":\"∠⃒\",\"nap\":\"≉\",\"napE\":\"⩰̸\",\"napid\":\"≋̸\",\"napos\":\"ʼn\",\"napprox\":\"≉\",\"natural\":\"♮\",\"naturals\":\"ℕ\",\"natur\":\"♮\",\"nbsp\":\" \",\"nbump\":\"≎̸\",\"nbumpe\":\"≏̸\",\"ncap\":\"⩃\",\"Ncaron\":\"Ň\",\"ncaron\":\"ň\",\"Ncedil\":\"Ņ\",\"ncedil\":\"ņ\",\"ncong\":\"≇\",\"ncongdot\":\"⩭̸\",\"ncup\":\"⩂\",\"Ncy\":\"Н\",\"ncy\":\"н\",\"ndash\":\"–\",\"nearhk\":\"⤤\",\"nearr\":\"↗\",\"neArr\":\"⇗\",\"nearrow\":\"↗\",\"ne\":\"≠\",\"nedot\":\"≐̸\",\"NegativeMediumSpace\":\"​\",\"NegativeThickSpace\":\"​\",\"NegativeThinSpace\":\"​\",\"NegativeVeryThinSpace\":\"​\",\"nequiv\":\"≢\",\"nesear\":\"⤨\",\"nesim\":\"≂̸\",\"NestedGreaterGreater\":\"≫\",\"NestedLessLess\":\"≪\",\"NewLine\":\"\\n\",\"nexist\":\"∄\",\"nexists\":\"∄\",\"Nfr\":\"𝔑\",\"nfr\":\"𝔫\",\"ngE\":\"≧̸\",\"nge\":\"≱\",\"ngeq\":\"≱\",\"ngeqq\":\"≧̸\",\"ngeqslant\":\"⩾̸\",\"nges\":\"⩾̸\",\"nGg\":\"⋙̸\",\"ngsim\":\"≵\",\"nGt\":\"≫⃒\",\"ngt\":\"≯\",\"ngtr\":\"≯\",\"nGtv\":\"≫̸\",\"nharr\":\"↮\",\"nhArr\":\"⇎\",\"nhpar\":\"⫲\",\"ni\":\"∋\",\"nis\":\"⋼\",\"nisd\":\"⋺\",\"niv\":\"∋\",\"NJcy\":\"Њ\",\"njcy\":\"њ\",\"nlarr\":\"↚\",\"nlArr\":\"⇍\",\"nldr\":\"‥\",\"nlE\":\"≦̸\",\"nle\":\"≰\",\"nleftarrow\":\"↚\",\"nLeftarrow\":\"⇍\",\"nleftrightarrow\":\"↮\",\"nLeftrightarrow\":\"⇎\",\"nleq\":\"≰\",\"nleqq\":\"≦̸\",\"nleqslant\":\"⩽̸\",\"nles\":\"⩽̸\",\"nless\":\"≮\",\"nLl\":\"⋘̸\",\"nlsim\":\"≴\",\"nLt\":\"≪⃒\",\"nlt\":\"≮\",\"nltri\":\"⋪\",\"nltrie\":\"⋬\",\"nLtv\":\"≪̸\",\"nmid\":\"∤\",\"NoBreak\":\"⁠\",\"NonBreakingSpace\":\" \",\"nopf\":\"𝕟\",\"Nopf\":\"ℕ\",\"Not\":\"⫬\",\"not\":\"¬\",\"NotCongruent\":\"≢\",\"NotCupCap\":\"≭\",\"NotDoubleVerticalBar\":\"∦\",\"NotElement\":\"∉\",\"NotEqual\":\"≠\",\"NotEqualTilde\":\"≂̸\",\"NotExists\":\"∄\",\"NotGreater\":\"≯\",\"NotGreaterEqual\":\"≱\",\"NotGreaterFullEqual\":\"≧̸\",\"NotGreaterGreater\":\"≫̸\",\"NotGreaterLess\":\"≹\",\"NotGreaterSlantEqual\":\"⩾̸\",\"NotGreaterTilde\":\"≵\",\"NotHumpDownHump\":\"≎̸\",\"NotHumpEqual\":\"≏̸\",\"notin\":\"∉\",\"notindot\":\"⋵̸\",\"notinE\":\"⋹̸\",\"notinva\":\"∉\",\"notinvb\":\"⋷\",\"notinvc\":\"⋶\",\"NotLeftTriangleBar\":\"⧏̸\",\"NotLeftTriangle\":\"⋪\",\"NotLeftTriangleEqual\":\"⋬\",\"NotLess\":\"≮\",\"NotLessEqual\":\"≰\",\"NotLessGreater\":\"≸\",\"NotLessLess\":\"≪̸\",\"NotLessSlantEqual\":\"⩽̸\",\"NotLessTilde\":\"≴\",\"NotNestedGreaterGreater\":\"⪢̸\",\"NotNestedLessLess\":\"⪡̸\",\"notni\":\"∌\",\"notniva\":\"∌\",\"notnivb\":\"⋾\",\"notnivc\":\"⋽\",\"NotPrecedes\":\"⊀\",\"NotPrecedesEqual\":\"⪯̸\",\"NotPrecedesSlantEqual\":\"⋠\",\"NotReverseElement\":\"∌\",\"NotRightTriangleBar\":\"⧐̸\",\"NotRightTriangle\":\"⋫\",\"NotRightTriangleEqual\":\"⋭\",\"NotSquareSubset\":\"⊏̸\",\"NotSquareSubsetEqual\":\"⋢\",\"NotSquareSuperset\":\"⊐̸\",\"NotSquareSupersetEqual\":\"⋣\",\"NotSubset\":\"⊂⃒\",\"NotSubsetEqual\":\"⊈\",\"NotSucceeds\":\"⊁\",\"NotSucceedsEqual\":\"⪰̸\",\"NotSucceedsSlantEqual\":\"⋡\",\"NotSucceedsTilde\":\"≿̸\",\"NotSuperset\":\"⊃⃒\",\"NotSupersetEqual\":\"⊉\",\"NotTilde\":\"≁\",\"NotTildeEqual\":\"≄\",\"NotTildeFullEqual\":\"≇\",\"NotTildeTilde\":\"≉\",\"NotVerticalBar\":\"∤\",\"nparallel\":\"∦\",\"npar\":\"∦\",\"nparsl\":\"⫽⃥\",\"npart\":\"∂̸\",\"npolint\":\"⨔\",\"npr\":\"⊀\",\"nprcue\":\"⋠\",\"nprec\":\"⊀\",\"npreceq\":\"⪯̸\",\"npre\":\"⪯̸\",\"nrarrc\":\"⤳̸\",\"nrarr\":\"↛\",\"nrArr\":\"⇏\",\"nrarrw\":\"↝̸\",\"nrightarrow\":\"↛\",\"nRightarrow\":\"⇏\",\"nrtri\":\"⋫\",\"nrtrie\":\"⋭\",\"nsc\":\"⊁\",\"nsccue\":\"⋡\",\"nsce\":\"⪰̸\",\"Nscr\":\"𝒩\",\"nscr\":\"𝓃\",\"nshortmid\":\"∤\",\"nshortparallel\":\"∦\",\"nsim\":\"≁\",\"nsime\":\"≄\",\"nsimeq\":\"≄\",\"nsmid\":\"∤\",\"nspar\":\"∦\",\"nsqsube\":\"⋢\",\"nsqsupe\":\"⋣\",\"nsub\":\"⊄\",\"nsubE\":\"⫅̸\",\"nsube\":\"⊈\",\"nsubset\":\"⊂⃒\",\"nsubseteq\":\"⊈\",\"nsubseteqq\":\"⫅̸\",\"nsucc\":\"⊁\",\"nsucceq\":\"⪰̸\",\"nsup\":\"⊅\",\"nsupE\":\"⫆̸\",\"nsupe\":\"⊉\",\"nsupset\":\"⊃⃒\",\"nsupseteq\":\"⊉\",\"nsupseteqq\":\"⫆̸\",\"ntgl\":\"≹\",\"Ntilde\":\"Ñ\",\"ntilde\":\"ñ\",\"ntlg\":\"≸\",\"ntriangleleft\":\"⋪\",\"ntrianglelefteq\":\"⋬\",\"ntriangleright\":\"⋫\",\"ntrianglerighteq\":\"⋭\",\"Nu\":\"Ν\",\"nu\":\"ν\",\"num\":\"#\",\"numero\":\"№\",\"numsp\":\" \",\"nvap\":\"≍⃒\",\"nvdash\":\"⊬\",\"nvDash\":\"⊭\",\"nVdash\":\"⊮\",\"nVDash\":\"⊯\",\"nvge\":\"≥⃒\",\"nvgt\":\">⃒\",\"nvHarr\":\"⤄\",\"nvinfin\":\"⧞\",\"nvlArr\":\"⤂\",\"nvle\":\"≤⃒\",\"nvlt\":\"<⃒\",\"nvltrie\":\"⊴⃒\",\"nvrArr\":\"⤃\",\"nvrtrie\":\"⊵⃒\",\"nvsim\":\"∼⃒\",\"nwarhk\":\"⤣\",\"nwarr\":\"↖\",\"nwArr\":\"⇖\",\"nwarrow\":\"↖\",\"nwnear\":\"⤧\",\"Oacute\":\"Ó\",\"oacute\":\"ó\",\"oast\":\"⊛\",\"Ocirc\":\"Ô\",\"ocirc\":\"ô\",\"ocir\":\"⊚\",\"Ocy\":\"О\",\"ocy\":\"о\",\"odash\":\"⊝\",\"Odblac\":\"Ő\",\"odblac\":\"ő\",\"odiv\":\"⨸\",\"odot\":\"⊙\",\"odsold\":\"⦼\",\"OElig\":\"Œ\",\"oelig\":\"œ\",\"ofcir\":\"⦿\",\"Ofr\":\"𝔒\",\"ofr\":\"𝔬\",\"ogon\":\"˛\",\"Ograve\":\"Ò\",\"ograve\":\"ò\",\"ogt\":\"⧁\",\"ohbar\":\"⦵\",\"ohm\":\"Ω\",\"oint\":\"∮\",\"olarr\":\"↺\",\"olcir\":\"⦾\",\"olcross\":\"⦻\",\"oline\":\"‾\",\"olt\":\"⧀\",\"Omacr\":\"Ō\",\"omacr\":\"ō\",\"Omega\":\"Ω\",\"omega\":\"ω\",\"Omicron\":\"Ο\",\"omicron\":\"ο\",\"omid\":\"⦶\",\"ominus\":\"⊖\",\"Oopf\":\"𝕆\",\"oopf\":\"𝕠\",\"opar\":\"⦷\",\"OpenCurlyDoubleQuote\":\"“\",\"OpenCurlyQuote\":\"‘\",\"operp\":\"⦹\",\"oplus\":\"⊕\",\"orarr\":\"↻\",\"Or\":\"⩔\",\"or\":\"∨\",\"ord\":\"⩝\",\"order\":\"ℴ\",\"orderof\":\"ℴ\",\"ordf\":\"ª\",\"ordm\":\"º\",\"origof\":\"⊶\",\"oror\":\"⩖\",\"orslope\":\"⩗\",\"orv\":\"⩛\",\"oS\":\"Ⓢ\",\"Oscr\":\"𝒪\",\"oscr\":\"ℴ\",\"Oslash\":\"Ø\",\"oslash\":\"ø\",\"osol\":\"⊘\",\"Otilde\":\"Õ\",\"otilde\":\"õ\",\"otimesas\":\"⨶\",\"Otimes\":\"⨷\",\"otimes\":\"⊗\",\"Ouml\":\"Ö\",\"ouml\":\"ö\",\"ovbar\":\"⌽\",\"OverBar\":\"‾\",\"OverBrace\":\"⏞\",\"OverBracket\":\"⎴\",\"OverParenthesis\":\"⏜\",\"para\":\"¶\",\"parallel\":\"∥\",\"par\":\"∥\",\"parsim\":\"⫳\",\"parsl\":\"⫽\",\"part\":\"∂\",\"PartialD\":\"∂\",\"Pcy\":\"П\",\"pcy\":\"п\",\"percnt\":\"%\",\"period\":\".\",\"permil\":\"‰\",\"perp\":\"⊥\",\"pertenk\":\"‱\",\"Pfr\":\"𝔓\",\"pfr\":\"𝔭\",\"Phi\":\"Φ\",\"phi\":\"φ\",\"phiv\":\"ϕ\",\"phmmat\":\"ℳ\",\"phone\":\"☎\",\"Pi\":\"Π\",\"pi\":\"π\",\"pitchfork\":\"⋔\",\"piv\":\"ϖ\",\"planck\":\"ℏ\",\"planckh\":\"ℎ\",\"plankv\":\"ℏ\",\"plusacir\":\"⨣\",\"plusb\":\"⊞\",\"pluscir\":\"⨢\",\"plus\":\"+\",\"plusdo\":\"∔\",\"plusdu\":\"⨥\",\"pluse\":\"⩲\",\"PlusMinus\":\"±\",\"plusmn\":\"±\",\"plussim\":\"⨦\",\"plustwo\":\"⨧\",\"pm\":\"±\",\"Poincareplane\":\"ℌ\",\"pointint\":\"⨕\",\"popf\":\"𝕡\",\"Popf\":\"ℙ\",\"pound\":\"£\",\"prap\":\"⪷\",\"Pr\":\"⪻\",\"pr\":\"≺\",\"prcue\":\"≼\",\"precapprox\":\"⪷\",\"prec\":\"≺\",\"preccurlyeq\":\"≼\",\"Precedes\":\"≺\",\"PrecedesEqual\":\"⪯\",\"PrecedesSlantEqual\":\"≼\",\"PrecedesTilde\":\"≾\",\"preceq\":\"⪯\",\"precnapprox\":\"⪹\",\"precneqq\":\"⪵\",\"precnsim\":\"⋨\",\"pre\":\"⪯\",\"prE\":\"⪳\",\"precsim\":\"≾\",\"prime\":\"′\",\"Prime\":\"″\",\"primes\":\"ℙ\",\"prnap\":\"⪹\",\"prnE\":\"⪵\",\"prnsim\":\"⋨\",\"prod\":\"∏\",\"Product\":\"∏\",\"profalar\":\"⌮\",\"profline\":\"⌒\",\"profsurf\":\"⌓\",\"prop\":\"∝\",\"Proportional\":\"∝\",\"Proportion\":\"∷\",\"propto\":\"∝\",\"prsim\":\"≾\",\"prurel\":\"⊰\",\"Pscr\":\"𝒫\",\"pscr\":\"𝓅\",\"Psi\":\"Ψ\",\"psi\":\"ψ\",\"puncsp\":\" \",\"Qfr\":\"𝔔\",\"qfr\":\"𝔮\",\"qint\":\"⨌\",\"qopf\":\"𝕢\",\"Qopf\":\"ℚ\",\"qprime\":\"⁗\",\"Qscr\":\"𝒬\",\"qscr\":\"𝓆\",\"quaternions\":\"ℍ\",\"quatint\":\"⨖\",\"quest\":\"?\",\"questeq\":\"≟\",\"quot\":\"\\\"\",\"QUOT\":\"\\\"\",\"rAarr\":\"⇛\",\"race\":\"∽̱\",\"Racute\":\"Ŕ\",\"racute\":\"ŕ\",\"radic\":\"√\",\"raemptyv\":\"⦳\",\"rang\":\"⟩\",\"Rang\":\"⟫\",\"rangd\":\"⦒\",\"range\":\"⦥\",\"rangle\":\"⟩\",\"raquo\":\"»\",\"rarrap\":\"⥵\",\"rarrb\":\"⇥\",\"rarrbfs\":\"⤠\",\"rarrc\":\"⤳\",\"rarr\":\"→\",\"Rarr\":\"↠\",\"rArr\":\"⇒\",\"rarrfs\":\"⤞\",\"rarrhk\":\"↪\",\"rarrlp\":\"↬\",\"rarrpl\":\"⥅\",\"rarrsim\":\"⥴\",\"Rarrtl\":\"⤖\",\"rarrtl\":\"↣\",\"rarrw\":\"↝\",\"ratail\":\"⤚\",\"rAtail\":\"⤜\",\"ratio\":\"∶\",\"rationals\":\"ℚ\",\"rbarr\":\"⤍\",\"rBarr\":\"⤏\",\"RBarr\":\"⤐\",\"rbbrk\":\"❳\",\"rbrace\":\"}\",\"rbrack\":\"]\",\"rbrke\":\"⦌\",\"rbrksld\":\"⦎\",\"rbrkslu\":\"⦐\",\"Rcaron\":\"Ř\",\"rcaron\":\"ř\",\"Rcedil\":\"Ŗ\",\"rcedil\":\"ŗ\",\"rceil\":\"⌉\",\"rcub\":\"}\",\"Rcy\":\"Р\",\"rcy\":\"р\",\"rdca\":\"⤷\",\"rdldhar\":\"⥩\",\"rdquo\":\"”\",\"rdquor\":\"”\",\"rdsh\":\"↳\",\"real\":\"ℜ\",\"realine\":\"ℛ\",\"realpart\":\"ℜ\",\"reals\":\"ℝ\",\"Re\":\"ℜ\",\"rect\":\"▭\",\"reg\":\"®\",\"REG\":\"®\",\"ReverseElement\":\"∋\",\"ReverseEquilibrium\":\"⇋\",\"ReverseUpEquilibrium\":\"⥯\",\"rfisht\":\"⥽\",\"rfloor\":\"⌋\",\"rfr\":\"𝔯\",\"Rfr\":\"ℜ\",\"rHar\":\"⥤\",\"rhard\":\"⇁\",\"rharu\":\"⇀\",\"rharul\":\"⥬\",\"Rho\":\"Ρ\",\"rho\":\"ρ\",\"rhov\":\"ϱ\",\"RightAngleBracket\":\"⟩\",\"RightArrowBar\":\"⇥\",\"rightarrow\":\"→\",\"RightArrow\":\"→\",\"Rightarrow\":\"⇒\",\"RightArrowLeftArrow\":\"⇄\",\"rightarrowtail\":\"↣\",\"RightCeiling\":\"⌉\",\"RightDoubleBracket\":\"⟧\",\"RightDownTeeVector\":\"⥝\",\"RightDownVectorBar\":\"⥕\",\"RightDownVector\":\"⇂\",\"RightFloor\":\"⌋\",\"rightharpoondown\":\"⇁\",\"rightharpoonup\":\"⇀\",\"rightleftarrows\":\"⇄\",\"rightleftharpoons\":\"⇌\",\"rightrightarrows\":\"⇉\",\"rightsquigarrow\":\"↝\",\"RightTeeArrow\":\"↦\",\"RightTee\":\"⊢\",\"RightTeeVector\":\"⥛\",\"rightthreetimes\":\"⋌\",\"RightTriangleBar\":\"⧐\",\"RightTriangle\":\"⊳\",\"RightTriangleEqual\":\"⊵\",\"RightUpDownVector\":\"⥏\",\"RightUpTeeVector\":\"⥜\",\"RightUpVectorBar\":\"⥔\",\"RightUpVector\":\"↾\",\"RightVectorBar\":\"⥓\",\"RightVector\":\"⇀\",\"ring\":\"˚\",\"risingdotseq\":\"≓\",\"rlarr\":\"⇄\",\"rlhar\":\"⇌\",\"rlm\":\"‏\",\"rmoustache\":\"⎱\",\"rmoust\":\"⎱\",\"rnmid\":\"⫮\",\"roang\":\"⟭\",\"roarr\":\"⇾\",\"robrk\":\"⟧\",\"ropar\":\"⦆\",\"ropf\":\"𝕣\",\"Ropf\":\"ℝ\",\"roplus\":\"⨮\",\"rotimes\":\"⨵\",\"RoundImplies\":\"⥰\",\"rpar\":\")\",\"rpargt\":\"⦔\",\"rppolint\":\"⨒\",\"rrarr\":\"⇉\",\"Rrightarrow\":\"⇛\",\"rsaquo\":\"›\",\"rscr\":\"𝓇\",\"Rscr\":\"ℛ\",\"rsh\":\"↱\",\"Rsh\":\"↱\",\"rsqb\":\"]\",\"rsquo\":\"’\",\"rsquor\":\"’\",\"rthree\":\"⋌\",\"rtimes\":\"⋊\",\"rtri\":\"▹\",\"rtrie\":\"⊵\",\"rtrif\":\"▸\",\"rtriltri\":\"⧎\",\"RuleDelayed\":\"⧴\",\"ruluhar\":\"⥨\",\"rx\":\"℞\",\"Sacute\":\"Ś\",\"sacute\":\"ś\",\"sbquo\":\"‚\",\"scap\":\"⪸\",\"Scaron\":\"Š\",\"scaron\":\"š\",\"Sc\":\"⪼\",\"sc\":\"≻\",\"sccue\":\"≽\",\"sce\":\"⪰\",\"scE\":\"⪴\",\"Scedil\":\"Ş\",\"scedil\":\"ş\",\"Scirc\":\"Ŝ\",\"scirc\":\"ŝ\",\"scnap\":\"⪺\",\"scnE\":\"⪶\",\"scnsim\":\"⋩\",\"scpolint\":\"⨓\",\"scsim\":\"≿\",\"Scy\":\"С\",\"scy\":\"с\",\"sdotb\":\"⊡\",\"sdot\":\"⋅\",\"sdote\":\"⩦\",\"searhk\":\"⤥\",\"searr\":\"↘\",\"seArr\":\"⇘\",\"searrow\":\"↘\",\"sect\":\"§\",\"semi\":\";\",\"seswar\":\"⤩\",\"setminus\":\"∖\",\"setmn\":\"∖\",\"sext\":\"✶\",\"Sfr\":\"𝔖\",\"sfr\":\"𝔰\",\"sfrown\":\"⌢\",\"sharp\":\"♯\",\"SHCHcy\":\"Щ\",\"shchcy\":\"щ\",\"SHcy\":\"Ш\",\"shcy\":\"ш\",\"ShortDownArrow\":\"↓\",\"ShortLeftArrow\":\"←\",\"shortmid\":\"∣\",\"shortparallel\":\"∥\",\"ShortRightArrow\":\"→\",\"ShortUpArrow\":\"↑\",\"shy\":\"­\",\"Sigma\":\"Σ\",\"sigma\":\"σ\",\"sigmaf\":\"ς\",\"sigmav\":\"ς\",\"sim\":\"∼\",\"simdot\":\"⩪\",\"sime\":\"≃\",\"simeq\":\"≃\",\"simg\":\"⪞\",\"simgE\":\"⪠\",\"siml\":\"⪝\",\"simlE\":\"⪟\",\"simne\":\"≆\",\"simplus\":\"⨤\",\"simrarr\":\"⥲\",\"slarr\":\"←\",\"SmallCircle\":\"∘\",\"smallsetminus\":\"∖\",\"smashp\":\"⨳\",\"smeparsl\":\"⧤\",\"smid\":\"∣\",\"smile\":\"⌣\",\"smt\":\"⪪\",\"smte\":\"⪬\",\"smtes\":\"⪬︀\",\"SOFTcy\":\"Ь\",\"softcy\":\"ь\",\"solbar\":\"⌿\",\"solb\":\"⧄\",\"sol\":\"/\",\"Sopf\":\"𝕊\",\"sopf\":\"𝕤\",\"spades\":\"♠\",\"spadesuit\":\"♠\",\"spar\":\"∥\",\"sqcap\":\"⊓\",\"sqcaps\":\"⊓︀\",\"sqcup\":\"⊔\",\"sqcups\":\"⊔︀\",\"Sqrt\":\"√\",\"sqsub\":\"⊏\",\"sqsube\":\"⊑\",\"sqsubset\":\"⊏\",\"sqsubseteq\":\"⊑\",\"sqsup\":\"⊐\",\"sqsupe\":\"⊒\",\"sqsupset\":\"⊐\",\"sqsupseteq\":\"⊒\",\"square\":\"□\",\"Square\":\"□\",\"SquareIntersection\":\"⊓\",\"SquareSubset\":\"⊏\",\"SquareSubsetEqual\":\"⊑\",\"SquareSuperset\":\"⊐\",\"SquareSupersetEqual\":\"⊒\",\"SquareUnion\":\"⊔\",\"squarf\":\"▪\",\"squ\":\"□\",\"squf\":\"▪\",\"srarr\":\"→\",\"Sscr\":\"𝒮\",\"sscr\":\"𝓈\",\"ssetmn\":\"∖\",\"ssmile\":\"⌣\",\"sstarf\":\"⋆\",\"Star\":\"⋆\",\"star\":\"☆\",\"starf\":\"★\",\"straightepsilon\":\"ϵ\",\"straightphi\":\"ϕ\",\"strns\":\"¯\",\"sub\":\"⊂\",\"Sub\":\"⋐\",\"subdot\":\"⪽\",\"subE\":\"⫅\",\"sube\":\"⊆\",\"subedot\":\"⫃\",\"submult\":\"⫁\",\"subnE\":\"⫋\",\"subne\":\"⊊\",\"subplus\":\"⪿\",\"subrarr\":\"⥹\",\"subset\":\"⊂\",\"Subset\":\"⋐\",\"subseteq\":\"⊆\",\"subseteqq\":\"⫅\",\"SubsetEqual\":\"⊆\",\"subsetneq\":\"⊊\",\"subsetneqq\":\"⫋\",\"subsim\":\"⫇\",\"subsub\":\"⫕\",\"subsup\":\"⫓\",\"succapprox\":\"⪸\",\"succ\":\"≻\",\"succcurlyeq\":\"≽\",\"Succeeds\":\"≻\",\"SucceedsEqual\":\"⪰\",\"SucceedsSlantEqual\":\"≽\",\"SucceedsTilde\":\"≿\",\"succeq\":\"⪰\",\"succnapprox\":\"⪺\",\"succneqq\":\"⪶\",\"succnsim\":\"⋩\",\"succsim\":\"≿\",\"SuchThat\":\"∋\",\"sum\":\"∑\",\"Sum\":\"∑\",\"sung\":\"♪\",\"sup1\":\"¹\",\"sup2\":\"²\",\"sup3\":\"³\",\"sup\":\"⊃\",\"Sup\":\"⋑\",\"supdot\":\"⪾\",\"supdsub\":\"⫘\",\"supE\":\"⫆\",\"supe\":\"⊇\",\"supedot\":\"⫄\",\"Superset\":\"⊃\",\"SupersetEqual\":\"⊇\",\"suphsol\":\"⟉\",\"suphsub\":\"⫗\",\"suplarr\":\"⥻\",\"supmult\":\"⫂\",\"supnE\":\"⫌\",\"supne\":\"⊋\",\"supplus\":\"⫀\",\"supset\":\"⊃\",\"Supset\":\"⋑\",\"supseteq\":\"⊇\",\"supseteqq\":\"⫆\",\"supsetneq\":\"⊋\",\"supsetneqq\":\"⫌\",\"supsim\":\"⫈\",\"supsub\":\"⫔\",\"supsup\":\"⫖\",\"swarhk\":\"⤦\",\"swarr\":\"↙\",\"swArr\":\"⇙\",\"swarrow\":\"↙\",\"swnwar\":\"⤪\",\"szlig\":\"ß\",\"Tab\":\"\\t\",\"target\":\"⌖\",\"Tau\":\"Τ\",\"tau\":\"τ\",\"tbrk\":\"⎴\",\"Tcaron\":\"Ť\",\"tcaron\":\"ť\",\"Tcedil\":\"Ţ\",\"tcedil\":\"ţ\",\"Tcy\":\"Т\",\"tcy\":\"т\",\"tdot\":\"⃛\",\"telrec\":\"⌕\",\"Tfr\":\"𝔗\",\"tfr\":\"𝔱\",\"there4\":\"∴\",\"therefore\":\"∴\",\"Therefore\":\"∴\",\"Theta\":\"Θ\",\"theta\":\"θ\",\"thetasym\":\"ϑ\",\"thetav\":\"ϑ\",\"thickapprox\":\"≈\",\"thicksim\":\"∼\",\"ThickSpace\":\"  \",\"ThinSpace\":\" \",\"thinsp\":\" \",\"thkap\":\"≈\",\"thksim\":\"∼\",\"THORN\":\"Þ\",\"thorn\":\"þ\",\"tilde\":\"˜\",\"Tilde\":\"∼\",\"TildeEqual\":\"≃\",\"TildeFullEqual\":\"≅\",\"TildeTilde\":\"≈\",\"timesbar\":\"⨱\",\"timesb\":\"⊠\",\"times\":\"×\",\"timesd\":\"⨰\",\"tint\":\"∭\",\"toea\":\"⤨\",\"topbot\":\"⌶\",\"topcir\":\"⫱\",\"top\":\"⊤\",\"Topf\":\"𝕋\",\"topf\":\"𝕥\",\"topfork\":\"⫚\",\"tosa\":\"⤩\",\"tprime\":\"‴\",\"trade\":\"™\",\"TRADE\":\"™\",\"triangle\":\"▵\",\"triangledown\":\"▿\",\"triangleleft\":\"◃\",\"trianglelefteq\":\"⊴\",\"triangleq\":\"≜\",\"triangleright\":\"▹\",\"trianglerighteq\":\"⊵\",\"tridot\":\"◬\",\"trie\":\"≜\",\"triminus\":\"⨺\",\"TripleDot\":\"⃛\",\"triplus\":\"⨹\",\"trisb\":\"⧍\",\"tritime\":\"⨻\",\"trpezium\":\"⏢\",\"Tscr\":\"𝒯\",\"tscr\":\"𝓉\",\"TScy\":\"Ц\",\"tscy\":\"ц\",\"TSHcy\":\"Ћ\",\"tshcy\":\"ћ\",\"Tstrok\":\"Ŧ\",\"tstrok\":\"ŧ\",\"twixt\":\"≬\",\"twoheadleftarrow\":\"↞\",\"twoheadrightarrow\":\"↠\",\"Uacute\":\"Ú\",\"uacute\":\"ú\",\"uarr\":\"↑\",\"Uarr\":\"↟\",\"uArr\":\"⇑\",\"Uarrocir\":\"⥉\",\"Ubrcy\":\"Ў\",\"ubrcy\":\"ў\",\"Ubreve\":\"Ŭ\",\"ubreve\":\"ŭ\",\"Ucirc\":\"Û\",\"ucirc\":\"û\",\"Ucy\":\"У\",\"ucy\":\"у\",\"udarr\":\"⇅\",\"Udblac\":\"Ű\",\"udblac\":\"ű\",\"udhar\":\"⥮\",\"ufisht\":\"⥾\",\"Ufr\":\"𝔘\",\"ufr\":\"𝔲\",\"Ugrave\":\"Ù\",\"ugrave\":\"ù\",\"uHar\":\"⥣\",\"uharl\":\"↿\",\"uharr\":\"↾\",\"uhblk\":\"▀\",\"ulcorn\":\"⌜\",\"ulcorner\":\"⌜\",\"ulcrop\":\"⌏\",\"ultri\":\"◸\",\"Umacr\":\"Ū\",\"umacr\":\"ū\",\"uml\":\"¨\",\"UnderBar\":\"_\",\"UnderBrace\":\"⏟\",\"UnderBracket\":\"⎵\",\"UnderParenthesis\":\"⏝\",\"Union\":\"⋃\",\"UnionPlus\":\"⊎\",\"Uogon\":\"Ų\",\"uogon\":\"ų\",\"Uopf\":\"𝕌\",\"uopf\":\"𝕦\",\"UpArrowBar\":\"⤒\",\"uparrow\":\"↑\",\"UpArrow\":\"↑\",\"Uparrow\":\"⇑\",\"UpArrowDownArrow\":\"⇅\",\"updownarrow\":\"↕\",\"UpDownArrow\":\"↕\",\"Updownarrow\":\"⇕\",\"UpEquilibrium\":\"⥮\",\"upharpoonleft\":\"↿\",\"upharpoonright\":\"↾\",\"uplus\":\"⊎\",\"UpperLeftArrow\":\"↖\",\"UpperRightArrow\":\"↗\",\"upsi\":\"υ\",\"Upsi\":\"ϒ\",\"upsih\":\"ϒ\",\"Upsilon\":\"Υ\",\"upsilon\":\"υ\",\"UpTeeArrow\":\"↥\",\"UpTee\":\"⊥\",\"upuparrows\":\"⇈\",\"urcorn\":\"⌝\",\"urcorner\":\"⌝\",\"urcrop\":\"⌎\",\"Uring\":\"Ů\",\"uring\":\"ů\",\"urtri\":\"◹\",\"Uscr\":\"𝒰\",\"uscr\":\"𝓊\",\"utdot\":\"⋰\",\"Utilde\":\"Ũ\",\"utilde\":\"ũ\",\"utri\":\"▵\",\"utrif\":\"▴\",\"uuarr\":\"⇈\",\"Uuml\":\"Ü\",\"uuml\":\"ü\",\"uwangle\":\"⦧\",\"vangrt\":\"⦜\",\"varepsilon\":\"ϵ\",\"varkappa\":\"ϰ\",\"varnothing\":\"∅\",\"varphi\":\"ϕ\",\"varpi\":\"ϖ\",\"varpropto\":\"∝\",\"varr\":\"↕\",\"vArr\":\"⇕\",\"varrho\":\"ϱ\",\"varsigma\":\"ς\",\"varsubsetneq\":\"⊊︀\",\"varsubsetneqq\":\"⫋︀\",\"varsupsetneq\":\"⊋︀\",\"varsupsetneqq\":\"⫌︀\",\"vartheta\":\"ϑ\",\"vartriangleleft\":\"⊲\",\"vartriangleright\":\"⊳\",\"vBar\":\"⫨\",\"Vbar\":\"⫫\",\"vBarv\":\"⫩\",\"Vcy\":\"В\",\"vcy\":\"в\",\"vdash\":\"⊢\",\"vDash\":\"⊨\",\"Vdash\":\"⊩\",\"VDash\":\"⊫\",\"Vdashl\":\"⫦\",\"veebar\":\"⊻\",\"vee\":\"∨\",\"Vee\":\"⋁\",\"veeeq\":\"≚\",\"vellip\":\"⋮\",\"verbar\":\"|\",\"Verbar\":\"‖\",\"vert\":\"|\",\"Vert\":\"‖\",\"VerticalBar\":\"∣\",\"VerticalLine\":\"|\",\"VerticalSeparator\":\"❘\",\"VerticalTilde\":\"≀\",\"VeryThinSpace\":\" \",\"Vfr\":\"𝔙\",\"vfr\":\"𝔳\",\"vltri\":\"⊲\",\"vnsub\":\"⊂⃒\",\"vnsup\":\"⊃⃒\",\"Vopf\":\"𝕍\",\"vopf\":\"𝕧\",\"vprop\":\"∝\",\"vrtri\":\"⊳\",\"Vscr\":\"𝒱\",\"vscr\":\"𝓋\",\"vsubnE\":\"⫋︀\",\"vsubne\":\"⊊︀\",\"vsupnE\":\"⫌︀\",\"vsupne\":\"⊋︀\",\"Vvdash\":\"⊪\",\"vzigzag\":\"⦚\",\"Wcirc\":\"Ŵ\",\"wcirc\":\"ŵ\",\"wedbar\":\"⩟\",\"wedge\":\"∧\",\"Wedge\":\"⋀\",\"wedgeq\":\"≙\",\"weierp\":\"℘\",\"Wfr\":\"𝔚\",\"wfr\":\"𝔴\",\"Wopf\":\"𝕎\",\"wopf\":\"𝕨\",\"wp\":\"℘\",\"wr\":\"≀\",\"wreath\":\"≀\",\"Wscr\":\"𝒲\",\"wscr\":\"𝓌\",\"xcap\":\"⋂\",\"xcirc\":\"◯\",\"xcup\":\"⋃\",\"xdtri\":\"▽\",\"Xfr\":\"𝔛\",\"xfr\":\"𝔵\",\"xharr\":\"⟷\",\"xhArr\":\"⟺\",\"Xi\":\"Ξ\",\"xi\":\"ξ\",\"xlarr\":\"⟵\",\"xlArr\":\"⟸\",\"xmap\":\"⟼\",\"xnis\":\"⋻\",\"xodot\":\"⨀\",\"Xopf\":\"𝕏\",\"xopf\":\"𝕩\",\"xoplus\":\"⨁\",\"xotime\":\"⨂\",\"xrarr\":\"⟶\",\"xrArr\":\"⟹\",\"Xscr\":\"𝒳\",\"xscr\":\"𝓍\",\"xsqcup\":\"⨆\",\"xuplus\":\"⨄\",\"xutri\":\"△\",\"xvee\":\"⋁\",\"xwedge\":\"⋀\",\"Yacute\":\"Ý\",\"yacute\":\"ý\",\"YAcy\":\"Я\",\"yacy\":\"я\",\"Ycirc\":\"Ŷ\",\"ycirc\":\"ŷ\",\"Ycy\":\"Ы\",\"ycy\":\"ы\",\"yen\":\"¥\",\"Yfr\":\"𝔜\",\"yfr\":\"𝔶\",\"YIcy\":\"Ї\",\"yicy\":\"ї\",\"Yopf\":\"𝕐\",\"yopf\":\"𝕪\",\"Yscr\":\"𝒴\",\"yscr\":\"𝓎\",\"YUcy\":\"Ю\",\"yucy\":\"ю\",\"yuml\":\"ÿ\",\"Yuml\":\"Ÿ\",\"Zacute\":\"Ź\",\"zacute\":\"ź\",\"Zcaron\":\"Ž\",\"zcaron\":\"ž\",\"Zcy\":\"З\",\"zcy\":\"з\",\"Zdot\":\"Ż\",\"zdot\":\"ż\",\"zeetrf\":\"ℨ\",\"ZeroWidthSpace\":\"​\",\"Zeta\":\"Ζ\",\"zeta\":\"ζ\",\"zfr\":\"𝔷\",\"Zfr\":\"ℨ\",\"ZHcy\":\"Ж\",\"zhcy\":\"ж\",\"zigrarr\":\"⇝\",\"zopf\":\"𝕫\",\"Zopf\":\"ℤ\",\"Zscr\":\"𝒵\",\"zscr\":\"𝓏\",\"zwj\":\"‍\",\"zwnj\":\"‌\"}"); /***/ }), -/* 239 */ +/* 238 */ /***/ (function(module) { module.exports = JSON.parse("{\"Aacute\":\"Á\",\"aacute\":\"á\",\"Acirc\":\"Â\",\"acirc\":\"â\",\"acute\":\"´\",\"AElig\":\"Æ\",\"aelig\":\"æ\",\"Agrave\":\"À\",\"agrave\":\"à\",\"amp\":\"&\",\"AMP\":\"&\",\"Aring\":\"Å\",\"aring\":\"å\",\"Atilde\":\"Ã\",\"atilde\":\"ã\",\"Auml\":\"Ä\",\"auml\":\"ä\",\"brvbar\":\"¦\",\"Ccedil\":\"Ç\",\"ccedil\":\"ç\",\"cedil\":\"¸\",\"cent\":\"¢\",\"copy\":\"©\",\"COPY\":\"©\",\"curren\":\"¤\",\"deg\":\"°\",\"divide\":\"÷\",\"Eacute\":\"É\",\"eacute\":\"é\",\"Ecirc\":\"Ê\",\"ecirc\":\"ê\",\"Egrave\":\"È\",\"egrave\":\"è\",\"ETH\":\"Ð\",\"eth\":\"ð\",\"Euml\":\"Ë\",\"euml\":\"ë\",\"frac12\":\"½\",\"frac14\":\"¼\",\"frac34\":\"¾\",\"gt\":\">\",\"GT\":\">\",\"Iacute\":\"Í\",\"iacute\":\"í\",\"Icirc\":\"Î\",\"icirc\":\"î\",\"iexcl\":\"¡\",\"Igrave\":\"Ì\",\"igrave\":\"ì\",\"iquest\":\"¿\",\"Iuml\":\"Ï\",\"iuml\":\"ï\",\"laquo\":\"«\",\"lt\":\"<\",\"LT\":\"<\",\"macr\":\"¯\",\"micro\":\"µ\",\"middot\":\"·\",\"nbsp\":\" \",\"not\":\"¬\",\"Ntilde\":\"Ñ\",\"ntilde\":\"ñ\",\"Oacute\":\"Ó\",\"oacute\":\"ó\",\"Ocirc\":\"Ô\",\"ocirc\":\"ô\",\"Ograve\":\"Ò\",\"ograve\":\"ò\",\"ordf\":\"ª\",\"ordm\":\"º\",\"Oslash\":\"Ø\",\"oslash\":\"ø\",\"Otilde\":\"Õ\",\"otilde\":\"õ\",\"Ouml\":\"Ö\",\"ouml\":\"ö\",\"para\":\"¶\",\"plusmn\":\"±\",\"pound\":\"£\",\"quot\":\"\\\"\",\"QUOT\":\"\\\"\",\"raquo\":\"»\",\"reg\":\"®\",\"REG\":\"®\",\"sect\":\"§\",\"shy\":\"­\",\"sup1\":\"¹\",\"sup2\":\"²\",\"sup3\":\"³\",\"szlig\":\"ß\",\"THORN\":\"Þ\",\"thorn\":\"þ\",\"times\":\"×\",\"Uacute\":\"Ú\",\"uacute\":\"ú\",\"Ucirc\":\"Û\",\"ucirc\":\"û\",\"Ugrave\":\"Ù\",\"ugrave\":\"ù\",\"uml\":\"¨\",\"Uuml\":\"Ü\",\"uuml\":\"ü\",\"Yacute\":\"Ý\",\"yacute\":\"ý\",\"yen\":\"¥\",\"yuml\":\"ÿ\"}"); /***/ }), -/* 240 */ +/* 239 */ /***/ (function(module) { module.exports = JSON.parse("{\"amp\":\"&\",\"apos\":\"'\",\"gt\":\">\",\"lt\":\"<\",\"quot\":\"\\\"\"}"); /***/ }), -/* 241 */ +/* 240 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5101,9 +5100,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DomHandler = void 0; -var domelementtype_1 = __webpack_require__(242); -var node_1 = __webpack_require__(243); -__exportStar(__webpack_require__(243), exports); +var domelementtype_1 = __webpack_require__(241); +var node_1 = __webpack_require__(242); +__exportStar(__webpack_require__(242), exports); var reWhitespace = /\s+/g; // Default options var defaultOpts = { @@ -5263,7 +5262,7 @@ exports.default = DomHandler; /***/ }), -/* 242 */ +/* 241 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5325,7 +5324,7 @@ exports.Doctype = ElementType.Doctype; /***/ }), -/* 243 */ +/* 242 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5358,7 +5357,7 @@ var __assign = (this && this.__assign) || function () { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.cloneNode = exports.hasChildren = exports.isDocument = exports.isDirective = exports.isComment = exports.isText = exports.isCDATA = exports.isTag = exports.Element = exports.Document = exports.NodeWithChildren = exports.ProcessingInstruction = exports.Comment = exports.Text = exports.DataNode = exports.Node = void 0; -var domelementtype_1 = __webpack_require__(242); +var domelementtype_1 = __webpack_require__(241); var nodeTypes = new Map([ [domelementtype_1.ElementType.Tag, 1], [domelementtype_1.ElementType.Script, 1], @@ -5776,7 +5775,7 @@ function cloneChildren(childs) { /***/ }), -/* 244 */ +/* 243 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5820,9 +5819,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseFeed = exports.FeedHandler = void 0; -var domhandler_1 = __importDefault(__webpack_require__(241)); -var DomUtils = __importStar(__webpack_require__(245)); -var Parser_1 = __webpack_require__(234); +var domhandler_1 = __importDefault(__webpack_require__(240)); +var DomUtils = __importStar(__webpack_require__(244)); +var Parser_1 = __webpack_require__(233); var FeedItemMediaMedium; (function (FeedItemMediaMedium) { FeedItemMediaMedium[FeedItemMediaMedium["image"] = 0] = "image"; @@ -6018,7 +6017,7 @@ exports.parseFeed = parseFeed; /***/ }), -/* 245 */ +/* 244 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6035,15 +6034,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.hasChildren = exports.isDocument = exports.isComment = exports.isText = exports.isCDATA = exports.isTag = void 0; -__exportStar(__webpack_require__(246), exports); +__exportStar(__webpack_require__(245), exports); +__exportStar(__webpack_require__(251), exports); __exportStar(__webpack_require__(252), exports); __exportStar(__webpack_require__(253), exports); __exportStar(__webpack_require__(254), exports); __exportStar(__webpack_require__(255), exports); __exportStar(__webpack_require__(256), exports); -__exportStar(__webpack_require__(257), exports); /** @deprecated Use these methods from `domhandler` directly. */ -var domhandler_1 = __webpack_require__(241); +var domhandler_1 = __webpack_require__(240); Object.defineProperty(exports, "isTag", { enumerable: true, get: function () { return domhandler_1.isTag; } }); Object.defineProperty(exports, "isCDATA", { enumerable: true, get: function () { return domhandler_1.isCDATA; } }); Object.defineProperty(exports, "isText", { enumerable: true, get: function () { return domhandler_1.isText; } }); @@ -6053,7 +6052,7 @@ Object.defineProperty(exports, "hasChildren", { enumerable: true, get: function /***/ }), -/* 246 */ +/* 245 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6063,9 +6062,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.innerText = exports.textContent = exports.getText = exports.getInnerHTML = exports.getOuterHTML = void 0; -var domhandler_1 = __webpack_require__(241); -var dom_serializer_1 = __importDefault(__webpack_require__(247)); -var domelementtype_1 = __webpack_require__(242); +var domhandler_1 = __webpack_require__(240); +var dom_serializer_1 = __importDefault(__webpack_require__(246)); +var domelementtype_1 = __webpack_require__(241); /** * @param node Node to get the outer HTML of. * @param options Options for serialization. @@ -6146,7 +6145,7 @@ exports.innerText = innerText; /***/ }), -/* 247 */ +/* 246 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6185,15 +6184,15 @@ Object.defineProperty(exports, "__esModule", { value: true }); /* * Module dependencies */ -var ElementType = __importStar(__webpack_require__(242)); -var entities_1 = __webpack_require__(248); +var ElementType = __importStar(__webpack_require__(241)); +var entities_1 = __webpack_require__(247); /** * 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_1 = __webpack_require__(251); +var foreignNames_1 = __webpack_require__(250); var unencodedElements = new Set([ "style", "script", @@ -6364,15 +6363,15 @@ function renderComment(elem) { /***/ }), -/* 248 */ +/* 247 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeXMLStrict = exports.decodeHTML5Strict = exports.decodeHTML4Strict = exports.decodeHTML5 = exports.decodeHTML4 = exports.decodeHTMLStrict = exports.decodeHTML = exports.decodeXML = exports.encodeHTML5 = exports.encodeHTML4 = exports.escapeUTF8 = exports.escape = exports.encodeNonAsciiHTML = exports.encodeHTML = exports.encodeXML = exports.encode = exports.decodeStrict = exports.decode = void 0; -var decode_1 = __webpack_require__(249); -var encode_1 = __webpack_require__(250); +var decode_1 = __webpack_require__(248); +var encode_1 = __webpack_require__(249); /** * Decodes a string with entities. * @@ -6406,7 +6405,7 @@ function encode(data, level) { return (!level || level <= 0 ? encode_1.encodeXML : encode_1.encodeHTML)(data); } exports.encode = encode; -var encode_2 = __webpack_require__(250); +var encode_2 = __webpack_require__(249); Object.defineProperty(exports, "encodeXML", { enumerable: true, get: function () { return encode_2.encodeXML; } }); Object.defineProperty(exports, "encodeHTML", { enumerable: true, get: function () { return encode_2.encodeHTML; } }); Object.defineProperty(exports, "encodeNonAsciiHTML", { enumerable: true, get: function () { return encode_2.encodeNonAsciiHTML; } }); @@ -6415,7 +6414,7 @@ Object.defineProperty(exports, "escapeUTF8", { enumerable: true, get: function ( // Legacy aliases (deprecated) Object.defineProperty(exports, "encodeHTML4", { enumerable: true, get: function () { return encode_2.encodeHTML; } }); Object.defineProperty(exports, "encodeHTML5", { enumerable: true, get: function () { return encode_2.encodeHTML; } }); -var decode_2 = __webpack_require__(249); +var decode_2 = __webpack_require__(248); Object.defineProperty(exports, "decodeXML", { enumerable: true, get: function () { return decode_2.decodeXML; } }); Object.defineProperty(exports, "decodeHTML", { enumerable: true, get: function () { return decode_2.decodeHTML; } }); Object.defineProperty(exports, "decodeHTMLStrict", { enumerable: true, get: function () { return decode_2.decodeHTMLStrict; } }); @@ -6428,7 +6427,7 @@ Object.defineProperty(exports, "decodeXMLStrict", { enumerable: true, get: funct /***/ }), -/* 249 */ +/* 248 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6438,10 +6437,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeHTML = exports.decodeHTMLStrict = exports.decodeXML = void 0; -var entities_json_1 = __importDefault(__webpack_require__(238)); -var legacy_json_1 = __importDefault(__webpack_require__(239)); -var xml_json_1 = __importDefault(__webpack_require__(240)); -var decode_codepoint_1 = __importDefault(__webpack_require__(236)); +var entities_json_1 = __importDefault(__webpack_require__(237)); +var legacy_json_1 = __importDefault(__webpack_require__(238)); +var xml_json_1 = __importDefault(__webpack_require__(239)); +var decode_codepoint_1 = __importDefault(__webpack_require__(235)); var strictEntityRe = /&(?:[a-zA-Z0-9]+|#[xX][\da-fA-F]+|#\d+);/g; exports.decodeXML = getStrictDecoder(xml_json_1.default); exports.decodeHTMLStrict = getStrictDecoder(entities_json_1.default); @@ -6488,7 +6487,7 @@ function getReplacer(map) { /***/ }), -/* 250 */ +/* 249 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6498,7 +6497,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.escapeUTF8 = exports.escape = exports.encodeNonAsciiHTML = exports.encodeHTML = exports.encodeXML = void 0; -var xml_json_1 = __importDefault(__webpack_require__(240)); +var xml_json_1 = __importDefault(__webpack_require__(239)); var inverseXML = getInverseObj(xml_json_1.default); var xmlReplacer = getInverseReplacer(inverseXML); /** @@ -6509,7 +6508,7 @@ var xmlReplacer = getInverseReplacer(inverseXML); * numeric hexadecimal reference (eg. `ü`) will be used. */ exports.encodeXML = getASCIIEncoder(inverseXML); -var entities_json_1 = __importDefault(__webpack_require__(238)); +var entities_json_1 = __importDefault(__webpack_require__(237)); var inverseHTML = getInverseObj(entities_json_1.default); var htmlReplacer = getInverseReplacer(inverseHTML); /** @@ -6631,7 +6630,7 @@ function getASCIIEncoder(obj) { /***/ }), -/* 251 */ +/* 250 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6741,14 +6740,14 @@ exports.attributeNames = new Map([ /***/ }), -/* 252 */ +/* 251 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.prevElementSibling = exports.nextElementSibling = exports.getName = exports.hasAttrib = exports.getAttributeValue = exports.getSiblings = exports.getParent = exports.getChildren = void 0; -var domhandler_1 = __webpack_require__(241); +var domhandler_1 = __webpack_require__(240); var emptyArray = []; /** * Get a node's children. @@ -6865,7 +6864,7 @@ exports.prevElementSibling = prevElementSibling; /***/ }), -/* 253 */ +/* 252 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -7001,14 +7000,14 @@ exports.prepend = prepend; /***/ }), -/* 254 */ +/* 253 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findAll = exports.existsOne = exports.findOne = exports.findOneChild = exports.find = exports.filter = void 0; -var domhandler_1 = __webpack_require__(241); +var domhandler_1 = __webpack_require__(240); /** * Search a node and its children for nodes passing a test function. * @@ -7134,15 +7133,15 @@ exports.findAll = findAll; /***/ }), -/* 255 */ +/* 254 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getElementsByTagType = exports.getElementsByTagName = exports.getElementById = exports.getElements = exports.testElement = void 0; -var domhandler_1 = __webpack_require__(241); -var querying_1 = __webpack_require__(254); +var domhandler_1 = __webpack_require__(240); +var querying_1 = __webpack_require__(253); var Checks = { tag_name: function (name) { if (typeof name === "function") { @@ -7265,14 +7264,14 @@ exports.getElementsByTagType = getElementsByTagType; /***/ }), -/* 256 */ +/* 255 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.uniqueSort = exports.compareDocumentPosition = exports.removeSubsets = void 0; -var domhandler_1 = __webpack_require__(241); +var domhandler_1 = __webpack_require__(240); /** * Given an array of nodes, remove any member that is contained by another. * @@ -7397,15 +7396,15 @@ exports.uniqueSort = uniqueSort; /***/ }), -/* 257 */ +/* 256 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFeed = void 0; -var stringify_1 = __webpack_require__(246); -var legacy_1 = __webpack_require__(255); +var stringify_1 = __webpack_require__(245); +var legacy_1 = __webpack_require__(254); /** * Get the feed object from the root of a DOM tree. * @@ -7594,7 +7593,7 @@ function isValidFeed(value) { /***/ }), -/* 258 */ +/* 257 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -7614,7 +7613,7 @@ module.exports = string => { /***/ }), -/* 259 */ +/* 258 */ /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; @@ -7657,7 +7656,7 @@ function isPlainObject(o) { /***/ }), -/* 260 */ +/* 259 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -7797,7 +7796,7 @@ module.exports = deepmerge_1; /***/ }), -/* 261 */ +/* 260 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/** @@ -8128,30 +8127,30 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 262 */ +/* 261 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(process) { -let CssSyntaxError = __webpack_require__(263) -let Declaration = __webpack_require__(266) -let LazyResult = __webpack_require__(271) -let Container = __webpack_require__(280) -let Processor = __webpack_require__(293) -let stringify = __webpack_require__(270) -let fromJSON = __webpack_require__(295) -let Document = __webpack_require__(282) -let Warning = __webpack_require__(285) -let Comment = __webpack_require__(281) -let AtRule = __webpack_require__(289) -let Result = __webpack_require__(284) -let Input = __webpack_require__(276) -let parse = __webpack_require__(286) -let list = __webpack_require__(292) -let Rule = __webpack_require__(291) -let Root = __webpack_require__(290) -let Node = __webpack_require__(267) +let CssSyntaxError = __webpack_require__(262) +let Declaration = __webpack_require__(265) +let LazyResult = __webpack_require__(270) +let Container = __webpack_require__(279) +let Processor = __webpack_require__(292) +let stringify = __webpack_require__(269) +let fromJSON = __webpack_require__(294) +let Document = __webpack_require__(281) +let Warning = __webpack_require__(284) +let Comment = __webpack_require__(280) +let AtRule = __webpack_require__(288) +let Result = __webpack_require__(283) +let Input = __webpack_require__(275) +let parse = __webpack_require__(285) +let list = __webpack_require__(291) +let Rule = __webpack_require__(290) +let Root = __webpack_require__(289) +let Node = __webpack_require__(266) function postcss(...plugins) { if (plugins.length === 1 && Array.isArray(plugins[0])) { @@ -8235,15 +8234,15 @@ postcss.default = postcss /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(13))) /***/ }), -/* 263 */ +/* 262 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let pico = __webpack_require__(264) +let pico = __webpack_require__(263) -let terminalHighlight = __webpack_require__(265) +let terminalHighlight = __webpack_require__(264) class CssSyntaxError extends Error { constructor(message, line, column, source, file, plugin) { @@ -8342,7 +8341,7 @@ CssSyntaxError.default = CssSyntaxError /***/ }), -/* 264 */ +/* 263 */ /***/ (function(module, exports) { var x=String; @@ -8352,19 +8351,19 @@ module.exports.createColors = create; /***/ }), -/* 265 */ +/* 264 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 266 */ +/* 265 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Node = __webpack_require__(267) +let Node = __webpack_require__(266) class Declaration extends Node { constructor(defaults) { @@ -8389,16 +8388,16 @@ Declaration.default = Declaration /***/ }), -/* 267 */ +/* 266 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let { isClean, my } = __webpack_require__(268) -let CssSyntaxError = __webpack_require__(263) -let Stringifier = __webpack_require__(269) -let stringify = __webpack_require__(270) +let { isClean, my } = __webpack_require__(267) +let CssSyntaxError = __webpack_require__(262) +let Stringifier = __webpack_require__(268) +let stringify = __webpack_require__(269) function cloneNode(obj, parent) { let cloned = new obj.constructor() @@ -8775,7 +8774,7 @@ Node.default = Node /***/ }), -/* 268 */ +/* 267 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -8787,7 +8786,7 @@ module.exports.my = Symbol('my') /***/ }), -/* 269 */ +/* 268 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -9147,13 +9146,13 @@ Stringifier.default = Stringifier /***/ }), -/* 270 */ +/* 269 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Stringifier = __webpack_require__(269) +let Stringifier = __webpack_require__(268) function stringify(node, builder) { let str = new Stringifier(builder) @@ -9165,21 +9164,21 @@ stringify.default = stringify /***/ }), -/* 271 */ +/* 270 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let { isClean, my } = __webpack_require__(268) -let MapGenerator = __webpack_require__(272) -let stringify = __webpack_require__(270) -let Container = __webpack_require__(280) -let Document = __webpack_require__(282) -let warnOnce = __webpack_require__(283) -let Result = __webpack_require__(284) -let parse = __webpack_require__(286) -let Root = __webpack_require__(290) +let { isClean, my } = __webpack_require__(267) +let MapGenerator = __webpack_require__(271) +let stringify = __webpack_require__(269) +let Container = __webpack_require__(279) +let Document = __webpack_require__(281) +let warnOnce = __webpack_require__(282) +let Result = __webpack_require__(283) +let parse = __webpack_require__(285) +let Root = __webpack_require__(289) const TYPE_TO_CLASS_NAME = { document: 'Document', @@ -9722,17 +9721,17 @@ Document.registerLazyResult(LazyResult) /***/ }), -/* 272 */ +/* 271 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(Buffer) { -let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(273) -let { dirname, resolve, relative, sep } = __webpack_require__(274) -let { pathToFileURL } = __webpack_require__(275) +let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(272) +let { dirname, resolve, relative, sep } = __webpack_require__(273) +let { pathToFileURL } = __webpack_require__(274) -let Input = __webpack_require__(276) +let Input = __webpack_require__(275) let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) let pathAvailable = Boolean(dirname && resolve && relative && sep) @@ -10061,38 +10060,38 @@ module.exports = MapGenerator /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(7).Buffer)) /***/ }), -/* 273 */ +/* 272 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 274 */ +/* 273 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 275 */ +/* 274 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 276 */ +/* 275 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(273) -let { fileURLToPath, pathToFileURL } = __webpack_require__(275) -let { resolve, isAbsolute } = __webpack_require__(274) -let { nanoid } = __webpack_require__(277) +let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(272) +let { fileURLToPath, pathToFileURL } = __webpack_require__(274) +let { resolve, isAbsolute } = __webpack_require__(273) +let { nanoid } = __webpack_require__(276) -let terminalHighlight = __webpack_require__(265) -let CssSyntaxError = __webpack_require__(263) -let PreviousMap = __webpack_require__(278) +let terminalHighlight = __webpack_require__(264) +let CssSyntaxError = __webpack_require__(262) +let PreviousMap = __webpack_require__(277) let fromOffsetCache = Symbol('fromOffsetCache') @@ -10334,7 +10333,7 @@ if (terminalHighlight && terminalHighlight.registerInput) { /***/ }), -/* 277 */ +/* 276 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -10365,15 +10364,15 @@ let nanoid = (size = 21) => { /***/ }), -/* 278 */ +/* 277 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(Buffer) { -let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(273) -let { existsSync, readFileSync } = __webpack_require__(279) -let { dirname, join } = __webpack_require__(274) +let { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(272) +let { existsSync, readFileSync } = __webpack_require__(278) +let { dirname, join } = __webpack_require__(273) function fromBase64(str) { if (Buffer) { @@ -10515,22 +10514,22 @@ PreviousMap.default = PreviousMap /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(7).Buffer)) /***/ }), -/* 279 */ +/* 278 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 280 */ +/* 279 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let { isClean, my } = __webpack_require__(268) -let Declaration = __webpack_require__(266) -let Comment = __webpack_require__(281) -let Node = __webpack_require__(267) +let { isClean, my } = __webpack_require__(267) +let Declaration = __webpack_require__(265) +let Comment = __webpack_require__(280) +let Node = __webpack_require__(266) let parse, Rule, AtRule @@ -10961,13 +10960,13 @@ Container.rebuild = node => { /***/ }), -/* 281 */ +/* 280 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Node = __webpack_require__(267) +let Node = __webpack_require__(266) class Comment extends Node { constructor(defaults) { @@ -10981,13 +10980,13 @@ Comment.default = Comment /***/ }), -/* 282 */ +/* 281 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Container = __webpack_require__(280) +let Container = __webpack_require__(279) let LazyResult, Processor @@ -11021,7 +11020,7 @@ Document.default = Document /***/ }), -/* 283 */ +/* 282 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11041,13 +11040,13 @@ module.exports = function warnOnce(message) { /***/ }), -/* 284 */ +/* 283 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Warning = __webpack_require__(285) +let Warning = __webpack_require__(284) class Result { constructor(processor, root, opts) { @@ -11090,7 +11089,7 @@ Result.default = Result /***/ }), -/* 285 */ +/* 284 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11134,15 +11133,15 @@ Warning.default = Warning /***/ }), -/* 286 */ +/* 285 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Container = __webpack_require__(280) -let Parser = __webpack_require__(287) -let Input = __webpack_require__(276) +let Container = __webpack_require__(279) +let Parser = __webpack_require__(286) +let Input = __webpack_require__(275) function parse(css, opts) { let input = new Input(css, opts) @@ -11183,18 +11182,18 @@ Container.registerParse(parse) /***/ }), -/* 287 */ +/* 286 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Declaration = __webpack_require__(266) -let tokenizer = __webpack_require__(288) -let Comment = __webpack_require__(281) -let AtRule = __webpack_require__(289) -let Root = __webpack_require__(290) -let Rule = __webpack_require__(291) +let Declaration = __webpack_require__(265) +let tokenizer = __webpack_require__(287) +let Comment = __webpack_require__(280) +let AtRule = __webpack_require__(288) +let Root = __webpack_require__(289) +let Rule = __webpack_require__(290) class Parser { constructor(input) { @@ -11775,7 +11774,7 @@ module.exports = Parser /***/ }), -/* 288 */ +/* 287 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -12048,13 +12047,13 @@ module.exports = function tokenizer(input, options = {}) { /***/ }), -/* 289 */ +/* 288 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Container = __webpack_require__(280) +let Container = __webpack_require__(279) class AtRule extends Container { constructor(defaults) { @@ -12080,13 +12079,13 @@ Container.registerAtRule(AtRule) /***/ }), -/* 290 */ +/* 289 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Container = __webpack_require__(280) +let Container = __webpack_require__(279) let LazyResult, Processor @@ -12146,14 +12145,14 @@ Root.default = Root /***/ }), -/* 291 */ +/* 290 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Container = __webpack_require__(280) -let list = __webpack_require__(292) +let Container = __webpack_require__(279) +let list = __webpack_require__(291) class Rule extends Container { constructor(defaults) { @@ -12180,7 +12179,7 @@ Container.registerRule(Rule) /***/ }), -/* 292 */ +/* 291 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -12243,16 +12242,16 @@ list.default = list /***/ }), -/* 293 */ +/* 292 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let NoWorkResult = __webpack_require__(294) -let LazyResult = __webpack_require__(271) -let Document = __webpack_require__(282) -let Root = __webpack_require__(290) +let NoWorkResult = __webpack_require__(293) +let LazyResult = __webpack_require__(270) +let Document = __webpack_require__(281) +let Root = __webpack_require__(289) class Processor { constructor(plugins = []) { @@ -12317,17 +12316,17 @@ Document.registerProcessor(Processor) /***/ }), -/* 294 */ +/* 293 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let MapGenerator = __webpack_require__(272) -let stringify = __webpack_require__(270) -let warnOnce = __webpack_require__(283) -let parse = __webpack_require__(286) -const Result = __webpack_require__(284) +let MapGenerator = __webpack_require__(271) +let stringify = __webpack_require__(269) +let warnOnce = __webpack_require__(282) +let parse = __webpack_require__(285) +const Result = __webpack_require__(283) class NoWorkResult { constructor(processor, css, opts) { @@ -12456,19 +12455,19 @@ NoWorkResult.default = NoWorkResult /***/ }), -/* 295 */ +/* 294 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -let Declaration = __webpack_require__(266) -let PreviousMap = __webpack_require__(278) -let Comment = __webpack_require__(281) -let AtRule = __webpack_require__(289) -let Input = __webpack_require__(276) -let Root = __webpack_require__(290) -let Rule = __webpack_require__(291) +let Declaration = __webpack_require__(265) +let PreviousMap = __webpack_require__(277) +let Comment = __webpack_require__(280) +let AtRule = __webpack_require__(288) +let Input = __webpack_require__(275) +let Root = __webpack_require__(289) +let Rule = __webpack_require__(290) function fromJSON(json, inputs) { if (Array.isArray(json)) return json.map(n => fromJSON(n)) @@ -86888,12 +86887,12 @@ const dereq_encoding_japanese = /******/ /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 231); +/******/ return __webpack_require__(__webpack_require__.s = 230); /******/ }) /************************************************************************/ /******/ ({ -/***/ 231: +/***/ 230: /***/ (function(module, exports, __webpack_require__) { var require;var require;/*! @@ -93056,11 +93055,11 @@ const att_1 = __webpack_require__(217); const buf_1 = __webpack_require__(5); const msg_block_1 = __webpack_require__(4); const msg_block_parser_1 = __webpack_require__(219); -const pgp_password_1 = __webpack_require__(229); +const pgp_password_1 = __webpack_require__(228); const store_1 = __webpack_require__(224); const common_1 = __webpack_require__(214); const const_1 = __webpack_require__(226); -const validate_input_1 = __webpack_require__(230); +const validate_input_1 = __webpack_require__(229); const xss_1 = __webpack_require__(220); const const_2 = __webpack_require__(226); const pgp_1 = __webpack_require__(222); @@ -93069,11 +93068,6 @@ class Endpoints { this.version = async () => { return (0, format_output_1.fmtRes)({ app_version: const_1.VERSION }); }; - this.encryptMsg = async (uncheckedReq, data) => { - const req = validate_input_1.ValidateInput.encryptMsg(uncheckedReq); - 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.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); @@ -93094,7 +93088,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 = []; @@ -93110,6 +93104,11 @@ class Endpoints { throw new Error(`Unknown format: ${req.format}`); } }; + this.encryptMsg = async (uncheckedReq, data) => { + const req = validate_input_1.ValidateInput.encryptMsg(uncheckedReq); + const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: req.pubKeys, pwd: req.msgPwd, data: buf_1.Buf.concat(data), armor: true }); + return (0, format_output_1.fmtRes)({}, buf_1.Buf.fromUtfStr(encrypted.data)); + }; this.encryptFile = async (uncheckedReq, data) => { const req = validate_input_1.ValidateInput.encryptFile(uncheckedReq); const encrypted = await pgp_msg_1.PgpMsg.encrypt({ pubkeys: req.pubKeys, data: buf_1.Buf.concat(data), filename: req.name, armor: false }); @@ -190646,7 +190645,6 @@ const buf_1 = __webpack_require__(5); const catch_1 = __webpack_require__(218); const msg_block_parser_1 = __webpack_require__(219); const pgp_armor_1 = __webpack_require__(221); -const pgp_hash_1 = __webpack_require__(228); const store_1 = __webpack_require__(224); const pgp_1 = __webpack_require__(222); var DecryptErrTypes; @@ -190811,7 +190809,6 @@ PgpMsg.decrypt = async ({ kisWithPp, encryptedData, msgPwd, verificationPubkeys PgpMsg.encrypt = async ({ pubkeys, signingPrv, pwd, data, filename, armor, date }) => { const message = pgp_1.openpgp.message.fromBinary(data, filename, date); const options = { armor, message, date }; - let usedChallenge = false; if (pubkeys) { options.publicKeys = []; for (const armoredPubkey of pubkeys) { @@ -190820,10 +190817,9 @@ PgpMsg.encrypt = async ({ pubkeys, signingPrv, pwd, data, filename, armor, date } } if (pwd) { - options.passwords = [await pgp_hash_1.PgpHash.challengeAnswer(pwd)]; - usedChallenge = true; + options.passwords = [pwd]; } - if (!pubkeys && !usedChallenge) { + if (!pubkeys && !pwd) { throw new Error('no-pubkeys-no-challenge'); } if (signingPrv && typeof signingPrv.isPrivate !== 'undefined' && signingPrv.isPrivate()) { // tslint:disable-line:no-unbound-method - only testing if exists @@ -191017,42 +191013,6 @@ PgpMsg.cryptoMsgDecryptCategorizeErr = (decryptErr, msgPwd) => { "use strict"; /* © 2016-present FlowCrypt a. s. Limitations apply. Contact human@flowcrypt.com */ -var _a; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PgpHash = void 0; -const buf_1 = __webpack_require__(5); -const pgp_1 = __webpack_require__(222); -class PgpHash { -} -exports.PgpHash = PgpHash; -_a = PgpHash; -PgpHash.sha1UtfStr = async (string) => { - return pgp_1.openpgp.util.Uint8Array_to_hex(await pgp_1.openpgp.crypto.hash.digest(pgp_1.openpgp.enums.hash.sha1, buf_1.Buf.fromUtfStr(string))); -}; -PgpHash.sha256UtfStr = async (string) => { - return pgp_1.openpgp.util.Uint8Array_to_hex(await pgp_1.openpgp.crypto.hash.digest(pgp_1.openpgp.enums.hash.sha256, buf_1.Buf.fromUtfStr(string))); -}; -PgpHash.doubleSha1Upper = async (string) => { - return (await PgpHash.sha1UtfStr(await PgpHash.sha1UtfStr(string))).toUpperCase(); -}; -PgpHash.challengeAnswer = async (answer) => { - return await PgpHash.cryptoHashSha256Loop(answer); -}; -PgpHash.cryptoHashSha256Loop = async (string, times = 100000) => { - for (let i = 0; i < times; i++) { - string = await PgpHash.sha256UtfStr(string); - } - return string; -}; - - -/***/ }), -/* 229 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* © 2016-present FlowCrypt a. s. Limitations apply. Contact human@flowcrypt.com */ - Object.defineProperty(exports, "__esModule", { value: true }); exports.PgpPwd = void 0; const util_1 = __webpack_require__(6); @@ -191147,7 +191107,7 @@ PgpPwd.readableCrackTime = (totalSeconds) => { /***/ }), -/* 230 */ +/* 229 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -191172,13 +191132,13 @@ ValidateInput.generateKey = (v) => { throw new Error('Wrong request structure for NodeRequest.generateKey'); }; ValidateInput.encryptMsg = (v) => { - if (isObj(v) && hasProp(v, 'pubKeys', 'string[]')) { + if (isObj(v) && hasProp(v, 'pubKeys', 'string[]') && hasProp(v, 'msgPwd', 'string?')) { return v; } throw new Error('Wrong request structure for NodeRequest.encryptMsg'); }; 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[]?')) { diff --git a/FlowCryptAppTests/Core/CoreTypesTest.swift b/FlowCryptAppTests/Core/CoreTypesTest.swift index 57465077d..46d9fe25c 100644 --- a/FlowCryptAppTests/Core/CoreTypesTest.swift +++ b/FlowCryptAppTests/Core/CoreTypesTest.swift @@ -11,6 +11,48 @@ import XCTest class CoreTypesTest: XCTestCase { + func test_sendable_msg_copy() { + let msg = SendableMsg( + text: "this is message", + html: "this is message", + to: ["some@gmail.com"], + cc: [], + bcc: [], + from: "from@gmail.com", + subject: "Some subject", + replyToMimeMsg: nil, + atts: [], + pubKeys: ["public key"], + signingPrv: nil, + password: "123") + + let copyBody = SendableMsgBody( + text: "another message", + html: "another message" + ) + let copyAttachments = [SendableMsg.Attachment( + name: "test.txt", + type: "text/plain", + base64: "test".data().base64EncodedString() + )] + let copyPubKeys = ["another key"] + + let msgCopy = msg.copy( + body: copyBody, + atts: copyAttachments, + pubKeys: copyPubKeys + ) + + XCTAssertEqual(msgCopy.text, copyBody.text) + XCTAssertEqual(msgCopy.html, copyBody.html) + XCTAssertEqual(msgCopy.atts, copyAttachments) + XCTAssertEqual(msgCopy.pubKeys, copyPubKeys) + XCTAssertEqual(msgCopy.to, msg.to) + XCTAssertEqual(msgCopy.from, msg.from) + XCTAssertEqual(msgCopy.subject, msg.subject) + XCTAssertEqual(msgCopy.password, msg.password) + } + func test_key_details_with_same_fingerprints() { let firstKeyDetail = KeyDetails( public: "public1", diff --git a/FlowCryptAppTests/Core/FlowCryptCoreTests.swift b/FlowCryptAppTests/Core/FlowCryptCoreTests.swift index e5f54f610..30d45be69 100644 --- a/FlowCryptAppTests/Core/FlowCryptCoreTests.swift +++ b/FlowCryptAppTests/Core/FlowCryptCoreTests.swift @@ -106,6 +106,7 @@ final class FlowCryptCoreTests: XCTestCase { func testComposeEmailPlain() async throws { let msg = SendableMsg( text: "this is the message", + html: "this is the message", to: ["email@hello.com"], cc: [], bcc: [], @@ -128,6 +129,7 @@ final class FlowCryptCoreTests: XCTestCase { func testComposeEmailEncryptInline() async throws { let msg = SendableMsg( text: "this is the message", + html: "this is the message", to: ["email@hello.com"], cc: [], bcc: [], @@ -158,6 +160,7 @@ final class FlowCryptCoreTests: XCTestCase { ) let msg = SendableMsg( text: "this is the message", + html: "this is the message", to: ["email@hello.com"], cc: [], bcc: [], from: "sender@hello.com", subject: "subj", replyToMimeMsg: nil, @@ -186,6 +189,7 @@ final class FlowCryptCoreTests: XCTestCase { let k = generateKeyRes.key let msg = SendableMsg( text: text, + html: text, to: [email], cc: [], bcc: [], @@ -252,13 +256,13 @@ final class FlowCryptCoreTests: XCTestCase { ] // When - let encrypted = try await core.encryptFile( - pubKeys: [k.public], - fileData: fileData, - name: initialFileName + let encrypted = try await core.encrypt( + file: fileData, + name: initialFileName, + pubKeys: [k.public] ) let decrypted = try await core.decryptFile( - encrypted: encrypted.encryptedFile, + encrypted: encrypted, keys: keys, msgPwd: nil ) @@ -318,13 +322,13 @@ final class FlowCryptCoreTests: XCTestCase { userIds: [UserId(email: email, name: "End to end")] ) let k = generateKeyRes.key - let encrypted = try await core.encryptFile( - pubKeys: [k.public], - fileData: fileData, - name: initialFileName + let encryptedFile = try await core.encrypt( + file: fileData, + name: initialFileName, + pubKeys: [k.public] ) let decryptResult = try await core.decryptFile( - encrypted: encrypted.encryptedFile, + encrypted: encryptedFile, keys: [], msgPwd: nil ) @@ -356,13 +360,13 @@ final class FlowCryptCoreTests: XCTestCase { ] // When - let encrypted = try await core.encryptFile( - pubKeys: [k.public], - fileData: fileData, - name: initialFileName + let encrypted = try await core.encrypt( + file: fileData, + name: initialFileName, + pubKeys: [k.public] ) let decrypted = try await core.decryptFile( - encrypted: encrypted.encryptedFile, + encrypted: encrypted, keys: keys, msgPwd: nil ) diff --git a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift index 4f90e55d4..dc5f50f05 100644 --- a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift +++ b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift @@ -58,7 +58,7 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { return "" } - func upload(message: Data, details: MessageUploadDetails) async throws -> String { + func upload(message: Data, details: MessageUploadDetails, progressHandler: ((Float) -> Void)?) async throws -> String { return "" } } diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index 59d30cfb8..95c86847e 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -333,6 +333,7 @@ class ComposeMessageServiceTests: XCTestCase { let expected = SendableMsg( text: message, + html: nil, to: recipients.map(\.email), cc: [], bcc: [], @@ -407,6 +408,7 @@ class ComposeMessageServiceTests: XCTestCase { let expected = SendableMsg( text: message, + html: nil, to: recipients.map(\.email), cc: [], bcc: [], diff --git a/FlowCryptAppTests/Mocks/CoreComposeMessageMock.swift b/FlowCryptAppTests/Mocks/CoreComposeMessageMock.swift index edcae8624..21919444c 100644 --- a/FlowCryptAppTests/Mocks/CoreComposeMessageMock.swift +++ b/FlowCryptAppTests/Mocks/CoreComposeMessageMock.swift @@ -10,7 +10,6 @@ import Foundation class CoreComposeMessageMock: CoreComposeMessageType, KeyParser { - var composeEmailResult: ((SendableMsg, MsgFmt) -> (CoreRes.ComposeEmail))! func composeEmail(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail { return composeEmailResult(msg, fmt) @@ -20,4 +19,14 @@ class CoreComposeMessageMock: CoreComposeMessageType, KeyParser { func parseKeys(armoredOrBinary: Data) throws -> CoreRes.ParseKeys { return parseKeysResult(armoredOrBinary) } + + var encryptMsgResult: ((Data, [String]?, String?) -> Data)! + func encrypt(data: Data, pubKeys: [String]?, password: String?) async throws -> Data { + return encryptMsgResult(data, pubKeys, password) + } + + var encryptFileResult: ((Data, String, [String]?) -> Data)! + func encrypt(file: Data, name: String, pubKeys: [String]?) async throws -> Data { + return encryptFileResult(file, name, pubKeys) + } } diff --git a/FlowCryptCommon/Extensions/CodableExntensions.swift b/FlowCryptCommon/Extensions/CodableExtensions.swift similarity index 90% rename from FlowCryptCommon/Extensions/CodableExntensions.swift rename to FlowCryptCommon/Extensions/CodableExtensions.swift index e96982b85..1f4dbc541 100644 --- a/FlowCryptCommon/Extensions/CodableExntensions.swift +++ b/FlowCryptCommon/Extensions/CodableExtensions.swift @@ -10,7 +10,7 @@ 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() } 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. diff --git a/FlowCryptCommon/Extensions/URLSessionExtensions.swift b/FlowCryptCommon/Extensions/URLSessionExtensions.swift index 3ebc8fdac..c4edab01e 100644 --- a/FlowCryptCommon/Extensions/URLSessionExtensions.swift +++ b/FlowCryptCommon/Extensions/URLSessionExtensions.swift @@ -35,14 +35,15 @@ public enum HTTPMethod: String { public extension URLSession { static let generalError = -1 - func call(_ urlRequest: URLRequest, tolerateStatus: [Int]? = nil) async throws -> HttpRes { + func call(_ urlRequest: URLRequest, tolerateStatus: [Int]? = nil, delegate: URLSessionTaskDelegate? = nil) async throws -> HttpRes { let trace = Trace(id: "call") var data: Data? var response: URLResponse? var requestError: Error? + do { - (data, response) = try await self.data(for: urlRequest) + (data, response) = try await self.data(for: urlRequest, delegate: delegate) } catch { requestError = error }