Compare commits

..

1 Commits

Author SHA1 Message Date
Chris Ballinger 52d0d63968 Fixing KVO crash 2018-04-29 23:32:45 -07:00
553 changed files with 8304 additions and 6968 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: chrisballinger

5
.gitignore vendored
View File

@ -1,4 +1,3 @@
.env
.DS_Store .DS_Store
build build
*.mode1v3 *.mode1v3
@ -15,7 +14,3 @@ Carthage/
com.mono0926.LicensePlist.Output/ com.mono0926.LicensePlist.Output/
Secrets.plist Secrets.plist
Preview.html
fastlane/report.xml
fastlane/test_output/
.ruby-version

9
.gitmodules vendored
View File

@ -31,12 +31,3 @@
[submodule "Submodules/LumberjackConsole"] [submodule "Submodules/LumberjackConsole"]
path = Submodules/LumberjackConsole path = Submodules/LumberjackConsole
url = git@github.com:ChatSecure/LumberjackConsole.git url = git@github.com:ChatSecure/LumberjackConsole.git
[submodule "Submodules/YapDatabase"]
path = Submodules/YapDatabase
url = git@github.com:ChatSecure/YapDatabase.git
[submodule "Submodules/YapTaskQueue"]
path = Submodules/YapTaskQueue
url = git@github.com:ChatSecure/YapTaskQueue.git
[submodule "Submodules/libsqlfs"]
path = Submodules/libsqlfs
url = git@github.com:ChatSecure/libsqlfs.git

View File

@ -1,4 +1,4 @@
osx_image: xcode12 osx_image: xcode9.3
language: objective-c language: objective-c
# Handle git submodules yourself # Handle git submodules yourself
@ -9,8 +9,9 @@ git:
# Use sed to replace the SSH URL with the public URL, then initialize submodules # Use sed to replace the SSH URL with the public URL, then initialize submodules
before_install: before_install:
# Fix Travis xcodebuild exited with 65 https://github.com/travis-ci/travis-ci/issues/6675#issuecomment-257964767 # Fix Travis xcodebuild exited with 65 https://github.com/travis-ci/travis-ci/issues/6675#issuecomment-257964767
- export IOS_SIMULATOR_UDID=`instruments -s devices | grep -m 1 "iPhone 8 (14" | awk -F '[ ]' '{print $4}' | awk -F '[\[]' '{print $2}' | sed 's/.$//'` - export IOS_SIMULATOR_UDID=`instruments -s devices | grep -m 1 "iPhone 8 (11" | awk -F '[ ]' '{print $4}' | awk -F '[\[]' '{print $2}' | sed 's/.$//'`
- echo $IOS_SIMULATOR_UDID - echo $IOS_SIMULATOR_UDID
- open -a "simulator" --args -CurrentDeviceUDID $IOS_SIMULATOR_UDID
- bundle install # We need a pre-release CocoaPods version - bundle install # We need a pre-release CocoaPods version
- sed -i -e 's/git@github.com:/git:\/\/github.com\//' .gitmodules - sed -i -e 's/git@github.com:/git:\/\/github.com\//' .gitmodules
- sed -i -e 's/git@github.com:/git:\/\/github.com\//' Podfile - sed -i -e 's/git@github.com:/git:\/\/github.com\//' Podfile
@ -21,8 +22,12 @@ install:
- curl -L https://github.com/ChatSecure/ChatSecure-iOS-Precompiled-Dependencies/archive/master.zip -o ChatSecure-iOS-Precompiled-Dependencies.zip - curl -L https://github.com/ChatSecure/ChatSecure-iOS-Precompiled-Dependencies/archive/master.zip -o ChatSecure-iOS-Precompiled-Dependencies.zip
- unzip -q ChatSecure-iOS-Precompiled-Dependencies.zip - unzip -q ChatSecure-iOS-Precompiled-Dependencies.zip
- mv ChatSecure-iOS-Precompiled-Dependencies-master ChatSecure-iOS-Precompiled-Dependencies - mv ChatSecure-iOS-Precompiled-Dependencies-master ChatSecure-iOS-Precompiled-Dependencies
- mkdir -p ./Carthage/Build/iOS/
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Carthage-iOS.zip -d ./Carthage/Build
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/CPAProxyDependencies.zip -d ./Submodules/CPAProxy/ - unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/CPAProxyDependencies.zip -d ./Submodules/CPAProxy/
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/OTRKitDependencies.zip -d ./Submodules/OTRKit/ - mv ./Submodules/CPAProxy/CPAProxyDependencies ./Submodules/CPAProxy/CPAProxyDependencies-iOS
- cp -r ./Submodules/CPAProxy/CPAProxyDependencies-iOS ./Submodules/CPAProxy/CPAProxyDependencies-macOS
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/OTRKitDependencies-iOS.zip -d ./Submodules/OTRKit/
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Pods.zip - unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Pods.zip
before_script: before_script:

16
Cartfile Normal file
View File

@ -0,0 +1,16 @@
github "ChatSecure/Mantle" "2.1.0_headerfix"
github "nolanw/HTMLReader" "1d0dda3"
github "AFNetworking/AFNetworking" ~> 3.1
github "TheLevelUp/ZXingObjC" ~> 3.2.2
github "soffes/SAMKeychain" ~> 1.5
github "jdg/MBProgressHUD" ~> 1.1
github "TTTAttributedLabel/TTTAttributedLabel" ~> 2.0
github "PureLayout/PureLayout" ~> 3.0
github "facebook/KVOController" ~> 1.2
github "xmartlabs/XLForm" ~> 4.0.0
github "mattt/FormatterKit" ~> 1.8
### Using CocoaPods due to Swift 3->4 issues ###
# github "Cocoanetics/Kvitto" ~> 1.0
# github "Cocoanetics/DTFoundation" ~> 1.7
# github "Alamofire/Alamofire" ~> 4.4

11
Cartfile.resolved Normal file
View File

@ -0,0 +1,11 @@
github "AFNetworking/AFNetworking" "3.2.0"
github "ChatSecure/Mantle" "4c1a09cb0c0811956cd35262340e42b940971cbb"
github "PureLayout/PureLayout" "v3.0.2"
github "TTTAttributedLabel/TTTAttributedLabel" "2.0.0"
github "TheLevelUp/ZXingObjC" "3.2.2"
github "facebook/KVOController" "v1.2.0"
github "jdg/MBProgressHUD" "1.1.0"
github "mattt/FormatterKit" "1.8.2"
github "nolanw/HTMLReader" "1d0dda3849ff719fa15a0c4cac0118c70ef2217c"
github "soffes/SAMKeychain" "v1.5.3"
github "xmartlabs/XLForm" "4.0.0"

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
BuildableName = "ChatSecureTests.xctest"
BlueprintName = "ChatSecureTests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "macOS_Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
disableMainThreadChecker = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
BuildableName = "ChatSecureTests.xctest"
BlueprintName = "ChatSecureTests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
BuildableName = "ChatSecureUITests.xctest"
BlueprintName = "ChatSecureUITests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "macOS_Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "macOS_Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "macOS_Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "macOS_Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1200" LastUpgradeVersion = "0930"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -37,20 +37,10 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES">
disableMainThreadChecker = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@ -63,7 +53,17 @@
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
<TestableReference <TestableReference
skipped = "YES"> skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D9227C321BA7952100B5E1D0"
BuildableName = "ChatSecureCoreTests.xctest"
BlueprintName = "ChatSecureCoreTests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "6365CEFB1E2453F6009E213F" BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
@ -73,9 +73,20 @@
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -98,12 +109,14 @@
<EnvironmentVariable <EnvironmentVariable
key = "OS_ACTIVITY_MODE" key = "OS_ACTIVITY_MODE"
value = "disable" value = "disable"
isEnabled = "NO"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
</EnvironmentVariables> </EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
@ -120,10 +133,10 @@
</BuildableProductRunnable> </BuildableProductRunnable>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "iOS_Debug"> buildConfiguration = "Debug">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
revealArchiveInOrganizer = "YES"> revealArchiveInOrganizer = "YES">
</ArchiveAction> </ArchiveAction>
</Scheme> </Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1200" LastUpgradeVersion = "0930"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -10,11 +10,11 @@
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "NO"
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "D9C527FF235CB55C002B213A" BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
BuildableName = "ChatSecureCore.framework" BuildableName = "ChatSecureCore.framework"
BlueprintName = "ChatSecureCore" BlueprintName = "ChatSecureCore"
ReferencedContainer = "container:ChatSecure.xcodeproj"> ReferencedContainer = "container:ChatSecure.xcodeproj">
@ -23,15 +23,26 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
BuildableName = "ChatSecureCore.framework"
BlueprintName = "ChatSecureCore"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -40,9 +51,20 @@
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
BuildableName = "ChatSecureCore.framework"
BlueprintName = "ChatSecureCore"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
@ -50,7 +72,7 @@
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "D9C527FF235CB55C002B213A" BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
BuildableName = "ChatSecureCore.framework" BuildableName = "ChatSecureCore.framework"
BlueprintName = "ChatSecureCore" BlueprintName = "ChatSecureCore"
ReferencedContainer = "container:ChatSecure.xcodeproj"> ReferencedContainer = "container:ChatSecure.xcodeproj">
@ -58,10 +80,10 @@
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "iOS_Debug"> buildConfiguration = "Debug">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
revealArchiveInOrganizer = "YES"> revealArchiveInOrganizer = "YES">
</ArchiveAction> </ArchiveAction>
</Scheme> </Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1200" LastUpgradeVersion = "0930"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -23,19 +23,10 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
BuildableName = "ChatSecureTests.xctest"
BlueprintName = "ChatSecureTests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@ -48,9 +39,20 @@
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
BuildableName = "ChatSecureTests.xctest"
BlueprintName = "ChatSecureTests"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "iOS_Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -75,9 +77,11 @@
isEnabled = "YES"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
</EnvironmentVariables> </EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
@ -93,10 +97,10 @@
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "iOS_Debug"> buildConfiguration = "Debug">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "iOS_Release" buildConfiguration = "Release"
revealArchiveInOrganizer = "YES"> revealArchiveInOrganizer = "YES">
</ArchiveAction> </ArchiveAction>
</Scheme> </Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1200" LastUpgradeVersion = "0930"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -10,8 +10,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES">
disableMainThreadChecker = "YES">
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@ -24,12 +23,22 @@
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
@ -46,6 +55,8 @@
ReferencedContainer = "container:ChatSecure.xcodeproj"> ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@ -8,17 +8,7 @@
<array> <array>
<string>applinks:chatsecure.org</string> <string>applinks:chatsecure.org</string>
</array> </array>
<key>com.apple.security.app-sandbox</key> <key>com.apple.developer.default-data-protection</key>
<true/> <string>NSFileProtectionComplete</string>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -9,14 +9,14 @@
import Foundation import Foundation
extension NSData { public extension NSData {
@objc public func hexString() -> String { @objc public func hexString() -> String {
return (self as Data).hexString() return (self as Data).hexString()
} }
} }
// http://stackoverflow.com/a/26502285/805882 // http://stackoverflow.com/a/26502285/805882
extension NSString { public extension NSString {
/// Create `Data` from hexadecimal string representation /// Create `Data` from hexadecimal string representation
/// ///

View File

@ -63,4 +63,7 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
@interface UIViewController (ChatSecureURL)
- (void) promptToShowURL:(NSURL*)url sender:(id)sender;
@end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -10,7 +10,6 @@
#import "OTRConstants.h" #import "OTRConstants.h"
@import XMPPFramework; @import XMPPFramework;
@import OTRAssets; @import OTRAssets;
#import "ChatSecureCoreCompat-Swift.h"
@implementation NSURL (ChatSecure) @implementation NSURL (ChatSecure)
@ -198,7 +197,7 @@
view = sender; view = sender;
} }
UIAlertAction *visitURL = [UIAlertAction actionWithTitle:OPEN_IN_SAFARI() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { UIAlertAction *visitURL = [UIAlertAction actionWithTitle:OPEN_IN_SAFARI() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] open:self]; [[UIApplication sharedApplication] openURL:self];
}]; }];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil]; UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet];
@ -212,4 +211,12 @@
[viewController presentViewController:alert animated:YES completion:nil]; [viewController presentViewController:alert animated:YES completion:nil];
} }
@end
@implementation UIViewController (ChatSecureURL)
- (void) promptToShowURL:(NSURL*)url sender:(id)sender {
[url promptToShowURLFromViewController:self sender:sender];
}
@end @end

View File

@ -0,0 +1,15 @@
//
// UIActionSheet+ChatSecure.h
// ChatSecure
//
// Created by David Chiles on 10/24/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
@import UIKit;
@interface UIActionSheet (ChatSecure)
- (void)otr_presentInView:(UIView *)view;
@end

View File

@ -0,0 +1,23 @@
//
// UIActionSheet+ChatSecure.m
// ChatSecure
//
// Created by David Chiles on 10/24/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
#import "UIActionSheet+ChatSecure.h"
#import "OTRAppDelegate.h"
@implementation UIActionSheet (ChatSecure)
- (void)otr_presentInView:(UIView *)view
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[self showInView:view];
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self showInView:[OTRAppDelegate appDelegate].window];
}
}
@end

View File

@ -8,6 +8,7 @@
#import "UIActivity+ChatSecure.h" #import "UIActivity+ChatSecure.h"
@import ARChromeActivity; @import ARChromeActivity;
@import TUSafariActivity;
#import "OTROpenInFacebookActivity.h" #import "OTROpenInFacebookActivity.h"
#import "OTROpenInTwitterActivity.h" #import "OTROpenInTwitterActivity.h"
@import OTRAssets; @import OTRAssets;
@ -15,21 +16,22 @@
@implementation UIActivity (ChatSecure) @implementation UIActivity (ChatSecure)
+ (NSArray<UIActivity*>*) otr_linkActivities { + (NSArray<UIActivity*>*) otr_linkActivities {
TUSafariActivity *safariActivity = [TUSafariActivity new];
ARChromeActivity *chromeActivity = [ARChromeActivity new]; ARChromeActivity *chromeActivity = [ARChromeActivity new];
chromeActivity.activityTitle = OPEN_IN_CHROME(); chromeActivity.activityTitle = OPEN_IN_CHROME();
chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"]; chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"];
OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new]; OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new];
OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new]; OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new];
NSArray *applicationActivites = @[twitterActivity,facebookActivity,chromeActivity]; NSArray *applicationActivites = @[twitterActivity,facebookActivity,safariActivity,chromeActivity];
return applicationActivites; return applicationActivites;
} }
+ (CGSize)otr_defaultImageSize + (CGSize)otr_defaultImageSize
{ {
CGSize size = CGSizeZero; CGSize size = CGSizeZero;
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
size = CGSizeMake(43, 43); size = CGSizeMake(43, 43);
} else if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
size = CGSizeMake(55, 55); size = CGSizeMake(55, 55);
} }
return size; return size;

View File

@ -8,6 +8,7 @@
#import "UIActivityViewController+ChatSecure.h" #import "UIActivityViewController+ChatSecure.h"
@import ARChromeActivity; @import ARChromeActivity;
@import TUSafariActivity;
#import "OTROpenInFacebookActivity.h" #import "OTROpenInFacebookActivity.h"
#import "OTROpenInTwitterActivity.h" #import "OTROpenInTwitterActivity.h"
@import OTRAssets; @import OTRAssets;

View File

@ -49,7 +49,7 @@ extension NotificationType: RawRepresentable {
public typealias RawValue = String public typealias RawValue = String
} }
extension UIApplication { public extension UIApplication {
/// Removes all but one foreground notifications for typing and message events sent from APNS /// Removes all but one foreground notifications for typing and message events sent from APNS
@objc public func removeExtraForegroundNotifications() { @objc public func removeExtraForegroundNotifications() {
@ -138,7 +138,7 @@ extension UIApplication {
let chatString = WANTS_TO_CHAT_STRING() let chatString = WANTS_TO_CHAT_STRING()
let text = "\(name) \(chatString)" let text = "\(name) \(chatString)"
let unreadCount = self.applicationIconBadgeNumber + 1 let unreadCount = self.applicationIconBadgeNumber + 1
self.showLocalNotificationWith(groupingIdentifier: nil, body: text, badge: unreadCount, userInfo: [kOTRNotificationType:kOTRNotificationTypeSubscriptionRequest], recurring: false) self.showLocalNotificationWith(identifier: nil, body: text, badge: unreadCount, userInfo: [kOTRNotificationType:kOTRNotificationTypeSubscriptionRequest], recurring: false)
} }
} }
@ -158,26 +158,28 @@ extension UIApplication {
let userInfo:[AnyHashable:Any] = [kOTRNotificationThreadKey:identifier, let userInfo:[AnyHashable:Any] = [kOTRNotificationThreadKey:identifier,
kOTRNotificationThreadCollection:thread.threadCollection, kOTRNotificationThreadCollection:thread.threadCollection,
kOTRNotificationType: kOTRNotificationTypeApprovedBuddy] kOTRNotificationType: kOTRNotificationTypeApprovedBuddy]
self.showLocalNotificationWith(groupingIdentifier: nil, body: message, badge: unreadCount, userInfo: userInfo, recurring: false) self.showLocalNotificationWith(identifier: identifier, body: message, badge: unreadCount, userInfo: userInfo, recurring: false)
} }
} }
internal func showLocalNotificationFor(_ thread:OTRThreadOwner?, text:String, unreadCount:Int) { internal func showLocalNotificationFor(_ thread:OTRThreadOwner?, text:String, unreadCount:Int) {
if let thread = thread, thread.isMuted { return } // No notifications for muted if let thread = thread, thread.isMuted { return } // No notifications for muted
DispatchQueue.main.async { DispatchQueue.main.async {
var identifier:String? = nil
var userInfo:[AnyHashable:Any]? = nil var userInfo:[AnyHashable:Any]? = nil
if let t = thread { if let t = thread {
identifier = t.threadIdentifier
userInfo = [kOTRNotificationThreadKey:t.threadIdentifier, userInfo = [kOTRNotificationThreadKey:t.threadIdentifier,
kOTRNotificationThreadCollection:t.threadCollection, kOTRNotificationThreadCollection:t.threadCollection,
kOTRNotificationType: kOTRNotificationTypeChatMessage] kOTRNotificationType: kOTRNotificationTypeChatMessage]
} }
self.showLocalNotificationWith(groupingIdentifier: nil, body: text, badge: unreadCount, userInfo: userInfo, recurring: false) self.showLocalNotificationWith(identifier: identifier, body: text, badge: unreadCount, userInfo: userInfo, recurring: false)
} }
} }
@objc public func showLocalNotificationWith(groupingIdentifier:String?, body:String, badge:Int, userInfo:[AnyHashable:Any]?, recurring:Bool) { @objc public func showLocalNotificationWith(identifier:String?, body:String, badge:Int, userInfo:[AnyHashable:Any]?, recurring:Bool) {
DispatchQueue.main.async { DispatchQueue.main.async {
if recurring, self.hasRecurringLocalNotificationWith(identifier: groupingIdentifier) { if recurring, self.hasRecurringLocalNotificationWith(identifier: identifier) {
return // Already pending return // Already pending
} }
// Use the new UserNotifications.framework on iOS 10+ // Use the new UserNotifications.framework on iOS 10+
@ -185,9 +187,9 @@ extension UIApplication {
let localNotification = UNMutableNotificationContent() let localNotification = UNMutableNotificationContent()
localNotification.body = body localNotification.body = body
localNotification.badge = NSNumber(integerLiteral: badge) localNotification.badge = NSNumber(integerLiteral: badge)
localNotification.sound = UNNotificationSound.default localNotification.sound = UNNotificationSound.default()
if let threadKey = userInfo?[kOTRNotificationThreadKey] as? String { if let identifier = identifier {
localNotification.threadIdentifier = threadKey localNotification.threadIdentifier = identifier
} }
if let userInfo = userInfo { if let userInfo = userInfo {
localNotification.userInfo = userInfo localNotification.userInfo = userInfo
@ -199,7 +201,7 @@ extension UIApplication {
date.minute = 0 date.minute = 0
trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true) trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
} }
let request = UNNotificationRequest(identifier: groupingIdentifier ?? UUID().uuidString, content: localNotification, trigger: trigger) // Schedule the notification. let request = UNNotificationRequest(identifier: UUID().uuidString, content: localNotification, trigger: trigger) // Schedule the notification.
let center = UNUserNotificationCenter.current() let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error: Error?) in center.add(request, withCompletionHandler: { (error: Error?) in
if let error = error as NSError? { if let error = error as NSError? {
@ -280,9 +282,9 @@ extension UIApplication {
let username = account.username let username = account.username
var body = "\(CONNECTION_ERROR_STRING()) \(username)." var body = "\(CONNECTION_ERROR_STRING()) \(username)."
if error.domain == GCDAsyncSocketErrorDomain, if error.domain == GCDAsyncSocketErrorDomain,
let code = GCDAsyncSocketError.Code.init(rawValue: error.code) { let code = GCDAsyncSocketError(rawValue: error.code) {
switch code { switch code {
case .noError, case .noError,
.connectTimeoutError, .connectTimeoutError,
@ -296,8 +298,6 @@ extension UIApplication {
case .otherError: case .otherError:
// this is probably a SSL error // this is probably a SSL error
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())" body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
@unknown default:
return
} }
} else if error.domain == "kCFStreamErrorDomainSSL" { } else if error.domain == "kCFStreamErrorDomainSSL" {
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())" body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
@ -325,12 +325,19 @@ extension UIApplication {
let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError, let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError,
kOTRNotificationAccountKey: accountKey] kOTRNotificationAccountKey: accountKey]
self.showLocalNotificationWith(groupingIdentifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false) if #available(iOS 10.0, *) {
} UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { (notifications) in
} // FIXME: this deduplication code doesn't seem to work
// if we are already showing a notification, let's not spam the user too much with more of them
extension UIApplication { for notification in notifications {
@objc public func open(_ url: URL) { if notification.request.identifier == accountKey {
open(url, options: [:], completionHandler: nil) return
}
}
self.showLocalNotificationWith(identifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
})
} else {
showLocalNotificationWith(identifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
}
} }
} }

View File

@ -10,7 +10,7 @@ import Foundation
import UIKit import UIKit
extension UINavigationController { public extension UINavigationController {
@objc public func otr_baseViewContorllers() -> [UIViewController] { @objc public func otr_baseViewContorllers() -> [UIViewController] {
var result:[UIViewController] = [] var result:[UIViewController] = []

View File

@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface UITableView (ChatSecure) @interface UITableView (ChatSecure)
/** deleteActionAlsoRemovesFromRoster is YES for the ChooseBuddy view, otherwise NO. Connection must be read-write */ /** deleteActionAlsoRemovesFromRoster is YES for the ChooseBuddy view, otherwise NO. Connection must be read-write */
+ (nullable UISwipeActionsConfiguration *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection; + (nullable NSArray<UITableViewRowAction *> *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -8,14 +8,14 @@
#import "UITableView+ChatSecure.h" #import "UITableView+ChatSecure.h"
#import "OTRXMPPBuddy.h" #import "OTRXMPPBuddy.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRXMPPManager_Private.h" #import "OTRXMPPManager_Private.h"
@import OTRAssets; @import OTRAssets;
@implementation UITableView (ChatSecure) @implementation UITableView (ChatSecure)
/** Connection must be read-write */ /** Connection must be read-write */
+ (nullable UISwipeActionsConfiguration *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection { + (nullable NSArray<UITableViewRowAction *> *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection {
NSParameterAssert(thread); NSParameterAssert(thread);
NSParameterAssert(connection); NSParameterAssert(connection);
if (!thread || !connection) { if (!thread || !connection) {
@ -33,23 +33,22 @@
archiveTitle = UNARCHIVE_ACTION_STRING(); archiveTitle = UNARCHIVE_ACTION_STRING();
} }
UIContextualAction *archiveAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:archiveTitle handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { UITableViewRowAction *archiveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:archiveTitle handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) { [connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
NSString *key = [thread threadIdentifier]; NSString *key = [thread threadIdentifier];
NSString *collection = [thread threadCollection]; NSString *collection = [thread threadCollection];
id object = [transaction objectForKey:key inCollection:collection]; id object = [transaction objectForKey:key inCollection:collection];
if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) { if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) {
completionHandler(NO);
return; return;
} }
id <OTRThreadOwner> thread = object; id <OTRThreadOwner> thread = object;
thread.isArchived = !thread.isArchived; thread.isArchived = !thread.isArchived;
[thread saveWithTransaction:transaction]; [thread saveWithTransaction:transaction];
completionHandler(YES);
}]; }];
}]; }];
UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:DELETE_STRING() handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:DELETE_STRING() handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction]; [OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction];
}]; }];
@ -62,7 +61,7 @@
[connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { [connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction]; account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction];
}]; }];
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account]; OTRXMPPManager *xmppManager = (OTRXMPPManager *)[OTRProtocolManager.shared protocolForAccount:account];
if (room.roomJID) { if (room.roomJID) {
[xmppManager.roomManager leaveRoom:room.roomJID]; [xmppManager.roomManager leaveRoom:room.roomJID];
} }
@ -71,7 +70,6 @@
//Delete database items //Delete database items
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[((OTRXMPPRoom *)thread) removeWithTransaction:transaction]; [((OTRXMPPRoom *)thread) removeWithTransaction:transaction];
completionHandler(YES);
}]; }];
} else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) { } else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) {
OTRBuddy *dbBuddy = (OTRBuddy*)thread; OTRBuddy *dbBuddy = (OTRBuddy*)thread;
@ -82,14 +80,11 @@
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[action saveWithTransaction:transaction]; [action saveWithTransaction:transaction];
[dbBuddy removeWithTransaction:transaction]; [dbBuddy removeWithTransaction:transaction];
completionHandler(YES);
}]; }];
} else {
completionHandler(NO);
} }
}]; }];
return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction, archiveAction]]; return @[deleteAction, archiveAction];
} }
@end @end

View File

@ -9,38 +9,26 @@
import UIKit import UIKit
import OTRAssets import OTRAssets
extension UIViewController { public extension UIViewController {
public func prompt(toShow url: URL, sender: Any) {
(url as NSURL).promptToShow(from: self, sender: sender)
}
/// Will show a prompt to bring user into system settings /// Will show a prompt to bring user into system settings
public func showPromptForSystemSettings(sender: Any) { public func showPromptForSystemSettings() {
let alert = UIAlertController(title: ENABLE_PUSH_IN_SETTINGS_STRING(), message: nil, preferredStyle: .alert) let alert = UIAlertController(title: ENABLE_PUSH_IN_SETTINGS_STRING(), message: nil, preferredStyle: .alert)
let settingsAction = UIAlertAction(title: SETTINGS_STRING(), style: .default, handler: { (action: UIAlertAction) -> Void in let settingsAction = UIAlertAction(title: SETTINGS_STRING(), style: .default, handler: { (action: UIAlertAction) -> Void in
let appSettings = URL(string: UIApplication.openSettingsURLString) let appSettings = URL(string: UIApplicationOpenSettingsURLString)
UIApplication.shared.open(appSettings!) UIApplication.shared.openURL(appSettings!)
}) })
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil) let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
alert.addAction(settingsAction) alert.addAction(settingsAction)
alert.addAction(cancelAction) alert.addAction(cancelAction)
if let sourceView = sender as? UIView {
alert.popoverPresentationController?.sourceView = sourceView;
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
}
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
public func showDestructivePrompt(title: String?, buttonTitle: String, sender: Any, handler: @escaping ((_ action: UIAlertAction) -> ())) { public func showDestructivePrompt(title: String?, buttonTitle: String, handler: @escaping ((_ action: UIAlertAction) -> ())) {
let alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet) let alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler) let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler)
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil) let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
alert.addAction(destroyAction) alert.addAction(destroyAction)
alert.addAction(cancelAction) alert.addAction(cancelAction)
if let sourceView = sender as? UIView {
alert.popoverPresentationController?.sourceView = sourceView;
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
}
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
extension XMPPMessage { public extension XMPPMessage {
/// Safely extracts XEP-0359 stanza-id /// Safely extracts XEP-0359 stanza-id
@objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? { @objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? {
let stanzaIds = self.stanzaIds let stanzaIds = self.stanzaIds

View File

@ -56,7 +56,7 @@ extension UIImage {
numTries = numTries + 1 numTries = numTries + 1
newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor) newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize) let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize)
scaledImageData = scaledImage.jpegData(compressionQuality: jpegQuality) scaledImageData = UIImageJPEGRepresentation(scaledImage, jpegQuality)
if let imageData = scaledImageData { if let imageData = scaledImageData {
sizeInBytes = UInt(imageData.count) sizeInBytes = UInt(imageData.count)
scaleFactor = scaleFactor * scaleDecrement scaleFactor = scaleFactor * scaleDecrement
@ -123,7 +123,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
let connection: YapDatabaseConnection let connection: YapDatabaseConnection
let internalQueue = DispatchQueue(label: "FileTransferManager Queue") let internalQueue = DispatchQueue(label: "FileTransferManager Queue")
let callbackQueue = DispatchQueue.main let callbackQueue = DispatchQueue.main
let sessionManager: Session let sessionManager: SessionManager
private var servers: [HTTPServer] = [] private var servers: [HTTPServer] = []
@objc public var canUploadFiles: Bool { @objc public var canUploadFiles: Bool {
@ -141,7 +141,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
self.serverCapabilities = serverCapabilities self.serverCapabilities = serverCapabilities
self.httpFileUpload = XMPPHTTPFileUpload() self.httpFileUpload = XMPPHTTPFileUpload()
self.connection = connection self.connection = connection
self.sessionManager = Alamofire.Session(configuration: sessionConfiguration) self.sessionManager = Alamofire.SessionManager(configuration: sessionConfiguration)
super.init() super.init()
if let stream = serverCapabilities.xmppStream { if let stream = serverCapabilities.xmppStream {
httpFileUpload.activate(stream) httpFileUpload.activate(stream)
@ -155,19 +155,15 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
// Resume downloads, i.e. look for media items that are partially downloaded and retry getting them. TODO - use ranges // Resume downloads, i.e. look for media items that are partially downloaded and retry getting them. TODO - use ranges
@objc public func resumeDownloads() { @objc public func resumeDownloads() {
/// https://github.com/ChatSecure/ChatSecure-iOS/issues/1034 connection.asyncRead { (transaction) in
DDLogWarn("WARN: Download resumption is disabled. See https://github.com/ChatSecure/ChatSecure-iOS/issues/1034 for more information.") transaction.enumerateUnfinishedDownloads({ (mediaItem, stop) in
// connection.asyncRead { [weak self] (transaction) in if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage, downloadMessage.messageError == nil {
// let unfinished = transaction.unfinishedDownloads() self.internalQueue.async {
// self?.internalQueue.async { self.downloadMedia(downloadMessage)
// for mediaItem in unfinished { }
// if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage, }
// downloadMessage.messageError == nil { })
// self?.downloadMedia(downloadMessage) }
// }
// }
// }
// }
} }
/// This will fetch capabilities and setup XMPP transfer module if needed /// This will fetch capabilities and setup XMPP transfer module if needed
@ -260,7 +256,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
var outData = data var outData = data
var outKeyIv: Data? = nil var outKeyIv: Data? = nil
if shouldEncrypt { if shouldEncrypt {
guard let key = OTRPasswordGenerator.randomData(withLength: 32), let iv = OTRSignalEncryptionHelper.generateIV() else { guard let key = OTRPasswordGenerator.randomData(withLength: 32), let iv = OTRPasswordGenerator.randomData(withLength: 16) else {
DDLogError("Could not generate key/iv") DDLogError("Could not generate key/iv")
self.callbackQueue.async { self.callbackQueue.async {
completion(nil, FileTransferError.keyGenerationError) completion(nil, FileTransferError.keyGenerationError)
@ -269,7 +265,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
} }
outKeyIv = iv + key outKeyIv = iv + key
do { do {
let crypted = try OTRSignalEncryptionHelper.encryptData(data, key: key, iv: iv) let crypted = try OTRCryptoUtility.encryptAESGCMData(data, key: key, iv: iv)
outData = crypted.data + crypted.authTag outData = crypted.data + crypted.authTag
} catch let error { } catch let error {
outData = Data() outData = Data()
@ -291,23 +287,9 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
} }
return return
} }
self.sessionManager.upload(outData, to: slot.putURL, method: .put)
// Pick optional headers from the slot and filter out any not allowed by
// XEP-0363 (https://xmpp.org/extensions/xep-0363.html#request)
let allowedHeaders = ["authorization", "cookie", "expires"]
var forwardedHeaders:HTTPHeaders = [:]
for (headerName, headerValue) in slot.putHeaders {
let name = headerName.replacingOccurrences(of: "\n", with: "").lowercased()
if allowedHeaders.contains(name) {
forwardedHeaders[name] = headerValue.replacingOccurrences(of: "\n", with: "")
}
}
forwardedHeaders["Content-Type"] = contentType
forwardedHeaders["Content-Length"] = "\(UInt(outData.count))"
self.sessionManager.upload(outData, to: slot.putURL, method: .put, headers: forwardedHeaders)
.validate() .validate()
.response(queue: self.callbackQueue) { response in .responseData(queue: self.callbackQueue) { response in
switch response.result { switch response.result {
case .success: case .success:
if let outKeyIv = outKeyIv { if let outKeyIv = outKeyIv {
@ -378,9 +360,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
} }
mediaItem.parentObjectKey = message.messageKey mediaItem.parentObjectKey = message.messageKey
mediaItem.parentObjectCollection = message.messageCollection mediaItem.parentObjectCollection = message.messageCollection
guard let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier) else { let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier)
return
}
self.connection.readWrite { transaction in self.connection.readWrite { transaction in
message.save(with: transaction) message.save(with: transaction)
mediaItem.save(with: transaction) mediaItem.save(with: transaction)
@ -494,8 +474,6 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
shouldEncrypt = true shouldEncrypt = true
case .invalid, .plaintext, .plaintextWithOTR: case .invalid, .plaintext, .plaintextWithOTR:
shouldEncrypt = false shouldEncrypt = false
@unknown default:
fatalError("Unhandled message security value!")
} }
self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in
@ -679,12 +657,7 @@ extension FileTransferManager {
// Remove placeholder media item // Remove placeholder media item
mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction) mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction)
mediaItem?.remove(with: transaction) mediaItem?.remove(with: transaction)
// If the file is encrypted, the server might not know its type mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
if url.aesGcmKey != nil && contentType == "application/octet-stream" {
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: nil)
} else {
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
}
mediaItem?.parentObjectKey = downloadMessage.uniqueId mediaItem?.parentObjectKey = downloadMessage.uniqueId
mediaItem?.parentObjectCollection = downloadMessage.messageCollection mediaItem?.parentObjectCollection = downloadMessage.messageCollection
mediaItem?.save(with: transaction) mediaItem?.save(with: transaction)
@ -813,19 +786,19 @@ extension OTRDownloadMessage {
} }
} }
extension OTRMessageProtocol { public extension OTRMessageProtocol {
public var downloadableURLs: [URL] { public var downloadableURLs: [URL] {
return self.messageText?.downloadableURLs ?? [] return self.messageText?.downloadableURLs ?? []
} }
} }
extension OTRBaseMessage { public extension OTRBaseMessage {
@objc public var downloadableNSURLs: [NSURL] { @objc public var downloadableNSURLs: [NSURL] {
return self.downloadableURLs as [NSURL] return self.downloadableURLs as [NSURL]
} }
} }
extension OTRXMPPRoomMessage { public extension OTRXMPPRoomMessage {
@objc public var downloadableNSURLs: [NSURL] { @objc public var downloadableNSURLs: [NSURL] {
return self.downloadableURLs as [NSURL] return self.downloadableURLs as [NSURL]
} }
@ -904,31 +877,20 @@ extension URL {
} }
var aesGcmKey: (key: Data, iv: Data)? { var aesGcmKey: (key: Data, iv: Data)? {
guard let data = self.anchorData else { return nil } guard let data = self.anchorData, data.count == 48 else { return nil }
let ivLength: Int let iv = data.subdata(in: 0..<16)
switch data.count { let key = data.subdata(in: 16..<48)
case 48:
// legacy clients send 16-byte IVs
ivLength = 16
case 44:
// newer clients send 12-byte IVs
ivLength = 12
default:
return nil
}
let iv = data.subdata(in: 0..<ivLength)
let key = data.subdata(in: ivLength..<data.count)
return (key, iv) return (key, iv)
} }
} }
extension NSString { public extension NSString {
public var isSingleURLOnly: Bool { public var isSingleURLOnly: Bool {
return (self as String).isSingleURLOnly return (self as String).isSingleURLOnly
} }
} }
extension String { public extension String {
private var urlRanges: ([URL], [NSRange]) { private var urlRanges: ([URL], [NSRange]) {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
@ -980,7 +942,7 @@ extension String {
} }
} }
extension FileTransferManager { public extension FileTransferManager {
/// Returns whether or not message should be displayed or hidden from collection. Single incoming URLs should be hidden, for example. /// Returns whether or not message should be displayed or hidden from collection. Single incoming URLs should be hidden, for example.
@objc public static func shouldDisplayMessage(_ message: OTRMessageProtocol, transaction: YapDatabaseReadTransaction) -> Bool { @objc public static func shouldDisplayMessage(_ message: OTRMessageProtocol, transaction: YapDatabaseReadTransaction) -> Bool {
// Always show media messages // Always show media messages

View File

@ -22,8 +22,11 @@ private class OutstandingActionInfo: Hashable, Equatable {
self.completion = completion self.completion = completion
} }
func hash(into hasher: inout Hasher) { /// Needed so we can store the struct in a dictionary
hasher.combine(action.yapKey()) var hashValue: Int {
get {
return action.yapKey().hashValue
}
} }
} }
@ -42,9 +45,10 @@ private struct OutstandingMessageInfo {
/// Needed so we can store the struct in a dictionary /// Needed so we can store the struct in a dictionary
extension OutstandingMessageInfo: Hashable { extension OutstandingMessageInfo: Hashable {
func hash(into hasher: inout Hasher) { var hashValue: Int {
hasher.combine(self.messageKey) get {
hasher.combine(self.messageCollection) return "\(self.messageKey)\(self.messageCollection)".hashValue
}
} }
} }
@ -213,7 +217,7 @@ public class MessageQueueHandler:NSObject {
switch message.messageSecurity { switch message.messageSecurity {
case .plaintext: case .plaintext:
self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion) self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion)
OTRProtocolManager.sharedInstance().send(message) OTRProtocolManager.shared.send(message)
break break
case .plaintextWithOTR: case .plaintextWithOTR:
self.sendOTRMessage(message: message, buddyKey: buddy.uniqueId, buddyUsername: buddy.username, accountUsername: account.username, accountProtocolStrintg: account.protocolTypeString(), requiresActiveSession: false, completion: completion) self.sendOTRMessage(message: message, buddyKey: buddy.uniqueId, buddyUsername: buddy.username, accountUsername: account.username, accountProtocolStrintg: account.protocolTypeString(), requiresActiveSession: false, completion: completion)
@ -227,8 +231,6 @@ public class MessageQueueHandler:NSObject {
case .invalid: case .invalid:
fatalError("Invalid message security. This should never happen... so let's crash!") fatalError("Invalid message security. This should never happen... so let's crash!")
break break
@unknown default:
fatalError("Invalid message security. This should never happen... so let's crash!")
} }
} }
@ -257,8 +259,6 @@ public class MessageQueueHandler:NSObject {
room.sendRoomMessage(message) room.sendRoomMessage(message)
case .OMEMO: case .OMEMO:
sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion) sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion)
@unknown default:
fatalError("Invalid group message security. This should never happen.")
} }
databaseConnection.readWrite { transaction in databaseConnection.readWrite { transaction in
if let sentMessage = message.refetch(with: transaction)?.copyAsSelf() { if let sentMessage = message.refetch(with: transaction)?.copyAsSelf() {
@ -296,7 +296,7 @@ public class MessageQueueHandler:NSObject {
} }
//Get the XMPP procol manager associated with this message and therefore account //Get the XMPP procol manager associated with this message and therefore account
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else { guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
completion(true, 0.0) completion(true, 0.0)
return return
} }
@ -350,7 +350,7 @@ public class MessageQueueHandler:NSObject {
} }
//Get the XMPP procol manager associated with this message and therefore account //Get the XMPP procol manager associated with this message and therefore account
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else { guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
completion(true, 0.0) completion(true, 0.0)
return return
} }
@ -385,7 +385,7 @@ public class MessageQueueHandler:NSObject {
} }
//Get the XMPP procol manager associated with this message and therefore account //Get the XMPP procol manager associated with this message and therefore account
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else { guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
completion(true, 0.0) completion(true, 0.0)
return return
} }

View File

@ -11,6 +11,7 @@
@import MobileCoreServices; @import MobileCoreServices;
@import OTRAssets; @import OTRAssets;
#import "OTRUtilities.h" #import "OTRUtilities.h"
#import "UIActionSheet+ChatSecure.h"
@interface OTRAttachmentPicker () <UINavigationControllerDelegate> @interface OTRAttachmentPicker () <UINavigationControllerDelegate>
@ -110,7 +111,7 @@
finalImage = originalImage; finalImage = originalImage;
} }
if (finalImage && [self.delegate respondsToSelector:@selector(attachmentPicker:gotPhoto:withInfo:)]) { if ([self.delegate respondsToSelector:@selector(attachmentPicker:gotPhoto:withInfo:)]) {
[self.delegate attachmentPicker:self gotPhoto:finalImage withInfo:info]; [self.delegate attachmentPicker:self gotPhoto:finalImage withInfo:info];
} }

View File

@ -16,7 +16,7 @@
@property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem; @property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem;
@property (nonatomic, weak, readonly) OTRAudioControlsView *currentAudioControlsView; @property (nonatomic, weak, readonly) OTRAudioControlsView *currentAudioControlsView;
- (BOOL)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError **)error; - (void)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError **)error;
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView; - (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView;

View File

@ -44,19 +44,18 @@
[self.currentAudioControlsView setTime:currentTime]; [self.currentAudioControlsView setTime:currentTime];
} }
- (BOOL)playURL:(NSURL *)url error:(NSError **)error; - (void)playURL:(NSURL *)url error:(NSError **)error;
{ {
AVURLAsset *asset = [AVURLAsset assetWithURL:url]; AVURLAsset *asset = [AVURLAsset assetWithURL:url];
self.duration = CMTimeGetSeconds(asset.duration); self.duration = CMTimeGetSeconds(asset.duration);
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause; self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
error = nil; error = nil;
BOOL result = [self.audioSessionManager playAudioWithURL:url error:error]; [self.audioSessionManager playAudioWithURL:url error:error];
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause; self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
[self.currentAudioControlsView setTime:0]; [self.currentAudioControlsView setTime:0];
[self startLabelTimer]; [self startLabelTimer];
return result;
} }
- (void)startLabelTimer - (void)startLabelTimer
@ -94,13 +93,13 @@
#pragma - mark Public Methods #pragma - mark Public Methods
- (BOOL)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError *__autoreleasing *)error - (void)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError *__autoreleasing *)error
{ {
NSURL *audioURL = [[OTRMediaServer sharedInstance] urlForMediaItem:audioItem buddyUniqueId:buddyUniqueId]; NSURL *audioURL = [[OTRMediaServer sharedInstance] urlForMediaItem:audioItem buddyUniqueId:buddyUniqueId];
_currentAudioItem = audioItem; _currentAudioItem = audioItem;
return [self playURL:audioURL error:error]; [self playURL:audioURL error:error];
} }
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView - (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView

View File

@ -25,7 +25,7 @@
@property (nonatomic, weak) id<OTRAudioSessionManagerDelegate> delegate; @property (nonatomic, weak) id<OTRAudioSessionManagerDelegate> delegate;
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error; - (void)playAudioWithURL:(NSURL *)url error:(NSError **)error;
- (void)pausePlaying; - (void)pausePlaying;
- (void)resumePlaying; - (void)resumePlaying;
- (void)stopPlaying; - (void)stopPlaying;
@ -34,7 +34,7 @@
- (NSURL *)currentPlayerURL; - (NSURL *)currentPlayerURL;
- (BOOL)isPlaying; - (BOOL)isPlaying;
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error; - (void)recordAudioToURL:(NSURL *)url error:(NSError **)error;
- (void)stopRecording; - (void)stopRecording;
- (NSTimeInterval)currentTimeRecordTime; - (NSTimeInterval)currentTimeRecordTime;
- (NSURL *)currentRecorderURL; - (NSURL *)currentRecorderURL;

View File

@ -49,7 +49,7 @@
#pragma - mark Public Methods #pragma - mark Public Methods
////// Playing ////// ////// Playing //////
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error - (void)playAudioWithURL:(NSURL *)url error:(NSError **)error
{ {
[self stopPlaying]; [self stopPlaying];
[self stopRecording]; [self stopRecording];
@ -57,22 +57,20 @@
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error];
if (error) { if (error) {
return NO; return;
} }
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error]; [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
if (error) { if (error) {
return NO; return;
} }
error = nil; error = nil;
self.currentPlayer = [self audioPlayerWithURL:url error:error]; self.currentPlayer = [self audioPlayerWithURL:url error:error];
if (error) { if (error) {
return NO; return;
} }
[self.currentPlayer play]; [self.currentPlayer play];
return YES;
} }
- (void)pausePlaying - (void)pausePlaying
@ -121,32 +119,30 @@
} }
////// Recording ////// ////// Recording //////
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error - (void)recordAudioToURL:(NSURL *)url error:(NSError **)error
{ {
[self stopRecording]; [self stopRecording];
[self stopPlaying]; [self stopPlaying];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:error]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:error];
if (error) { if (error) {
return NO; return;
} }
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error]; [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
if (error) { if (error) {
return NO; return;
} }
self.currentRecorder = [self audioRecorderWithURL:url error:error]; self.currentRecorder = [self audioRecorderWithURL:url error:error];
if (error) { if (error) {
return NO; return;
} }
self.currentRecorder.meteringEnabled = YES; self.currentRecorder.meteringEnabled = YES;
self.recordDecibelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(updateDecibelRecording:) userInfo:nil repeats:YES]; self.recordDecibelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(updateDecibelRecording:) userInfo:nil repeats:YES];
[self.currentRecorder record]; [self.currentRecorder record];
return YES;
} }
- (void)stopRecording - (void)stopRecording
@ -183,9 +179,9 @@
#pragma - mark Private Methods #pragma - mark Private Methods
- (BOOL)deactivateSession:(NSError **)error - (void)deactivateSession:(NSError **)error
{ {
return [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:error]; [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:error];
} }
- (AVPlayer *)audioPlayerWithURL:(NSURL *)url error:(NSError **)error - (AVPlayer *)audioPlayerWithURL:(NSURL *)url error:(NSError **)error

View File

@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
directory:(nullable NSString*)directory directory:(nullable NSString*)directory
withMediaStorage:(BOOL)withMediaStorage; withMediaStorage:(BOOL)withMediaStorage;
- (BOOL)setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError *_Nullable*)error; - (void)setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError *_Nullable*)error;
- (BOOL)hasPassphrase; - (BOOL)hasPassphrase;

View File

@ -15,6 +15,7 @@
#import "OTRConstants.h" #import "OTRConstants.h"
#import "OTRXMPPAccount.h" #import "OTRXMPPAccount.h"
#import "OTRXMPPTorAccount.h" #import "OTRXMPPTorAccount.h"
#import "OTRGoogleOAuthXMPPAccount.h"
#import "OTRAccount.h" #import "OTRAccount.h"
#import "OTRIncomingMessage.h" #import "OTRIncomingMessage.h"
#import "OTROutgoingMessage.h" #import "OTROutgoingMessage.h"
@ -28,7 +29,7 @@
#import "OTRSignalSession.h" #import "OTRSignalSession.h"
#import "OTRSettingsManager.h" #import "OTRSettingsManager.h"
#import "OTRXMPPPresenceSubscriptionRequest.h" #import "OTRXMPPPresenceSubscriptionRequest.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
@interface OTRDatabaseManager () @interface OTRDatabaseManager ()
@ -123,7 +124,6 @@
} }
return keyData; return keyData;
}; };
options.cipherCompatability = YapDatabaseCipherCompatability_Version3;
_databaseDirectory = [directory copy]; _databaseDirectory = [directory copy];
if (!_databaseDirectory) { if (!_databaseDirectory) {
_databaseDirectory = [[self class] defaultYapDatabaseDirectory]; _databaseDirectory = [[self class] defaultYapDatabaseDirectory];
@ -134,14 +134,17 @@
} }
NSString *databasePath = [self.databaseDirectory stringByAppendingPathComponent:name]; NSString *databasePath = [self.databaseDirectory stringByAppendingPathComponent:name];
self.database = [[YapDatabase alloc] initWithURL:[NSURL fileURLWithPath:databasePath] options:options]; self.database = [[YapDatabase alloc] initWithPath:databasePath
serializer:nil
deserializer:nil
options:options];
// Stop trying to setup up the database. Something went wrong. Most likely the password is incorrect. // Stop trying to setup up the database. Something went wrong. Most likely the password is incorrect.
if (self.database == nil) { if (self.database == nil) {
return NO; return NO;
} }
self.database.connectionDefaults.objectCacheLimit = 10000; self.database.defaultObjectPolicy = YapDatabasePolicyShare;
self.database.defaultObjectCacheLimit = 10000;
[self setupConnections]; [self setupConnections];
@ -193,7 +196,7 @@
NSString *name = [YapDatabaseConstants extensionName:DatabaseExtensionNameMessageQueueBrokerViewName]; NSString *name = [YapDatabaseConstants extensionName:DatabaseExtensionNameMessageQueueBrokerViewName];
self->_messageQueueBroker = [YapTaskQueueBroker setupWithDatabase:self.database name:name handler:self.messageQueueHandler error:nil]; _messageQueueBroker = [YapTaskQueueBroker setupWithDatabase:self.database name:name handler:self.messageQueueHandler error:nil];
//Register Buddy username & displayName FTS and corresponding view //Register Buddy username & displayName FTS and corresponding view
@ -296,17 +299,15 @@
return [[NSFileManager defaultManager] fileExistsAtPath:[self defaultYapDatabasePathWithName:OTRYapDatabaseName]]; return [[NSFileManager defaultManager] fileExistsAtPath:[self defaultYapDatabasePathWithName:OTRYapDatabaseName]];
} }
- (BOOL) setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError**)error - (void) setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError**)error
{ {
BOOL result = YES;
if (rememeber) { if (rememeber) {
self.inMemoryPassphrase = nil; self.inMemoryPassphrase = nil;
result = [SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error]; [SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
} else { } else {
[SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName]; [SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName];
self.inMemoryPassphrase = passphrase; self.inMemoryPassphrase = passphrase;
} }
return result;
} }
- (BOOL)hasPassphrase - (BOOL)hasPassphrase

View File

@ -14,7 +14,7 @@
#import "OTRIncomingMessage.h" #import "OTRIncomingMessage.h"
#import "OTRLog.h" #import "OTRLog.h"
#import "OTROutgoingMessage.h" #import "OTROutgoingMessage.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName"; NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName"; NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";

View File

@ -21,7 +21,9 @@
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>. // along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
@import Foundation; @import Foundation;
@class OTRKit, OTRDataHandler, OTRFingerprint; @import OTRKit;
@class OTRPushTLVHandler;
extern NSString * _Nonnull const OTRMessageStateDidChangeNotification; extern NSString * _Nonnull const OTRMessageStateDidChangeNotification;
extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification; extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification;
@ -41,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, readonly) OTRKit *otrKit; @property (nonatomic, strong, readonly) OTRKit *otrKit;
@property (nonatomic, strong, readonly) OTRDataHandler *dataHandler; @property (nonatomic, strong, readonly) OTRDataHandler *dataHandler;
@property (nonatomic, strong, readonly) OTRPushTLVHandler *pushTLVHandler;
/** /**
* This method takes a buddy key and collection. If it finds an object in the database and `hasGoneEncryptedBefore` is true * This method takes a buddy key and collection. If it finds an object in the database and `hasGoneEncryptedBefore` is true

View File

@ -39,9 +39,10 @@
#import "OTRMediaServer.h" #import "OTRMediaServer.h"
#import "OTRDatabaseManager.h" #import "OTRDatabaseManager.h"
#import "OTRLog.h" #import "OTRLog.h"
#import "OTRPushTLVHandler.h"
#import "OTRXMPPManager.h" #import "OTRXMPPManager.h"
#import "OTRYapMessageSendAction.h" #import "OTRYapMessageSendAction.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
@import AVFoundation; @import AVFoundation;
@import XMPPFramework; @import XMPPFramework;
@ -66,6 +67,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
_otrFingerprintCache = [[NSCache alloc] init]; _otrFingerprintCache = [[NSCache alloc] init];
_otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil]; _otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil];
_dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self]; _dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self];
_pushTLVHandler = [[OTRPushTLVHandler alloc] initWithOTRKit:self.otrKit delegate:nil];
_readConnection = OTRDatabaseManager.shared.readConnection; _readConnection = OTRDatabaseManager.shared.readConnection;
NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath]; NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath];
for (NSString *path in protectPaths) { for (NSString *path in protectPaths) {
@ -246,7 +248,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
} completionBlock:^{ } completionBlock:^{
if (!buddy) { return; } if (!buddy) { return; }
message.buddyUniqueId = buddy.uniqueId; message.buddyUniqueId = buddy.uniqueId;
[[OTRProtocolManager sharedInstance] sendMessage:message]; [OTRProtocolManager.shared sendMessage:message];
}]; }];
} }

View File

@ -33,7 +33,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
//#865 //#865
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem - (void)deleteDataForItem:(OTRMediaItem *)mediaItem
buddyUniqueId:(NSString *)buddyUniqueId buddyUniqueId:(NSString *)buddyUniqueId
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion completion:(void (^)(BOOL success, NSError * _Nullable error))completion
completionQueue:(nullable dispatch_queue_t)completionQueue; completionQueue:(nullable dispatch_queue_t)completionQueue;
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem - (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
@ -43,10 +43,8 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
buddyUniqueId:(NSString *)buddyUniqueId buddyUniqueId:(NSString *)buddyUniqueId
error:(NSError* __autoreleasing *)error; error:(NSError* __autoreleasing *)error;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId; + (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash; + (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
- (void)vacuum:(dispatch_block_t)completion;
@property (class, nonatomic, readonly) OTRMediaFileManager *shared; @property (class, nonatomic, readonly) OTRMediaFileManager *shared;

View File

@ -52,10 +52,7 @@ NSString *const kOTRRootMediaDirectory = @"media";
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password - (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
{ {
_ioCipher = [[IOCipher alloc] initWithPath:path password:password]; _ioCipher = [[IOCipher alloc] initWithPath:path password:password];
if (!_ioCipher) { return _ioCipher != nil;
return NO;
}
return [_ioCipher setCipherCompatibility:3];
} }
- (void)copyDataFromFilePath:(NSString *)filePath - (void)copyDataFromFilePath:(NSString *)filePath
@ -134,7 +131,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
//#865 //#865
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem - (void)deleteDataForItem:(OTRMediaItem *)mediaItem
buddyUniqueId:(NSString *)buddyUniqueId buddyUniqueId:(NSString *)buddyUniqueId
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion completion:(void (^)(BOOL success, NSError * _Nullable error))completion
completionQueue:(nullable dispatch_queue_t)completionQueue { completionQueue:(nullable dispatch_queue_t)completionQueue {
if (!completionQueue) { if (!completionQueue) {
completionQueue = dispatch_get_main_queue(); completionQueue = dispatch_get_main_queue();
@ -284,12 +281,4 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
} }
} }
- (void)vacuum:(dispatch_block_t)completion {
[self performAsyncWrite:^{
[self.ioCipher vacuum];
if (completion != nil) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
}
@end @end

View File

@ -0,0 +1,21 @@
//
// OTROAuthRefresher.h
// Off the Record
//
// Created by David Chiles on 3/28/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
@import Foundation;
@class GTMOAuth2Authentication;
@class FBAccessTokenData;
@class OTROAuthXMPPAccount;
typedef void(^OTROAuthCompletionBlock)(id token,NSError *);
@interface OTROAuthRefresher : NSObject
+ (void)refreshAccount:(OTROAuthXMPPAccount *)account completion:(OTROAuthCompletionBlock)completionBlock;
@end

View File

@ -0,0 +1,41 @@
//
// OTROAuthRefresher.m
// Off the Record
//
// Created by David Chiles on 3/28/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
#import "OTROAuthRefresher.h"
#import "GTMOAuth2Authentication.h"
#import "OTRSecrets.h"
#import "OTRConstants.h"
#import "OTROAuthXMPPAccount.h"
@implementation OTROAuthRefresher
+ (void)refreshGoogleToken:(GTMOAuth2Authentication *)authToken completion:(OTROAuthCompletionBlock)completionBlock
{
[authToken authorizeRequest:nil completionHandler:^(NSError *error) {
if (completionBlock) {
if (!error) {
completionBlock(authToken,nil);
}
else {
completionBlock(nil,error);
}
}
}];
}
+ (void)refreshAccount:(OTROAuthXMPPAccount *)account completion:(OTROAuthCompletionBlock)completionBlock
{
if (account.accountType == OTRAccountTypeGoogleTalk) {
[self refreshGoogleToken:[account accountSpecificToken] completion:completionBlock];
}
}
@end

View File

@ -19,16 +19,16 @@ import SignalProtocolObjC
@objc public static let DeviceListUpdateNotificationName = Notification.Name(rawValue: "DeviceListUpdateNotification") @objc public static let DeviceListUpdateNotificationName = Notification.Name(rawValue: "DeviceListUpdateNotification")
public let signalEncryptionManager:OTRAccountSignalEncryptionManager open let signalEncryptionManager:OTRAccountSignalEncryptionManager
public let omemoStorageManager:OTROMEMOStorageManager open let omemoStorageManager:OTROMEMOStorageManager
@objc public let accountYapKey:String @objc open let accountYapKey:String
@objc public let databaseConnection:YapDatabaseConnection @objc open let databaseConnection:YapDatabaseConnection
@objc open weak var omemoModule:OMEMOModule? @objc open weak var omemoModule:OMEMOModule?
@objc open weak var omemoModuleQueue:DispatchQueue? @objc open weak var omemoModuleQueue:DispatchQueue?
@objc open var callbackQueue:DispatchQueue @objc open var callbackQueue:DispatchQueue
@objc public let workQueue:DispatchQueue @objc open let workQueue:DispatchQueue
@objc public let messageStorage: MessageStorage @objc open let messageStorage: MessageStorage
@objc public let roomManager: OTRXMPPRoomManager @objc open let roomManager: OTRXMPPRoomManager
private var roomStorage: RoomStorage { private var roomStorage: RoomStorage {
return roomManager.roomStorage return roomManager.roomStorage
@ -295,7 +295,7 @@ import SignalProtocolObjC
//Strong self work here //Strong self work here
var buddies: [OTRXMPPBuddy] = [] var buddies: [OTRXMPPBuddy] = []
self.databaseConnection.read { (transaction) in self.databaseConnection.read { (transaction) in
buddies = buddyKeys.compactMap({ key in buddies = buddyKeys.flatMap({ key in
OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction) OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction)
}) })
} }
@ -310,7 +310,10 @@ import SignalProtocolObjC
} }
do { do {
//Create the encrypted payload //Create the encrypted payload
let gcmData = try OTRSignalEncryptionHelper.encryptData(messageBodyData, key: keyData, iv: ivData) guard let gcmData = try OTRSignalEncryptionHelper.encryptData(messageBodyData, key: keyData, iv: ivData) else {
DDLogError("OMEMO Encryption error: Could not perform AES-GCM operation")
return
}
// this does the signal encryption. If we fail it doesn't matter here. We end up trying the next device and fail later if no devices worked. // this does the signal encryption. If we fail it doesn't matter here. We end up trying the next device and fail later if no devices worked.
let encryptClosure:(OMEMODevice) -> (OMEMOKeyData?) = { device in let encryptClosure:(OMEMODevice) -> (OMEMOKeyData?) = { device in
@ -331,7 +334,7 @@ import SignalProtocolObjC
4. Remove optional values 4. Remove optional values
*/ */
let buddyKeyDataArray = buddyKeys.flatMap({ key in let buddyKeyDataArray = buddyKeys.flatMap({ key in
self.omemoStorageManager.getDevicesForParentYapKey(key, yapCollection: OTRXMPPBuddy.collection, trustedOnly: true).map(encryptClosure).compactMap{ $0 } self.omemoStorageManager.getDevicesForParentYapKey(key, yapCollection: OTRXMPPBuddy.collection, trustedOnly: true).map(encryptClosure).flatMap{ $0 }
}) })
// Stop here if we were not able to encrypt to any of the buddies // Stop here if we were not able to encrypt to any of the buddies
@ -351,7 +354,7 @@ import SignalProtocolObjC
*/ */
let ourDevicesKeyData = self.omemoStorageManager.getDevicesForOurAccount(trustedOnly: true).filter({ (device) -> Bool in let ourDevicesKeyData = self.omemoStorageManager.getDevicesForOurAccount(trustedOnly: true).filter({ (device) -> Bool in
return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId
}).map(encryptClosure).compactMap{ $0 } }).map(encryptClosure).flatMap{ $0 }
// Combine teh two arrays for all key data // Combine teh two arrays for all key data
let keyDataArray = ourDevicesKeyData + buddyKeyDataArray let keyDataArray = ourDevicesKeyData + buddyKeyDataArray
@ -365,7 +368,6 @@ import SignalProtocolObjC
let groupMessage = self.omemoModule?.message(forKeyData: keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId) let groupMessage = self.omemoModule?.message(forKeyData: keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)
{ {
groupMessage.addAttribute(withName: "type", stringValue: "groupchat") groupMessage.addAttribute(withName: "type", stringValue: "groupchat")
groupMessage.addReceiptRequest()
self.omemoModule?.xmppStream?.send(groupMessage) self.omemoModule?.xmppStream?.send(groupMessage)
} else if message is OTROutgoingMessage { } else if message is OTROutgoingMessage {
self.omemoModule?.sendKeyData(keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId) self.omemoModule?.sendKeyData(keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)

View File

@ -21,6 +21,7 @@
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>. // along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
@class OTROutgoingMessage, OTRBuddy, OTRAccount; @class OTROutgoingMessage, OTRBuddy, OTRAccount;
@protocol PushControllerProtocol;
typedef NS_ENUM(int, OTRProtocolType) { typedef NS_ENUM(int, OTRProtocolType) {
OTRProtocolTypeNone = 0, OTRProtocolTypeNone = 0,

View File

@ -30,16 +30,16 @@
@class OTRAccount, OTRXMPPAccount, OTRBuddy, OTROutgoingMessage, PushController, OTRXMPPManager; @class OTRAccount, OTRXMPPAccount, OTRBuddy, OTROutgoingMessage, PushController, OTRXMPPManager;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface OTRProtocolManager : NSObject @interface _OTRProtocolManager : NSObject
@property (atomic, readonly) NSUInteger numberOfConnectedProtocols; @property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
@property (atomic, readonly) NSUInteger numberOfConnectingProtocols;
@property (atomic, readonly) NSArray<id<OTRProtocol>> *allProtocols;
- (BOOL)existsProtocolForAccount:(OTRAccount *)account; - (BOOL)existsProtocolForAccount:(OTRAccount *)account;
- (nullable id <OTRProtocol>)protocolForAccount:(OTRAccount *)account; - (nullable id <OTRProtocol>)protocolForAccount:(OTRAccount *)account;
- (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account; - (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account;
- (void)removeProtocolForAccount:(OTRAccount *)account; - (void)removeProtocolForAccount:(OTRAccount *)account;
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account;
- (BOOL)isAccountConnected:(OTRAccount *)account; - (BOOL)isAccountConnected:(OTRAccount *)account;
@ -47,8 +47,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated; - (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated;
- (void)loginAccounts:(NSArray<OTRAccount*> *)accounts; - (void)loginAccounts:(NSArray<OTRAccount*> *)accounts;
- (void)goAwayForAllAccounts; - (void)goAwayForAllAccounts;
- (void)disconnectAllAccounts;
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock;
- (void)sendMessage:(OTROutgoingMessage *)message; - (void)sendMessage:(OTROutgoingMessage *)message;
@ -58,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedInstance; // Singleton method + (instancetype)sharedInstance; // Singleton method
/** Convenience for sharedInstance */ /** Convenience for sharedInstance */
@property (class, nonatomic, readonly) OTRProtocolManager *shared; //@property (class, nonatomic, readonly) OTRProtocolManager *shared;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -26,23 +26,24 @@
#import "OTRIncomingMessage.h" #import "OTRIncomingMessage.h"
#import "OTROutgoingMessage.h" #import "OTROutgoingMessage.h"
#import "OTRConstants.h" #import "OTRConstants.h"
#import "OTROAuthRefresher.h"
#import "OTROAuthXMPPAccount.h"
#import "OTRDatabaseManager.h" #import "OTRDatabaseManager.h"
#import "OTRPushTLVHandler.h"
#import <BBlock/NSObject+BBlock.h> #import <BBlock/NSObject+BBlock.h>
@import YapDatabase; @import YapDatabase;
@import KVOController; @import KVOController;
@import OTRAssets; @import OTRAssets;
#import "OTRLog.h" #import "OTRLog.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRXMPPPresenceSubscriptionRequest.h" #import "OTRXMPPPresenceSubscriptionRequest.h"
@interface OTRProtocolManager () @interface _OTRProtocolManager ()
@property (atomic, readwrite) NSUInteger numberOfConnectedProtocols; //@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
@property (atomic, readwrite) NSUInteger numberOfConnectingProtocols;
@property (nonatomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
@end @end
@implementation OTRProtocolManager @implementation _OTRProtocolManager
-(instancetype)init -(instancetype)init
{ {
@ -50,53 +51,36 @@
if(self) if(self)
{ {
_protocolManagers = [[NSMutableDictionary alloc] init]; _protocolManagers = [[NSMutableDictionary alloc] init];
_numberOfConnectedProtocols = 0;
_numberOfConnectingProtocols = 0;
} }
return self; return self;
} }
- (NSArray<id<OTRProtocol>> *)allProtocols {
return self.protocolManagers.allValues;
}
- (void)removeProtocolForAccount:(OTRAccount *)account - (void)removeProtocolForAccount:(OTRAccount *)account
{ {
NSParameterAssert(account); NSParameterAssert(account);
if (!account) { return; } if (!account) { return; }
id<OTRProtocol> protocol = nil; id<OTRProtocol> protocol = nil;
@synchronized (self) { protocol = [self.protocolManagers objectForKey:account.uniqueId];
protocol = [self.protocolManagers objectForKey:account.uniqueId];
}
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) { if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
[protocol disconnect]; [protocol disconnect];
} }
[self.KVOController unobserve:protocol]; [self.protocolManagers removeObjectForKey:account.uniqueId];
@synchronized (self) {
[self.protocolManagers removeObjectForKey:account.uniqueId];
}
} }
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account - (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
{ {
@synchronized (self) { [self.protocolManagers setObject:protocol forKey:account.uniqueId];
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
}
[self.KVOController observe:protocol keyPath:NSStringFromSelector(@selector(loginStatus)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld action:@selector(protocolDidChange:)];
} }
- (BOOL)existsProtocolForAccount:(OTRAccount *)account - (BOOL)existsProtocolForAccount:(OTRAccount *)account
{ {
NSParameterAssert(account.uniqueId); NSParameterAssert(account.uniqueId);
if (!account.uniqueId) { return NO; } if (!account.uniqueId) { return NO; }
@synchronized (self) { return [self.protocolManagers objectForKey:account.uniqueId] != nil;
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
}
}
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account
{
NSParameterAssert(protocol);
NSParameterAssert(account.uniqueId);
if (!protocol || !account.uniqueId) { return; }
[self addProtocol:protocol forAccount:account];
} }
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account - (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
@ -104,14 +88,12 @@
NSParameterAssert(account); NSParameterAssert(account);
if (!account.uniqueId) { return nil; } if (!account.uniqueId) { return nil; }
id <OTRProtocol> protocol = nil; id <OTRProtocol> protocol = nil;
@synchronized (self) { protocol = [self.protocolManagers objectForKey:account.uniqueId];
protocol = [self.protocolManagers objectForKey:account.uniqueId]; if(!protocol)
if(!protocol) {
{ protocol = [[[account protocolClass] alloc] initWithAccount:account];
protocol = [[[account protocolClass] alloc] initWithAccount:account]; if (protocol && account.uniqueId) {
if (protocol && account.uniqueId) { [self addProtocol:protocol forAccount:account];
[self addProtocol:protocol forAccount:account];
}
} }
} }
return protocol; return protocol;
@ -133,7 +115,22 @@
if (!account) { return; } if (!account) { return; }
id <OTRProtocol> protocol = [self protocolForAccount:account]; id <OTRProtocol> protocol = [self protocolForAccount:account];
[protocol connectUserInitiated:userInitiated]; if([account isKindOfClass:[OTROAuthXMPPAccount class]])
{
[OTROAuthRefresher refreshAccount:(OTROAuthXMPPAccount *)account completion:^(id token, NSError *error) {
if (!error) {
((OTROAuthXMPPAccount *)account).accountSpecificToken = token;
[protocol connectUserInitiated:userInitiated];
}
else {
DDLogError(@"Error Refreshing Token");
}
}];
}
else
{
[protocol connectUserInitiated:userInitiated];
}
} }
- (void)loginAccount:(OTRAccount *)account - (void)loginAccount:(OTRAccount *)account
@ -149,89 +146,19 @@
} }
- (void)goAwayForAllAccounts { - (void)goAwayForAllAccounts {
@synchronized (self) { [self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) { if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
if ([protocol isKindOfClass:[OTRXMPPManager class]]) { OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol; [xmpp goAway];
[xmpp goAway];
}
}];
}
}
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock
{
@synchronized (self) {
dispatch_group_t group = dispatch_group_create();
NSMutableDictionary<NSString*, NSObject<OTRProtocol>*> *observingManagersForTokens = [NSMutableDictionary new];
for (NSObject<OTRProtocol> *manager in self.protocolManagers.allValues) {
OTRXMPPManager *xmpp = (OTRXMPPManager*)manager;
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
DDLogError(@"Wrong protocol class for manager %@", manager);
continue;
}
if (xmpp.loginStatus != OTRLoginStatusDisconnected) {
dispatch_group_enter(group);
NSString *token = [xmpp addObserverForKeyPath:NSStringFromSelector(@selector(loginStatus))
options:0
block:^(NSString *keyPath, OTRXMPPManager *mgr, NSDictionary *change) {
if (mgr.loginStatus == OTRLoginStatusDisconnected) {
dispatch_group_leave(group);
}
}];
observingManagersForTokens[token] = manager;
[manager disconnectSocketOnly:socketOnly];
}
} }
if (timeout > 0) { }];
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t) (timeout * NSEC_PER_SEC)));
}
for (NSString *token in observingManagersForTokens.allKeys) {
[observingManagersForTokens[token] removeObserverForToken:token];
}
if (completionBlock != nil) {
completionBlock();
}
}
}
- (void)disconnectAllAccounts
{
[self disconnectAllAccountsSocketOnly:NO timeout:0 completionBlock:nil];
}
- (void)protocolDidChange:(NSDictionary *)change
{
__block NSUInteger connected = 0;
__block NSUInteger connecting = 0;
@synchronized (self) {
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id<OTRProtocol> _Nonnull obj, BOOL * _Nonnull stop) {
OTRXMPPManager *xmpp = (OTRXMPPManager*)obj;
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
DDLogError(@"Wrong protocol class for account %@", obj);
return;
}
if (xmpp.loginStatus == OTRLoginStatusAuthenticated) {
connected++;
} else if (xmpp.loginStatus == OTRLoginStatusConnecting) {
connecting++;
}
}];
}
self.numberOfConnectedProtocols = connected;
self.numberOfConnectingProtocols = connecting;
} }
-(BOOL)isAccountConnected:(OTRAccount *)account; -(BOOL)isAccountConnected:(OTRAccount *)account;
{ {
BOOL connected = NO; BOOL connected = NO;
id <OTRProtocol> protocol = nil; id <OTRProtocol> protocol = nil;
@synchronized (self) { protocol = [self.protocolManagers objectForKey:account.uniqueId];
protocol = [self.protocolManagers objectForKey:account.uniqueId];
}
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol; OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]); NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
if (![xmpp isKindOfClass:OTRXMPPManager.class]) { if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
@ -250,7 +177,7 @@
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction]; OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction]; account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
} completionBlock:^{ } completionBlock:^{
OTRProtocolManager * protocolManager = [OTRProtocolManager sharedInstance]; OTRProtocolManager * protocolManager = [OTRProtocolManager shared];
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account]; id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
[protocol sendMessage:message]; [protocol sendMessage:message];
}]; }];
@ -264,7 +191,7 @@
if (otrFingerprint.length == 40) { if (otrFingerprint.length == 40) {
message = [message stringByAppendingFormat:@"\n%@", otrFingerprint]; message = [message stringByAppendingFormat:@"\n%@", otrFingerprint];
} }
UIAlertController *alert = [UIAlertController alertControllerWithTitle:ADD_BUDDY_STRING() message:message preferredStyle:(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:ADD_BUDDY_STRING() message:message preferredStyle:(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert];
NSMutableArray<OTRAccount*> *accounts = [NSMutableArray array]; NSMutableArray<OTRAccount*> *accounts = [NSMutableArray array];
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction]; NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
@ -289,7 +216,7 @@
title = account.username; title = account.username;
} }
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
OTRXMPPManager *manager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account]; OTRXMPPManager *manager = (OTRXMPPManager *)[[OTRProtocolManager shared] protocolForAccount:account];
OTRXMPPBuddy *buddy = [manager addToRosterWithJID:jid displayName:nil]; OTRXMPPBuddy *buddy = [manager addToRosterWithJID:jid displayName:nil];

View File

@ -0,0 +1,144 @@
//
// OTRProtocolManager.swift
// ChatSecureCore
//
// Created by Chris Ballinger on 1/22/18.
// Copyright © 2018 Chris Ballinger. All rights reserved.
//
import Foundation
import OTRAssets
@objc
public class OTRProtocolManager: NSObject {
private var protocols: [String:OTRProtocol] = [:]
private var xmppManagers: [XMPPManager] {
return protocols.values.compactMap { $0 as? XMPPManager }
}
@objc
public func existsProtocolForAccount(_ account: OTRAccount) -> Bool {
return existsProtocol(for: account)
}
public func existsProtocol(for account: OTRAccount) -> Bool {
return protocols[account.uniqueId] != nil
}
@objc
public func protocolForAccount(_ account: OTRAccount) -> OTRProtocol? {
return protocols[account.uniqueId]
}
public func `protocol`(for account: OTRAccount) -> OTRProtocol? {
return protocolForAccount(account)
}
@objc
public func xmppManagerForAccount(_ account: OTRAccount) -> XMPPManager? {
return xmppManager(for: account)
}
public func xmppManager(for account: OTRAccount) -> XMPPManager? {
return protocolForAccount(account) as? XMPPManager
}
@objc
public func removeProtocolForAccount(_ account: OTRAccount) {
removeProtocolForAccount(account)
}
public func removeProtocol(for account: OTRAccount) {
protocols[account.uniqueId] = nil
}
@objc
public func isAccountConnected(_ account: OTRAccount) -> Bool {
return xmppManager(for: account)?.loginStatus == .authenticated
}
@objc
public func loginAccount(_ account: OTRAccount) {
loginAccount(account, userInitiated: false)
}
@objc
public func loginAccount(_ account: OTRAccount, userInitiated: Bool) {
xmppManager(for: account)?.connectUserInitiated(userInitiated)
}
@objc
public func loginAccounts(_ accounts: [OTRAccount]) {
accounts.forEach {
self.loginAccount($0)
}
}
@objc
public func goAwayForAllAccounts() {
xmppManagers.forEach {
$0.goAway()
}
}
@objc
public func sendMessage(_ message: OTROutgoingMessage) {
send(message)
}
/// This should probably be moved elsewhere
public func send(_ message: OTROutgoingMessage) {
let _account = OTRDatabaseManager.shared.connections?.read.fetch {
message.buddy(with: $0)?.account(with: $0)
}
guard let account = _account else { return }
xmppManager(for: account)?.send(message)
}
@objc
public func disconnectAllAccounts() {
disconnectAllAccountsSocketOnly(false, timeout: 0, completionBlock: nil)
}
@objc
public func disconnectAllAccountsSocketOnly(_ socketOnly: Bool,
timeout: TimeInterval,
completionBlock: (()->Void)?) {
let group = DispatchGroup()
var observers: [NSKeyValueObservation] = []
xmppManagers.forEach { (xmpp) in
guard xmpp.loginStatus != .disconnected else {
return
}
group.enter()
let observer = xmpp.observe(\.loginStatus, changeHandler: { (xmpp, change) in
if xmpp.loginStatus == .disconnected {
group.leave()
}
})
observers.append(observer)
xmpp.disconnectSocketOnly(socketOnly)
}
group.notify(queue: .main) {
observers.removeAll()
completionBlock?()
}
}
}
public extension OTRProtocolManager {
#if DEBUG
/// when OTRBranding.pushStagingAPIURL is nil (during tests) a valid value must be supplied for the integration tests to pass
private static let pushApiEndpoint: URL = OTRBranding.pushStagingAPIURL ?? URL(string: "http://localhost")!
#else
private static let pushApiEndpoint: URL = OTRBranding.pushAPIURL
#endif
@objc public static let encryptionManager = OTREncryptionManager()
@objc public static let shared = OTRProtocolManager()
@objc public static func sharedInstance() -> OTRProtocolManager {
return OTRProtocolManager.shared
}
@objc public static let pushController = PushController(baseURL: OTRProtocolManager.pushApiEndpoint, sessionConfiguration: URLSessionConfiguration.ephemeral)
}

View File

@ -0,0 +1,48 @@
//
// OTRPurchaseController.h
// Off the Record
//
// Created by Christopher Ballinger on 9/28/12.
// Copyright (c) 2012 Chris Ballinger. All rights reserved.
//
// This file is part of ChatSecure.
//
// ChatSecure is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ChatSecure is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "OTRStoreTableViewCell.h"
#define kOTRPurchaseControllerProductUpdateNotification @"kOTRPurchaseControllerProductUpdateNotification"
@protocol OTRPurchaseControllerDelegate <NSObject>
@required
- (void) productsUpdated:(NSArray*)products;
@end
@interface OTRPurchaseController : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic, weak) id<OTRPurchaseControllerDelegate> delegate;
@property (nonatomic, strong) NSArray *products;
- (void) requestProducts;
- (void) buyProduct:(SKProduct *)product;
- (void) restorePurchases;
- (BOOL) isProductIdentifierPurchased:(NSString*)productIdentifier;
- (void) setProductIdentifier:(NSString*)productIdentifier purchased:(BOOL)purchased;
+ (OTRPurchaseController*) sharedInstance;
@end

View File

@ -0,0 +1,176 @@
//
// OTRPurchaseController.m
// Off the Record
//
// Created by Christopher Ballinger on 9/28/12.
// Copyright (c) 2012 Chris Ballinger. All rights reserved.
//
// This file is part of ChatSecure.
//
// ChatSecure is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ChatSecure is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
#import "OTRPurchaseController.h"
#import "AFNetworking.h"
#import "Strings.h"
#import "OTRPushAPIClient.h"
#define REQUEST_PRODUCT_IDENTIFIERS @"request_product_identifiers"
#define PRODUCT_IDENTIFIERS_KEY @"identifiers"
#define PRODUCTS_KEY @"products"
@implementation OTRPurchaseController
@synthesize products;
- (void) dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (id) init {
if (self = [super init]) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
+ (OTRPurchaseController*)sharedInstance
{
static dispatch_once_t once;
static OTRPurchaseController *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[OTRPurchaseController alloc] init];
});
return sharedInstance;
}
- (void) requestProducts {
if (products) {
[self.delegate productsUpdated:products];
} else {
[self requestProductIdentifiers];
}
}
- (void) requestProductIdentifiers {
// Code to request product identifiers here
NSURL *requestURL = [NSURL URLWithString:REQUEST_PRODUCT_IDENTIFIERS relativeToURL:[OTRPushAPIClient sharedClient].baseURL];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:requestURL.absoluteString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
[self fetchProductsWithIdentifiers:[NSSet setWithArray:[responseObject objectForKey:PRODUCT_IDENTIFIERS_KEY]]];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error loading product identifiers: %@%@", [error localizedDescription], [error userInfo]);
}];
}
- (void) fetchProductsWithIdentifiers:(NSSet*)identifiers {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
request.delegate = self;
[request start];
}
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
if ([response.invalidProductIdentifiers count] > 0) {
NSLog(@"Invalid products identifiers: %@", [response.invalidProductIdentifiers description]);
}
self.products = response.products;
[self.delegate productsUpdated:products];
}
- (void) buyProduct:(SKProduct *)product {
if ([SKPaymentQueue canMakePayments]) {
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:ERROR_STRING message:PAYMENTS_SETUP_ERROR_STRING delegate:nil cancelButtonTitle:nil otherButtonTitles:OK_STRING, nil];
[alert show];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
NSLog(@"Transaction: %@", transaction.transactionIdentifier);
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
NSLog(@"Transaction purchased");
//[[OTRPushController sharedInstance] registerWithReceipt:transaction.transactionReceipt resetAccount:NO];
[self setProductIdentifier:transaction.payment.productIdentifier purchased:YES];
[self sendProductUpdateNotification];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"Transaction failed");
[self sendProductUpdateNotification];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(@"Original transaction restored: %@", transaction.originalTransaction.transactionIdentifier);
//[[OTRPushController sharedInstance] registerWithReceipt:transaction.transactionReceipt resetAccount:YES];
[self setProductIdentifier:transaction.payment.productIdentifier purchased:YES];
[self sendProductUpdateNotification];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
NSLog(@"Transaction restored");
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"Purchasing transaction... ");
break;
default:
break;
}
}
}
- (void) sendProductUpdateNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:kOTRPurchaseControllerProductUpdateNotification object:self];
}
- (BOOL) isProductIdentifierPurchased:(NSString*)productIdentifier {
NSMutableDictionary *productsDictionary = [self productsDictionary];
NSNumber *productValue = [productsDictionary objectForKey:productIdentifier];
if (!productValue) {
return NO;
}
return [productValue boolValue];
}
- (NSMutableDictionary*) productsDictionary {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *productDictionary = [NSMutableDictionary dictionaryWithDictionary:[defaults objectForKey:PRODUCTS_KEY]];
if (!productDictionary) {
productDictionary = [NSMutableDictionary dictionary];
}
return productDictionary;
}
- (void) saveProductsDictionary:(NSMutableDictionary*)productsDictionary {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:productsDictionary forKey:PRODUCTS_KEY];
BOOL success = [defaults synchronize];
if (!success) {
NSLog(@"Product preferences not saved to disk!");
}
}
- (void) setProductIdentifier:(NSString*)productIdentifier purchased:(BOOL)purchased {
NSMutableDictionary *productsDictionary = [self productsDictionary];
[productsDictionary setObject:[NSNumber numberWithBool:purchased] forKey:productIdentifier];
[self saveProductsDictionary:productsDictionary];
}
- (void) restorePurchases {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
@end

View File

@ -0,0 +1,20 @@
//
// OTRPushTLVHandler.h
// ChatSecure
//
// Created by David Chiles on 9/28/15.
// Copyright © 2015 Chris Ballinger. All rights reserved.
//
@import Foundation;
@import OTRKit;
#import "OTRPushTLVHandlerProtocols.h"
@interface OTRPushTLVHandler : NSObject <OTRTLVHandler, OTRPushTLVHandlerProtocol>
@property (nonatomic, weak, readwrite) id<OTRPushTLVHandlerDelegate> delegate;
@property (nonatomic, weak, readwrite) OTRKit *otrKit;
- (instancetype)initWithOTRKit:(OTRKit *)otrKit delegate:(id<OTRPushTLVHandlerDelegate>)delegate;
@end

View File

@ -0,0 +1,43 @@
//
// OTRPushTLVHandler.m
// ChatSecure
//
// Created by David Chiles on 9/28/15.
// Copyright © 2015 Chris Ballinger. All rights reserved.
//
#import "OTRPushTLVHandler.h"
@import OTRKit;
static const uint16_t OTRPushTLVType = 0x01A4;
@implementation OTRPushTLVHandler
- (instancetype)initWithOTRKit:(OTRKit *)otrKit delegate:(id<OTRPushTLVHandlerDelegate>)delegate;
{
if (self = [self init]) {
self.otrKit = otrKit;
self.delegate = delegate;
[self.otrKit registerTLVHandler:self];
}
return self;
}
- (NSArray *)handledTLVTypes
{
return @[@(OTRPushTLVType)];
}
- (void)receiveTLV:(OTRTLV *)tlv username:(NSString *)username accountName:(NSString *)accountName protocol:(NSString *)protocol fingerprint:(OTRFingerprint *)fingerprint tag:(id)tag
{
[self.delegate receivePushData:tlv.data username:username accountName:accountName protocolString:protocol fingerprint:fingerprint];
}
- (void)sendPushData:(NSData *)data username:(NSString *)username accountName:(NSString *)accountName protocol:(NSString *)protocol
{
OTRTLV *tlv = [[OTRTLV alloc] initWithType:OTRPushTLVType data:data];
[self.otrKit encodeMessage:nil tlvs:@[tlv] username:username accountName:accountName protocol:protocol tag:nil];
}
@end

View File

@ -0,0 +1,28 @@
//
// OTRPushTLVHandlerDelegateProtocol.h
// ChatSecure
//
// Created by David Chiles on 9/28/15.
// Copyright © 2015 Chris Ballinger. All rights reserved.
//
@class OTRFingerprint;
@protocol OTRPushTLVHandlerDelegate
@required
- (void)receivePushData:(NSData *)tlvData
username:(NSString *)username
accountName:(NSString *)accountName
protocolString:(NSString *)protocolString
fingerprint:(OTRFingerprint *)fingerprint;
@end
@protocol OTRPushTLVHandlerProtocol
@required
- (void)sendPushData:(NSData *)data
username:(NSString *)username
accountName:(NSString *)accountName
protocol:(NSString *)protocol;
@end

View File

@ -36,7 +36,7 @@
#import "OTRIntSetting.h" #import "OTRIntSetting.h"
#import "OTRCertificateSetting.h" #import "OTRCertificateSetting.h"
#import "OTRUtilities.h" #import "OTRUtilities.h"
#import "ChatSecureCoreCompat-Swift.h" #import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRUtilities.h" #import "OTRUtilities.h"
@ -64,11 +64,11 @@
[settingsGroups addObject:accountsGroup]; [settingsGroups addObject:accountsGroup];
if (OTRBranding.allowsDonation) { if (OTRBranding.allowsDonation) {
NSString *donateTitle = nil; NSString *donateTitle = DONATE_STRING();
if (TransactionObserver.hasValidReceipt) { if (TransactionObserver.hasValidReceipt) {
donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()]; donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()];
} else { } else {
donateTitle = [NSString stringWithFormat:@"%@ 🎁", DONATE_STRING()]; donateTitle = [NSString stringWithFormat:@"%@ 🆕", DONATE_STRING()];
} }
OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil]; OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil];
//donateSetting.imageName = @"29-heart.png"; //donateSetting.imageName = @"29-heart.png";
@ -101,18 +101,16 @@
description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING() description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING()
settingsKey:kOTRSettingKeyAllowDBPassphraseBackup]; settingsKey:kOTRSettingKeyAllowDBPassphraseBackup];
if ([PushController getPushPreference] != PushPreferenceEnabled) { if (![PushController canReceivePushNotifications] ||
[PushController getPushPreference] != PushPreferenceEnabled) {
OTRViewSetting *pushViewSetting = [[OTRViewSetting alloc] initWithTitle:CHATSECURE_PUSH_STRING() description:nil viewControllerClass:[EnablePushViewController class]]; OTRViewSetting *pushViewSetting = [[OTRViewSetting alloc] initWithTitle:CHATSECURE_PUSH_STRING() description:nil viewControllerClass:[EnablePushViewController class]];
pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator; pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]]; OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]];
[settingsGroups addObject:pushGroup]; [settingsGroups addObject:pushGroup];
} }
OTRStorageUsageSetting *storageUsageSetting = [[OTRStorageUsageSetting alloc] initWithTitle:STORAGE_USAGE_TITLE()
description:STORAGE_USAGE_DESCRIPTION()];
storageUsageSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSArray *chatSettings = @[deletedDisconnectedConversations, storageUsageSetting]; NSArray *chatSettings = @[deletedDisconnectedConversations];
OTRSettingsGroup *chatSettingsGroup = [[OTRSettingsGroup alloc] initWithTitle:CHAT_STRING() settings:chatSettings]; OTRSettingsGroup *chatSettingsGroup = [[OTRSettingsGroup alloc] initWithTitle:CHAT_STRING() settings:chatSettings];
[settingsGroups addObject:chatSettingsGroup]; [settingsGroups addObject:chatSettingsGroup];

View File

@ -0,0 +1,55 @@
//
// OTROTRSignalEncryptionHelper.swift
// ChatSecure
//
// Created by David Chiles on 10/3/16.
// Copyright © 2016 Chris Ballinger. All rights reserved.
//
import UIKit
import OTRKit
class OTRSignalEncryptionHelper {
/**
Encrypt data with IV and key using aes-128-gcm
- parameter data: The data to be encrypted.
- parameter key The symmetric key
- parameter iv The initialization vector
returns: The encrypted data
*/
class func encryptData(_ data:Data, key:Data, iv:Data) throws -> OTRCryptoData? {
return try OTRCryptoUtility.encryptAESGCMData(data, key: key, iv: iv)
}
/**
Decrypt data with IV and key using aes-128-gcm
- parameter data: The data to be decrypted.
- parameter key The symmetric key
- parameter iv The initialization vector
returns: The Decrypted data
*/
class func decryptData(_ data:Data, key:Data, iv:Data, authTag:Data) throws -> Data? {
let cryptoData = OTRCryptoData(data: data, authTag: authTag)
return try OTRCryptoUtility.decryptAESGCMData(cryptoData, key: key, iv: iv)
}
/** Generates random data of length 16 bytes */
fileprivate class func randomDataOfBlockLength() -> Data? {
return OTRPasswordGenerator.randomData(withLength: 16)
}
/** Generates random key of length 16 bytes*/
class func generateSymmetricKey() -> Data? {
return self.randomDataOfBlockLength()
}
/** Generates random iv of length 16 bytes */
class func generateIV() -> Data? {
return self.randomDataOfBlockLength()
}
}

View File

@ -101,7 +101,7 @@ extension OMEMOBundle {
} }
} }
public protocol OTRSignalStorageManagerDelegate: AnyObject { public protocol OTRSignalStorageManagerDelegate: class {
/** Generate a new account key*/ /** Generate a new account key*/
func generateNewIdenityKeyPairForAccountKey(_ accountKey:String) -> OTRAccountSignalIdentity func generateNewIdenityKeyPairForAccountKey(_ accountKey:String) -> OTRAccountSignalIdentity
} }
@ -110,8 +110,8 @@ public protocol OTRSignalStorageManagerDelegate: AnyObject {
* This class implements the SignalStore protocol. One OTRSignalStorageManager should be created per account key/collection. * This class implements the SignalStore protocol. One OTRSignalStorageManager should be created per account key/collection.
*/ */
open class OTRSignalStorageManager: NSObject { open class OTRSignalStorageManager: NSObject {
public let accountKey:String open let accountKey:String
public let databaseConnection:YapDatabaseConnection open let databaseConnection:YapDatabaseConnection
open weak var delegate:OTRSignalStorageManagerDelegate? open weak var delegate:OTRSignalStorageManagerDelegate?
/** /**
@ -232,7 +232,7 @@ open class OTRSignalStorageManager: NSObject {
} }
let query = YapDatabaseQuery(string: "WHERE (OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName) = ?", parameters: ["\(self.accountKey)"]) let query = YapDatabaseQuery(string: "WHERE (OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName) = ?", parameters: ["\(self.accountKey)"])
let _ = secondaryIndexTransaction.iterateKeysAndObjects(matching: query, using: { (collection, key, object, stop) in secondaryIndexTransaction.enumerateKeysAndObjects(matching: query, using: { (collection, key, object, stop) in
guard let preKey = object as? OTRSignalPreKey else { guard let preKey = object as? OTRSignalPreKey else {
return return
} }

View File

@ -7,14 +7,12 @@
// //
@import Foundation; @import Foundation;
@class CPAProxyManager; @import CPAProxy;
NS_ASSUME_NONNULL_BEGIN
@interface OTRTorManager : NSObject @interface OTRTorManager : NSObject
@property (nonatomic, strong, nullable) CPAProxyManager *torManager; @property (nonatomic, strong) CPAProxyManager *torManager;
+ (instancetype) sharedInstance; + (instancetype) sharedInstance;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,49 @@
//
// OTRTorManager.m
// ChatSecure
//
// Created by Christopher Ballinger on 10/3/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
#import "OTRTorManager.h"
@import CPAProxy;
@implementation OTRTorManager
- (instancetype) init {
if (self = [super init]) {
// Get resource paths for the torrc and geoip files from the main bundle
NSBundle *cpaProxyFrameworkBundle = [NSBundle bundleForClass:[CPAProxyManager class]];
NSURL *cpaProxyBundleURL = [cpaProxyFrameworkBundle URLForResource:@"CPAProxy" withExtension:@"bundle"];
NSBundle *cpaProxyBundle = [[NSBundle alloc] initWithURL:cpaProxyBundleURL];
NSParameterAssert(cpaProxyBundle != nil);
NSString *torrcPath = [[NSBundle mainBundle] pathForResource:@"torrc" ofType:nil]; // use custom torrc
NSString *geoipPath = [cpaProxyBundle pathForResource:@"geoip" ofType:nil];
NSString *dataDirectory = [[[[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"com.ChatSecure.Tor"] path];
// Initialize a CPAProxyManager
CPAConfiguration *configuration = [CPAConfiguration configurationWithTorrcPath:torrcPath geoipPath:geoipPath torDataDirectoryPath:dataDirectory];
configuration.isolateDestinationAddress = YES;
configuration.isolateDestinationPort = YES;
self.torManager = [CPAProxyManager proxyWithConfiguration:configuration];
}
return self;
}
#pragma - mark Singleton Methodd
+ (instancetype)sharedInstance
{
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
@end

View File

@ -12,7 +12,7 @@ import XMPPFramework
@objc open class OTRXMPPChangeAvatar: NSObject { @objc open class OTRXMPPChangeAvatar: NSObject {
open weak var xmppvCardTempModule:XMPPvCardTempModule? open weak var xmppvCardTempModule:XMPPvCardTempModule?
public let photoData:Data open let photoData:Data
fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: []) fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: [])
fileprivate var waitingForVCardFetch:Bool = false fileprivate var waitingForVCardFetch:Bool = false

View File

@ -7,7 +7,8 @@
// //
import Foundation import Foundation
import YapDatabase import YapDatabase.YapDatabaseFullTextSearch
import YapDatabase.YapDatabaseSearchResultsView
open class OTRYapExtensions:NSObject { open class OTRYapExtensions:NSObject {
@ -17,7 +18,7 @@ open class OTRYapExtensions:NSObject {
let usernameColumnName = BuddyFTSColumnName.username.name() let usernameColumnName = BuddyFTSColumnName.username.name()
let displayNameColumnName = BuddyFTSColumnName.displayName.name() let displayNameColumnName = BuddyFTSColumnName.displayName.name()
let searchHandler = YapDatabaseFullTextSearchHandler.withObjectBlock { (transaction, dict, collection, key, object) in let searchHandler = YapDatabaseFullTextSearchHandler.withObjectBlock { (dict, collection, key, object) in
guard let buddy = object as? OTRBuddy else { guard let buddy = object as? OTRBuddy else {
return return
} }

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import YapDatabase import YapDatabase.YapDatabaseView
@objc public protocol OTRYapViewHandlerDelegateProtocol:NSObjectProtocol { @objc public protocol OTRYapViewHandlerDelegateProtocol:NSObjectProtocol {

View File

@ -10,13 +10,12 @@ import Foundation
import ChatSecure_Push_iOS import ChatSecure_Push_iOS
import YapDatabase import YapDatabase
import UserNotifications import UserNotifications
import OTRKit
@objc public protocol PushControllerProtocol { @objc public protocol PushControllerProtocol {
func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void) func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void)
func receiveRemoteNotification(_ notification:[AnyHashable: Any], completion: @escaping (_ buddy:OTRBuddy?, _ error:NSError?) -> Void) func receiveRemoteNotification(_ notification:[AnyHashable: Any], completion: @escaping (_ buddy:OTRBuddy?, _ error:NSError?) -> Void)
func pushStorage() -> PushStorageProtocol? func pushStorage() -> PushStorageProtocol
} }
@objc public enum PushPreference: Int { @objc public enum PushPreference: Int {
@ -69,38 +68,33 @@ public class PushInfo: NSObject {
The purpose of this class is to tie together the api client and the data store, YapDatabase. The purpose of this class is to tie together the api client and the data store, YapDatabase.
It also provides some helper methods that makes dealing with the api easier It also provides some helper methods that makes dealing with the api easier
*/ */
open class PushController: NSObject, PushControllerProtocol { open class PushController: NSObject, OTRPushTLVHandlerDelegate, PushControllerProtocol {
private var _storage: PushStorageProtocol?
private var storage: PushStorageProtocol? {
if _storage == nil, let storage: PushStorageProtocol
let write = connections?.write {
let storage = PushStorage(databaseConnection: write)
finishStorageSetup(storage: storage)
_storage = storage
}
return _storage
}
var apiClient : Client var apiClient : Client
var callbackQueue = OperationQueue() var callbackQueue = OperationQueue()
var otrListener: PushOTRListener?
let timeBufffer:TimeInterval = 60*60*24 let timeBufffer:TimeInterval = 60*60*24
var pubsubEndpoint: NSString? var pubsubEndpoint: NSString?
private var connections: DatabaseConnections? {
return OTRDatabaseManager.shared.connections
}
@objc public init(baseURL: URL, sessionConfiguration: URLSessionConfiguration, databaseConnection: YapDatabaseConnection? = nil) { @objc public init(baseURL: URL, sessionConfiguration: URLSessionConfiguration, databaseConnection: YapDatabaseConnection? = nil, tlvHandler:OTRPushTLVHandlerProtocol? = nil) {
let databaseConnection = databaseConnection ?? OTRDatabaseManager.shared.writeConnection!
self.apiClient = Client(baseUrl: baseURL, urlSessionConfiguration: sessionConfiguration, account: nil) self.apiClient = Client(baseUrl: baseURL, urlSessionConfiguration: sessionConfiguration, account: nil)
self.storage = PushStorage(databaseConnection: databaseConnection)
super.init() super.init()
} // We need to make sure we aren't doing a blocking read on a read/write connection
// because this causes long pauses on app launch
private func finishStorageSetup(storage: PushStorageProtocol) { let readConnection = OTRDatabaseManager.shared.database?.newConnection()
var account: Account? = nil; var account: Account? = nil;
connections?.read.asyncRead({ (transaction) in readConnection?.asyncRead({ (transaction) in
account = storage.thisDevicePushAccount() account = self.storage.thisDevicePushAccount()
}, completionBlock: { }, completionBlock: {
self.apiClient.account = account self.apiClient.account = account
storage.removeAllOurExpiredUnusedTokens(self.timeBufffer, completion: nil) self.otrListener = PushOTRListener(storage: self.storage, pushController: self, tlvHandler: tlvHandler)
self.storage.removeAllOurExpiredUnusedTokens(self.timeBufffer, completion: nil)
}) })
} }
@ -108,7 +102,7 @@ open class PushController: NSObject, PushControllerProtocol {
public func deactivate(completion: (()->())?, callbackQueue: DispatchQueue?) { public func deactivate(completion: (()->())?, callbackQueue: DispatchQueue?) {
apiClient.unregister { (success, error) in apiClient.unregister { (success, error) in
PushController.setPushPreference(.disabled) PushController.setPushPreference(.disabled)
self.storage?.deleteEverything(completion: completion, callbackQueue: callbackQueue) self.storage.deleteEverything(completion: completion, callbackQueue: callbackQueue)
self.apiClient.account = nil self.apiClient.account = nil
} }
} }
@ -147,7 +141,7 @@ open class PushController: NSObject, PushControllerProtocol {
self.apiClient.registerNewUser(username, password: password, email: nil) {[weak self] (account, error) -> Void in self.apiClient.registerNewUser(username, password: password, email: nil) {[weak self] (account, error) -> Void in
if let newAccount = account { if let newAccount = account {
self?.apiClient.account = newAccount self?.apiClient.account = newAccount
self?.storage?.saveThisAccount(newAccount) self?.storage.saveThisAccount(newAccount)
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil) completion(true, nil)
}) })
@ -164,14 +158,14 @@ open class PushController: NSObject, PushControllerProtocol {
- returns: The push storage object that controls storing and retrieving push tokens - returns: The push storage object that controls storing and retrieving push tokens
*/ */
@objc open func pushStorage() -> PushStorageProtocol? { @objc open func pushStorage() -> PushStorageProtocol {
return self.storage return self.storage
} }
@objc open func registerThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) { @objc open func registerThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
self.apiClient.registerDevice(apns, name: nil, deviceID: nil) {[weak self] (device, error) -> Void in self.apiClient.registerDevice(apns, name: nil, deviceID: nil) {[weak self] (device, error) -> Void in
if let newDevice = device { if let newDevice = device {
self?.storage?.saveThisDevice(newDevice) self?.storage.saveThisDevice(newDevice)
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil) completion(true, nil)
}) })
@ -187,7 +181,7 @@ open class PushController: NSObject, PushControllerProtocol {
@objc open func updateThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) { @objc open func updateThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
DispatchQueue.global().async {[weak self] () -> Void in DispatchQueue.global().async {[weak self] () -> Void in
guard let device = self?.storage?.thisDevice() else { guard let device = self?.storage.thisDevice() else {
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil)) completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
@ -204,7 +198,7 @@ open class PushController: NSObject, PushControllerProtocol {
self?.apiClient.updateDevice(id, APNSToken: apns, name: device.name, deviceID: device.id, completion: {[weak self] (device, error) -> Void in self?.apiClient.updateDevice(id, APNSToken: apns, name: device.name, deviceID: device.id, completion: {[weak self] (device, error) -> Void in
if let newDevice = device { if let newDevice = device {
self?.storage?.saveThisDevice(newDevice) self?.storage.saveThisDevice(newDevice)
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil) completion(true, nil)
}) })
@ -240,7 +234,7 @@ open class PushController: NSObject, PushControllerProtocol {
@objc open func getNewPushToken(_ buddyKey:String?, completion:@escaping (_ token:TokenContainer?,_ error:NSError?) -> Void) { @objc open func getNewPushToken(_ buddyKey:String?, completion:@escaping (_ token:TokenContainer?,_ error:NSError?) -> Void) {
DispatchQueue.global().async {[weak self] () -> Void in DispatchQueue.global().async {[weak self] () -> Void in
guard let tokenContainer = self?.storage?.unusedToken() else { guard let tokenContainer = self?.storage.unusedToken() else {
self?.updateUnusedTokenStore({[weak self] (success, error) -> Void in self?.updateUnusedTokenStore({[weak self] (success, error) -> Void in
if success { if success {
self?.getNewPushToken(buddyKey, completion: completion) self?.getNewPushToken(buddyKey, completion: completion)
@ -253,11 +247,11 @@ open class PushController: NSObject, PushControllerProtocol {
return return
} }
self?.storage?.removeUnusedToken(tokenContainer) self?.storage.removeUnusedToken(tokenContainer)
if let buddyKey = buddyKey { if let buddyKey = buddyKey {
self?.storage?.associateBuddy(tokenContainer, buddyKey: buddyKey) self?.storage.associateBuddy(tokenContainer, buddyKey: buddyKey)
} else { } else {
self?.storage?.saveUsedToken(tokenContainer) self?.storage.saveUsedToken(tokenContainer)
} }
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(tokenContainer, nil) completion(tokenContainer, nil)
@ -276,7 +270,7 @@ open class PushController: NSObject, PushControllerProtocol {
return return
} }
tokenContainer.pushToken = newToken tokenContainer.pushToken = newToken
self?.storage?.saveUnusedToken(tokenContainer) self?.storage.saveUnusedToken(tokenContainer)
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(true,nil) completion(true,nil)
}) })
@ -299,7 +293,7 @@ open class PushController: NSObject, PushControllerProtocol {
@objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) { @objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) {
DispatchQueue.global().async {[weak self] () -> Void in DispatchQueue.global().async {[weak self] () -> Void in
guard let id = self?.storage?.thisDevice()?.id else { guard let id = self?.storage.thisDevice()?.id else {
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil)) completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
}) })
@ -308,11 +302,11 @@ open class PushController: NSObject, PushControllerProtocol {
var tokensToCreate:UInt = 0 var tokensToCreate:UInt = 0
guard let unusedTokens = self?.storage?.numberUnusedTokens() else { guard let unusedTokens = self?.storage.numberUnusedTokens() else {
return; return;
} }
guard let minimumCount = self?.storage?.unusedTokenStoreMinimum() else { guard let minimumCount = self?.storage.unusedTokenStoreMinimum() else {
return; return;
} }
@ -369,7 +363,7 @@ open class PushController: NSObject, PushControllerProtocol {
tokenContainer.pushToken = token tokenContainer.pushToken = token
tokenContainer.endpoint = endpointURL tokenContainer.endpoint = endpointURL
tokenContainer.buddyKey = buddyKey tokenContainer.buddyKey = buddyKey
self?.storage?.saveUsedToken(tokenContainer) self?.storage.saveUsedToken(tokenContainer)
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil) completion(true, nil)
}) })
@ -412,7 +406,7 @@ open class PushController: NSObject, PushControllerProtocol {
public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) { public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) {
do { do {
let message = try Deserializer.messageFromPushDictionary(notification) let message = try Deserializer.messageFromPushDictionary(notification)
guard let buddy = self.storage?.buddy(message.token) else { guard let buddy = self.storage.buddy(message.token) else {
self.callbackQueue.addOperation({ () -> Void in self.callbackQueue.addOperation({ () -> Void in
completion(nil, NSError.chatSecureError(PushError.noBuddyFound, userInfo: nil)) completion(nil, NSError.chatSecureError(PushError.noBuddyFound, userInfo: nil))
}) })
@ -437,7 +431,7 @@ open class PushController: NSObject, PushControllerProtocol {
@objc open func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void) { @objc open func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void) {
DispatchQueue.global().async {[weak self] () -> Void in DispatchQueue.global().async {[weak self] () -> Void in
do { do {
guard let token = try self?.storage?.tokensForBuddy(buddyKey, createdByThisAccount: false).first else { guard let token = try self?.storage.tokensForBuddy(buddyKey, createdByThisAccount: false).first else {
self?.callbackQueue.addOperation({ () -> Void in self?.callbackQueue.addOperation({ () -> Void in
completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil)) completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil))
}) })
@ -464,7 +458,7 @@ open class PushController: NSObject, PushControllerProtocol {
if ((error as NSError?)?.code == 404) { if ((error as NSError?)?.code == 404) {
// Token was revoked or was never valid. // Token was revoked or was never valid.
self?.storage?.removeToken(token) self?.storage.removeToken(token)
// Retry and see if we have another token to use or will error out with noTokensFound // Retry and see if we have another token to use or will error out with noTokensFound
self?.sendKnock(buddyKey, completion: completion) self?.sendKnock(buddyKey, completion: completion)
} }
@ -547,7 +541,7 @@ open class PushController: NSObject, PushControllerProtocol {
//MARK: OTRPushTLVHandlerDelegate //MARK: OTRPushTLVHandlerDelegate
@objc open func receivePush(_ tlvData: Data!, username: String!, accountName: String!, protocolString: String!, fingerprint:OTRFingerprint!) { @objc open func receivePush(_ tlvData: Data!, username: String!, accountName: String!, protocolString: String!, fingerprint:OTRFingerprint!) {
let buddy = self.storage?.buddy(username, accountName: accountName) let buddy = self.storage.buddy(username, accountName: accountName)
guard let buddyKey = buddy?.uniqueId else { guard let buddyKey = buddy?.uniqueId else {
//Error fetching buddy //Error fetching buddy
@ -566,7 +560,7 @@ open class PushController: NSObject, PushControllerProtocol {
} }
// Don't store tokens for Tor accounts // Don't store tokens for Tor accounts
let account = self.storage?.account(buddy!.accountUniqueId) let account = self.storage.account(buddy!.accountUniqueId)
if account?.accountType == OTRAccountType.xmppTor { if account?.accountType == OTRAccountType.xmppTor {
return return
} }
@ -584,7 +578,7 @@ open class PushController: NSObject, PushControllerProtocol {
//MARK: Push Preferences //MARK: Push Preferences
@objc public static func getPushPreference() -> PushPreference { @objc open static func getPushPreference() -> PushPreference {
guard let value = UserDefaults.standard.object(forKey: kOTRPushEnabledKey) as? NSNumber else { guard let value = UserDefaults.standard.object(forKey: kOTRPushEnabledKey) as? NSNumber else {
return .undefined return .undefined
} }
@ -595,7 +589,7 @@ open class PushController: NSObject, PushControllerProtocol {
} }
} }
public static func setPushPreference(_ preference: PushPreference) { open static func setPushPreference(_ preference: PushPreference) {
var bool = false var bool = false
if preference == .enabled { if preference == .enabled {
bool = true bool = true
@ -607,79 +601,79 @@ open class PushController: NSObject, PushControllerProtocol {
//MARK: Utility //MARK: Utility
/// If callbackQueue is nil, it will complete on main queue /// If callbackQueue is nil, it will complete on main queue
public func gatherPushInfo(completion: @escaping (PushInfo?) -> (), callbackQueue: DispatchQueue = DispatchQueue.main) { public func gatherPushInfo(completion: @escaping (PushInfo) -> (), callbackQueue: DispatchQueue?) {
guard let storage = self.storage else {
callbackQueue.async {
completion(nil)
}
return
}
var pubsubEndpoint: String? var pubsubEndpoint: String?
var pushPermitted = false var pushPermitted = false
let group = DispatchGroup() let group = DispatchGroup()
group.enter() group.enter()
DispatchQueue.main.async { pushPermitted = PushController.canReceivePushNotifications() // This will be async in a later version when we do iOS 10 refactor
PushController.canReceivePushNotifications(completion: { (enabled) in
pushPermitted = enabled
group.leave()
})
}
group.enter() group.enter()
group.leave()
getPubsubEndpoint { (endpoint, error) in getPubsubEndpoint { (endpoint, error) in
pubsubEndpoint = endpoint pubsubEndpoint = endpoint
group.leave() group.leave()
} }
group.notify(queue: .main) { let queue = callbackQueue ?? DispatchQueue.main
let device = storage.thisDevice() group.notify(queue: DispatchQueue.global(qos: .default)) {
let device = self.storage.thisDevice()
let newPushInfo = PushInfo( let newPushInfo = PushInfo(
pushAPIURL: self.apiClient.baseUrl, pushAPIURL: self.apiClient.baseUrl,
hasPushAccount: storage.hasPushAccount(), hasPushAccount: self.storage.hasPushAccount(),
numUsedTokens: storage.numberUsedTokens(), numUsedTokens: self.storage.numberUsedTokens(),
numUnusedTokens: storage.numberUnusedTokens(), numUnusedTokens: self.storage.numberUnusedTokens(),
pushPermitted: pushPermitted, pushPermitted: pushPermitted,
pubsubEndpoint: pubsubEndpoint, pubsubEndpoint: pubsubEndpoint,
device: device) device: device)
callbackQueue.async { queue.async {
completion(newPushInfo) completion(newPushInfo)
} }
} }
} }
@objc public static func registerForPushNotifications() { @objc open static func registerForPushNotifications() {
let center = UNUserNotificationCenter.current() if #available(iOS 10.0, *) {
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in let center = UNUserNotificationCenter.current()
DispatchQueue.main.async(execute: { center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
// TODO: Handle push registration error DispatchQueue.main.async(execute: {
let app = UIApplication.shared // TODO: Handle push registration error
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil) let app = UIApplication.shared
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil)
if (granted) {
app.registerForRemoteNotifications()
}
})
}) })
})
UIApplication.shared.registerForRemoteNotifications()
}
@objc public static func canReceivePushNotifications(completion: @escaping (Bool)->Void) {
UNUserNotificationCenter.current?.getNotificationSettings { (settings) in
DispatchQueue.main.async {
completion(settings.authorizationStatus == .authorized)
}
}
}
@objc public static func openAppSettings() {
guard let appSettings = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(appSettings)
}
}
extension UNUserNotificationCenter {
/// XCTest & UNUserNotificationCenter: requires bundle identifier, crashes when accessed
/// http://www.openradar.me/27768556
static var current: UNUserNotificationCenter? {
// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
return nil
} else { } else {
return .current() let notificationSettings = UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(notificationSettings)
} }
} }
@objc open static func canReceivePushNotifications() -> Bool {
var isEnabled = false
if let settings = UIApplication.shared.currentUserNotificationSettings {
isEnabled = settings.types != UIUserNotificationType()
}
return isEnabled
// Making this function async to satisfy the iOS 10 way is extremely difficult due to how OTRSettingsManager.populateSettings works
/*
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.currentNotificationCenter()
center.getNotificationSettingsWithCompletionHandler({ (settings: UNNotificationSettings) in
let isEnabled = settings.authorizationStatus != .Authorized
dispatch_async(dispatch_get_main_queue(), {
completion(canReceive: isEnabled)
})
})
} else {
var isEnabled = false
if let settings = UIApplication.sharedApplication().currentUserNotificationSettings() {
isEnabled = settings.types != .None
}
dispatch_async(dispatch_get_main_queue(), {
completion(canReceive: isEnabled)
})
}
*/
}
} }

View File

@ -0,0 +1,90 @@
//
// PushOTRListener.swift
// ChatSecure
//
// Created by David Chiles on 9/29/15.
// Copyright © 2015 Chris Ballinger. All rights reserved.
//
import Foundation
import ChatSecure_Push_iOS
/**
* Listen for changes from EncryptionManager for changes in state and when detetced going encrypted
* ensures push token is transfered
*/
class PushOTRListener: NSObject {
let queue = OperationQueue()
var notification:NSObjectProtocol?
weak var storage:PushStorageProtocol?
weak var pushController:PushController?
weak var tlvHandler:OTRPushTLVHandlerProtocol?
init (storage:PushStorageProtocol?, pushController:PushController?, tlvHandler:OTRPushTLVHandlerProtocol?) {
self.storage = storage
self.pushController = pushController
self.tlvHandler = tlvHandler
super.init()
self.startObserving()
}
func startObserving() {
self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.OTRMessageStateDidChange, object: nil, queue: self.queue) {[weak self] (notification) -> Void in
self?.handleNotification(notification)
}
}
func handleNotification(_ notification:Notification) {
guard let buddy = notification.object as? OTRBuddy else {
return
}
if let dictionary = notification.userInfo as? [String:AnyObject] {
let number = dictionary[OTRMessageStateKey] as? NSNumber
if let enumValue = number?.uintValue, enumValue == OTREncryptionMessageState.encrypted.rawValue {
if let account = self.storage?.account(buddy.accountUniqueId) {
//Everytime we're starting a new OTR Session we resend a new fresh push token either from the server or the cache
self.pushController?.getNewPushToken(buddy.uniqueId, completion: {[weak self] (t, error) -> Void in
if let token = t, let pushToken = token.pushToken {
do {
try self?.sendOffToken(pushToken, buddyUsername: buddy.username, accountUsername: account.username, protocol: account.protocolTypeString())
} catch let error as NSError {
if (error.code == PushError.misingExpiresDate.rawValue) {
self?.pushController?.storage.removeToken(token)
//Somehow we got a token without a expires date. We need to clear the database of these tokens and try again
guard let timeBuffer = self?.pushController?.timeBufffer else {
return
}
self?.pushController?.storage.removeAllOurExpiredUnusedTokens(timeBuffer, completion: { (count) in
//try again
self?.handleNotification(notification)
})
}
}
}
})
}
}
}
}
func sendOffToken(_ token:Token, buddyUsername:String, accountUsername:String, protocol:String) throws -> Void {
if let url = self.pushController?.apiClient.messageEndpont().absoluteString {
if let data = try PushSerializer.serialize([token], APIEndpoint: url) {
self.tlvHandler?.sendPush(data, username: buddyUsername, accountName:accountUsername , protocol: `protocol`)
}
}
}
deinit {
if let token = self.notification {
NotificationCenter.default.removeObserver(token)
}
}
}

View File

@ -10,7 +10,7 @@ import Foundation
import ChatSecure_Push_iOS import ChatSecure_Push_iOS
import YapDatabase import YapDatabase
@objc public protocol PushStorageProtocol: AnyObject { @objc public protocol PushStorageProtocol: class {
func thisDevicePushAccount() -> Account? func thisDevicePushAccount() -> Account?
func hasPushAccount() -> Bool func hasPushAccount() -> Bool
func saveThisAccount(_ account:Account) func saveThisAccount(_ account:Account)
@ -132,9 +132,11 @@ class PushStorage: NSObject, PushStorageProtocol {
func unusedToken() -> TokenContainer? { func unusedToken() -> TokenContainer? {
var tokenContainer:TokenContainer? = nil var tokenContainer:TokenContainer? = nil
self.databaseConnection.read { (transaction) -> Void in self.databaseConnection.read { (transaction) -> Void in
transaction.iterateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, tc: TokenContainer, stop) -> Void in transaction.enumerateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, object, stop) -> Void in
tokenContainer = tc if let tc = object as? TokenContainer {
stop = true tokenContainer = tc
}
stop.initialize(to: true)
}) })
} }
return tokenContainer return tokenContainer
@ -234,16 +236,18 @@ class PushStorage: NSObject, PushStorageProtocol {
self.databaseConnection.asyncReadWrite({ (transaction) in self.databaseConnection.asyncReadWrite({ (transaction) in
let collection = PushYapCollections.unusedTokenCollection.rawValue let collection = PushYapCollections.unusedTokenCollection.rawValue
var removeKeyArray:[String] = [] var removeKeyArray:[String] = []
transaction.iterateKeysAndObjects(inCollection: collection, using: { (key, token: TokenContainer, stop) in transaction.enumerateKeysAndObjects(inCollection: collection, using: { (key, object, stop) in
//Check that there is an expires date otherwise remove if let token = object as? TokenContainer {
guard let expiresDate = token.pushToken?.expires else { //Check that there is an expires date otherwise remove
removeKeyArray.append(token.uniqueId) guard let expiresDate = token.pushToken?.expires else {
return removeKeyArray.append(token.uniqueId)
} return
}
// Check that the date is farther in the future than currentDate + timeBuffer // Check that the date is farther in the future than currentDate + timeBuffer
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) { if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
removeKeyArray.append(token.uniqueId) removeKeyArray.append(token.uniqueId)
}
} }
}) })

View File

@ -177,7 +177,9 @@ extension ServerCheck: XMPPPushDelegate {
public func pushModule(_ module: XMPPPushModule, readyWithCapabilities caps: XMLElement, jid: XMPPJID) { public func pushModule(_ module: XMPPPushModule, readyWithCapabilities caps: XMLElement, jid: XMPPJID) {
// This _should_ be handled elsewhere in OTRServerCapabilities // This _should_ be handled elsewhere in OTRServerCapabilities
// Not sure why it's not working properly // Not sure why it's not working properly
result.capabilities?[.XEP0357]?.status = .Available if var caps = result.capabilities {
caps[.XEP0357]?.status = .Available
}
checkReady() checkReady()
} }
} }

View File

@ -22,11 +22,11 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
return self.url! return self.url!
} }
public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType?) -> Any? {
return self.url return self.url
} }
open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
var name = SOMEONE_STRING() var name = SOMEONE_STRING()
if let displayName = account?.username { if let displayName = account?.username {
name = displayName name = displayName
@ -38,7 +38,7 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
} }
open class ShareController: NSObject { open class ShareController: NSObject {
@objc public static func shareAccount(_ account: OTRAccount, sender: Any, viewController: UIViewController) { @objc open static func shareAccount(_ account: OTRAccount, sender: Any, viewController: UIViewController) {
let fingerprintTypes = Set([NSNumber(value: OTRFingerprintType.OTR.rawValue as Int32)]) let fingerprintTypes = Set([NSNumber(value: OTRFingerprintType.OTR.rawValue as Int32)])
account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in
@ -48,7 +48,7 @@ open class ShareController: NSObject {
let qrCodeActivity = OTRQRCodeActivity() let qrCodeActivity = OTRQRCodeActivity()
let activityViewController = UIActivityViewController(activityItems: [self.getShareSource(account, url: url)], applicationActivities: [qrCodeActivity]) let activityViewController = UIActivityViewController(activityItems: [self.getShareSource(account, url: url)], applicationActivities: [qrCodeActivity])
activityViewController.excludedActivityTypes = [UIActivity.ActivityType.print, UIActivity.ActivityType.saveToCameraRoll, UIActivity.ActivityType.addToReadingList] activityViewController.excludedActivityTypes = [UIActivityType.print, UIActivityType.saveToCameraRoll, UIActivityType.addToReadingList]
if let ppc = activityViewController.popoverPresentationController { if let ppc = activityViewController.popoverPresentationController {
if let view = sender as? UIView { if let view = sender as? UIView {
ppc.sourceView = view ppc.sourceView = view
@ -61,7 +61,7 @@ open class ShareController: NSObject {
} }
@objc public static func getShareSource(_ account: OTRAccount, url: URL) -> AnyObject { @objc open static func getShareSource(_ account: OTRAccount, url: URL) -> AnyObject {
return ShareControllerURLSource(account: account, url: url) return ShareControllerURLSource(account: account, url: url)
} }
} }

View File

@ -49,8 +49,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
if ([super activate:aXmppStream]) if ([super activate:aXmppStream])
{ {
[self performBlock:^{ [self performBlock:^{
[self.capabilities addDelegate:self delegateQueue:self->moduleQueue]; [self.capabilities addDelegate:self delegateQueue:moduleQueue];
self->_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:self->moduleQueue]; _tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:moduleQueue];
}]; }];
return YES; return YES;
} }
@ -60,8 +60,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (void) deactivate { - (void) deactivate {
[self performBlock:^{ [self performBlock:^{
[self->_tracker removeAllIDs]; [_tracker removeAllIDs];
self->_tracker = nil; _tracker = nil;
[self.capabilities removeDelegate:self]; [self.capabilities removeDelegate:self];
self.discoveredServices = nil; self.discoveredServices = nil;
}]; }];
@ -74,7 +74,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (BOOL) autoDiscoverServices { - (BOOL) autoDiscoverServices {
__block BOOL discover = NO; __block BOOL discover = NO;
[self performBlock:^{ [self performBlock:^{
discover = self->_autoDiscoverServices; discover = _autoDiscoverServices;
}]; }];
return discover; return discover;
} }
@ -82,7 +82,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (void) setAutoDiscoverServices:(BOOL)autoDiscoverServices { - (void) setAutoDiscoverServices:(BOOL)autoDiscoverServices {
[self performBlockAsync:^{ [self performBlockAsync:^{
[self willChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))]; [self willChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
self->_autoDiscoverServices = autoDiscoverServices; _autoDiscoverServices = autoDiscoverServices;
[self didChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))]; [self didChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
}]; }];
} }
@ -90,7 +90,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (void) setDiscoveredServices:(NSArray<NSXMLElement *> * _Nullable)discoveredServices { - (void) setDiscoveredServices:(NSArray<NSXMLElement *> * _Nullable)discoveredServices {
[self performBlockAsync:^{ [self performBlockAsync:^{
[self willChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))]; [self willChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
self->_discoveredServices = [discoveredServices copy]; _discoveredServices = [discoveredServices copy];
[self didChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))]; [self didChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
}]; }];
} }
@ -98,7 +98,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (nullable NSArray<NSXMLElement *>*) discoveredServices { - (nullable NSArray<NSXMLElement *>*) discoveredServices {
__block NSArray<NSXMLElement *> *services = nil; __block NSArray<NSXMLElement *> *services = nil;
[self performBlock:^{ [self performBlock:^{
services = self->_discoveredServices; services = _discoveredServices;
}]; }];
return services; return services;
} }
@ -106,7 +106,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (void) setAllCapabilities:(NSDictionary<XMPPJID *,NSXMLElement *> * _Nullable)allCapabilities { - (void) setAllCapabilities:(NSDictionary<XMPPJID *,NSXMLElement *> * _Nullable)allCapabilities {
[self performBlockAsync:^{ [self performBlockAsync:^{
[self willChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))]; [self willChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
self->_allCapabilities = [allCapabilities copy]; _allCapabilities = [allCapabilities copy];
[self didChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))]; [self didChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
}]; }];
} }
@ -114,7 +114,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (nullable NSDictionary<XMPPJID*, NSXMLElement *> *) allCapabilities { - (nullable NSDictionary<XMPPJID*, NSXMLElement *> *) allCapabilities {
__block NSDictionary<XMPPJID*, NSXMLElement *> *caps = nil; __block NSDictionary<XMPPJID*, NSXMLElement *> *caps = nil;
[self performBlock:^{ [self performBlock:^{
caps = self->_allCapabilities; caps = _allCapabilities;
}]; }];
return caps; return caps;
} }
@ -122,8 +122,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (NSXMLElement*) streamFeatures { - (NSXMLElement*) streamFeatures {
__block NSXMLElement *features = nil; __block NSXMLElement *features = nil;
[self performBlock:^{ [self performBlock:^{
if (self->xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) { if (xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) {
features = [[self->xmppStream.rootElement elementForName:@"stream:features"] copy]; features = [[xmppStream.rootElement elementForName:@"stream:features"] copy];
} }
}]; }];
return features; return features;
@ -149,21 +149,21 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
{ {
// This is a public method, so it may be invoked on any thread/queue. // This is a public method, so it may be invoked on any thread/queue.
[self performBlockAsync:^{ [self performBlockAsync:^{
if (self->_hasRequestedServices) return; // We've already requested services if (_hasRequestedServices) return; // We've already requested services
if (self->_discoveredServices) { // We've already discovered the services if (_discoveredServices) { // We've already discovered the services
[self->multicastDelegate serverCapabilities:self didDiscoverServices:self->_discoveredServices]; [multicastDelegate serverCapabilities:self didDiscoverServices:_discoveredServices];
return; return;
} }
NSString *toStr = self->xmppStream.myJID.domain; NSString *toStr = xmppStream.myJID.domain;
NSXMLElement *query = [NSXMLElement elementWithName:@"query" NSXMLElement *query = [NSXMLElement elementWithName:@"query"
xmlns:XMPPDiscoverItemsNamespace]; xmlns:XMPPDiscoverItemsNamespace];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
to:[XMPPJID jidWithString:toStr] to:[XMPPJID jidWithString:toStr]
elementID:[self->xmppStream generateUUID] elementID:[xmppStream generateUUID]
child:query]; child:query];
if (!iq) { if (!iq) {
XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", self->xmppStream); XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", xmppStream);
return; return;
} }
XMPPLogInfo(@"OTRServerCapabilities: Discovering services for domain %@...", toStr); XMPPLogInfo(@"OTRServerCapabilities: Discovering services for domain %@...", toStr);
@ -173,8 +173,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
selector:@selector(handleDiscoverServicesQueryIQ:withInfo:) selector:@selector(handleDiscoverServicesQueryIQ:withInfo:)
timeout:15]; timeout:15];
[self->xmppStream sendElement:iq]; [xmppStream sendElement:iq];
self->_hasRequestedServices = YES; _hasRequestedServices = YES;
}]; }];
} }
@ -184,11 +184,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
*/ */
- (void)fetchAllCapabilities { - (void)fetchAllCapabilities {
[self performBlockAsync:^{ [self performBlockAsync:^{
if (self->xmppStream.state != STATE_XMPP_CONNECTED) { if (xmppStream.state != STATE_XMPP_CONNECTED) {
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not connected. %@", self); XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not connected. %@", self);
return; return;
} }
if (![self->xmppStream isAuthenticated]) { if (![xmppStream isAuthenticated]) {
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not authenticated. %@", self); XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not authenticated. %@", self);
return; return;
} }
@ -209,11 +209,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
// [self.allJIDs addObject:myJID]; // [self.allJIDs addObject:myJID];
[self.allJIDs addObject:myJID.domainJID]; [self.allJIDs addObject:myJID.domainJID];
} }
if (!self->_autoDiscoverServices) { if (!_autoDiscoverServices) {
return; return;
} }
[self discoverServices]; [self discoverServices];
if (!self->_autoFetchAllCapabilities) { if (!_autoFetchAllCapabilities) {
return; return;
} }
[self fetchCapabilitiesForJIDs:self.allJIDs]; [self fetchCapabilitiesForJIDs:self.allJIDs];
@ -241,7 +241,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
- (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info - (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
{ {
[self performBlockAsync:^{ [self performBlockAsync:^{
self->_hasRequestedServices = NO; // Set this back to NO to allow for future requests _hasRequestedServices = NO; // Set this back to NO to allow for future requests
NSError *error = nil; NSError *error = nil;
if (!iq) { if (!iq) {
NSDictionary *dict = @{NSLocalizedDescriptionKey : @"The request timed out.", NSDictionary *dict = @{NSLocalizedDescriptionKey : @"The request timed out.",
@ -262,15 +262,15 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
} }
} }
if (error) { if (error) {
XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", self->xmppStream.myJID.domain, error); XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", xmppStream.myJID.domain, error);
[self->multicastDelegate serverCapabilitiesFailedToDiscoverServices:self [multicastDelegate serverCapabilitiesFailedToDiscoverServices:self
withError:error]; withError:error];
// Deal with the race condition where we've already fetched your JID and server caps // Deal with the race condition where we've already fetched your JID and server caps
// but for whatever reason fetching services fails. // but for whatever reason fetching services fails.
NSSet *allCaps = [NSSet setWithArray:self.allCapabilities.allKeys]; NSSet *allCaps = [NSSet setWithArray:self.allCapabilities.allKeys];
if ([self.allJIDs isEqualToSet:allCaps]) { if ([self.allJIDs isEqualToSet:allCaps]) {
[self->multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities]; [multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
} }
return; return;
} }
@ -279,19 +279,16 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
xmlns:XMPPDiscoverItemsNamespace]; xmlns:XMPPDiscoverItemsNamespace];
NSArray<NSXMLElement*> *items = [query elementsForName:@"item"]; NSArray<NSXMLElement*> *items = [query elementsForName:@"item"];
if (!items) {
items = @[];
}
self.discoveredServices = [items copy]; self.discoveredServices = [items copy];
[self->multicastDelegate serverCapabilities:self didDiscoverServices:items]; [multicastDelegate serverCapabilities:self didDiscoverServices:items];
XMPPLogInfo(@"OTRServerCapabilities: Discovered services for domain %@:\n%@", self->xmppStream.myJID.domain, items); XMPPLogInfo(@"OTRServerCapabilities: Discovered services for domain %@:\n%@", xmppStream.myJID.domain, items);
// Recursively fetch service capabilities if needed // Recursively fetch service capabilities if needed
if (!self->_autoFetchAllCapabilities) { if (!_autoFetchAllCapabilities) {
return; return;
} }
NSSet<XMPPJID*> *jids = [self jidsFromItems:self->_discoveredServices]; NSSet<XMPPJID*> *jids = [self jidsFromItems:_discoveredServices];
[self.allJIDs unionSet:jids]; [self.allJIDs unionSet:jids];
[self fetchCapabilitiesForJIDs:jids]; [self fetchCapabilitiesForJIDs:jids];
}]; }];
@ -343,7 +340,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
} }
[newCaps setObject:caps forKey:jid]; [newCaps setObject:caps forKey:jid];
self.allCapabilities = newCaps; self.allCapabilities = newCaps;
[self->multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities]; [multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
}]; }];
} }
@ -388,9 +385,9 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
return; return;
} }
id <XMPPCapabilitiesStorage> storage = self.capabilities.xmppCapabilitiesStorage; id <XMPPCapabilitiesStorage> storage = self.capabilities.xmppCapabilitiesStorage;
BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:self->xmppStream]; BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:xmppStream];
if (fetched) { if (fetched) {
NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:self->xmppStream]; NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:xmppStream];
if (capabilities) { if (capabilities) {
[newCaps setObject:capabilities forKey:jid]; [newCaps setObject:capabilities forKey:jid];
*stop = YES; *stop = YES;

Some files were not shown because too many files have changed in this diff Show More