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
build
*.mode1v3
@ -15,7 +14,3 @@ Carthage/
com.mono0926.LicensePlist.Output/
Secrets.plist
Preview.html
fastlane/report.xml
fastlane/test_output/
.ruby-version

9
.gitmodules vendored
View File

@ -31,12 +31,3 @@
[submodule "Submodules/LumberjackConsole"]
path = Submodules/LumberjackConsole
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
# Handle git submodules yourself
@ -9,8 +9,9 @@ git:
# Use sed to replace the SSH URL with the public URL, then initialize submodules
before_install:
# 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
- open -a "simulator" --args -CurrentDeviceUDID $IOS_SIMULATOR_UDID
- 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\//' 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
- unzip -q ChatSecure-iOS-Precompiled-Dependencies.zip
- 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/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
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"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -37,20 +37,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "iOS_Debug"
buildConfiguration = "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>
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@ -63,7 +53,17 @@
</BuildableReference>
</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
BuildableIdentifier = "primary"
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
@ -73,9 +73,20 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
BuildableName = "ChatSecure.app"
BlueprintName = "ChatSecure"
ReferencedContainer = "container:ChatSecure.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "iOS_Debug"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
@ -98,12 +109,14 @@
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "NO">
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "iOS_Release"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
@ -120,10 +133,10 @@
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "iOS_Debug">
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "iOS_Release"
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

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

View File

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

View File

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

View File

@ -8,17 +8,7 @@
<array>
<string>applinks:chatsecure.org</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<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/>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
</dict>
</plist>

View File

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

View File

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

View File

@ -10,7 +10,6 @@
#import "OTRConstants.h"
@import XMPPFramework;
@import OTRAssets;
#import "ChatSecureCoreCompat-Swift.h"
@implementation NSURL (ChatSecure)
@ -198,7 +197,7 @@
view = sender;
}
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];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet];
@ -212,4 +211,12 @@
[viewController presentViewController:alert animated:YES completion:nil];
}
@end
@implementation UIViewController (ChatSecureURL)
- (void) promptToShowURL:(NSURL*)url sender:(id)sender {
[url promptToShowURLFromViewController:self sender:sender];
}
@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 ARChromeActivity;
@import TUSafariActivity;
#import "OTROpenInFacebookActivity.h"
#import "OTROpenInTwitterActivity.h"
@import OTRAssets;
@ -15,21 +16,22 @@
@implementation UIActivity (ChatSecure)
+ (NSArray<UIActivity*>*) otr_linkActivities {
TUSafariActivity *safariActivity = [TUSafariActivity new];
ARChromeActivity *chromeActivity = [ARChromeActivity new];
chromeActivity.activityTitle = OPEN_IN_CHROME();
chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"];
OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new];
OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new];
NSArray *applicationActivites = @[twitterActivity,facebookActivity,chromeActivity];
NSArray *applicationActivites = @[twitterActivity,facebookActivity,safariActivity,chromeActivity];
return applicationActivites;
}
+ (CGSize)otr_defaultImageSize
{
CGSize size = CGSizeZero;
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
size = CGSizeMake(43, 43);
} else if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
size = CGSizeMake(55, 55);
}
return size;

View File

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

View File

@ -49,7 +49,7 @@ extension NotificationType: RawRepresentable {
public typealias RawValue = String
}
extension UIApplication {
public extension UIApplication {
/// Removes all but one foreground notifications for typing and message events sent from APNS
@objc public func removeExtraForegroundNotifications() {
@ -138,7 +138,7 @@ extension UIApplication {
let chatString = WANTS_TO_CHAT_STRING()
let text = "\(name) \(chatString)"
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,
kOTRNotificationThreadCollection:thread.threadCollection,
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) {
if let thread = thread, thread.isMuted { return } // No notifications for muted
DispatchQueue.main.async {
var identifier:String? = nil
var userInfo:[AnyHashable:Any]? = nil
if let t = thread {
identifier = t.threadIdentifier
userInfo = [kOTRNotificationThreadKey:t.threadIdentifier,
kOTRNotificationThreadCollection:t.threadCollection,
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 {
if recurring, self.hasRecurringLocalNotificationWith(identifier: groupingIdentifier) {
if recurring, self.hasRecurringLocalNotificationWith(identifier: identifier) {
return // Already pending
}
// Use the new UserNotifications.framework on iOS 10+
@ -185,9 +187,9 @@ extension UIApplication {
let localNotification = UNMutableNotificationContent()
localNotification.body = body
localNotification.badge = NSNumber(integerLiteral: badge)
localNotification.sound = UNNotificationSound.default
if let threadKey = userInfo?[kOTRNotificationThreadKey] as? String {
localNotification.threadIdentifier = threadKey
localNotification.sound = UNNotificationSound.default()
if let identifier = identifier {
localNotification.threadIdentifier = identifier
}
if let userInfo = userInfo {
localNotification.userInfo = userInfo
@ -199,7 +201,7 @@ extension UIApplication {
date.minute = 0
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()
center.add(request, withCompletionHandler: { (error: Error?) in
if let error = error as NSError? {
@ -280,9 +282,9 @@ extension UIApplication {
let username = account.username
var body = "\(CONNECTION_ERROR_STRING()) \(username)."
if error.domain == GCDAsyncSocketErrorDomain,
let code = GCDAsyncSocketError.Code.init(rawValue: error.code) {
let code = GCDAsyncSocketError(rawValue: error.code) {
switch code {
case .noError,
.connectTimeoutError,
@ -296,8 +298,6 @@ extension UIApplication {
case .otherError:
// this is probably a SSL error
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
@unknown default:
return
}
} else if error.domain == "kCFStreamErrorDomainSSL" {
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
@ -325,12 +325,19 @@ extension UIApplication {
let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError,
kOTRNotificationAccountKey: accountKey]
self.showLocalNotificationWith(groupingIdentifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
}
}
extension UIApplication {
@objc public func open(_ url: URL) {
open(url, options: [:], completionHandler: nil)
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
for notification in notifications {
if notification.request.identifier == accountKey {
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
extension UINavigationController {
public extension UINavigationController {
@objc public func otr_baseViewContorllers() -> [UIViewController] {
var result:[UIViewController] = []

View File

@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface UITableView (ChatSecure)
/** 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
NS_ASSUME_NONNULL_END

View File

@ -8,14 +8,14 @@
#import "UITableView+ChatSecure.h"
#import "OTRXMPPBuddy.h"
#import "ChatSecureCoreCompat-Swift.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRXMPPManager_Private.h"
@import OTRAssets;
@implementation UITableView (ChatSecure)
/** 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(connection);
if (!thread || !connection) {
@ -33,23 +33,22 @@
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) {
NSString *key = [thread threadIdentifier];
NSString *collection = [thread threadCollection];
id object = [transaction objectForKey:key inCollection:collection];
if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) {
completionHandler(NO);
return;
}
id <OTRThreadOwner> thread = object;
thread.isArchived = !thread.isArchived;
[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) {
[OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction];
}];
@ -62,7 +61,7 @@
[connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction];
}];
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account];
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[OTRProtocolManager.shared protocolForAccount:account];
if (room.roomJID) {
[xmppManager.roomManager leaveRoom:room.roomJID];
}
@ -71,7 +70,6 @@
//Delete database items
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[((OTRXMPPRoom *)thread) removeWithTransaction:transaction];
completionHandler(YES);
}];
} else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) {
OTRBuddy *dbBuddy = (OTRBuddy*)thread;
@ -82,14 +80,11 @@
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[action saveWithTransaction:transaction];
[dbBuddy removeWithTransaction:transaction];
completionHandler(YES);
}];
} else {
completionHandler(NO);
}
}];
return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction, archiveAction]];
return @[deleteAction, archiveAction];
}
@end

View File

@ -9,38 +9,26 @@
import UIKit
import OTRAssets
extension UIViewController {
public func prompt(toShow url: URL, sender: Any) {
(url as NSURL).promptToShow(from: self, sender: sender)
}
public extension UIViewController {
/// 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 settingsAction = UIAlertAction(title: SETTINGS_STRING(), style: .default, handler: { (action: UIAlertAction) -> Void in
let appSettings = URL(string: UIApplication.openSettingsURLString)
UIApplication.shared.open(appSettings!)
let appSettings = URL(string: UIApplicationOpenSettingsURLString)
UIApplication.shared.openURL(appSettings!)
})
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
alert.addAction(settingsAction)
alert.addAction(cancelAction)
if let sourceView = sender as? UIView {
alert.popoverPresentationController?.sourceView = sourceView;
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
}
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 destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler)
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
alert.addAction(destroyAction)
alert.addAction(cancelAction)
if let sourceView = sender as? UIView {
alert.popoverPresentationController?.sourceView = sourceView;
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
}
present(alert, animated: true, completion: nil)
}
}

View File

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

View File

@ -56,7 +56,7 @@ extension UIImage {
numTries = numTries + 1
newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize)
scaledImageData = scaledImage.jpegData(compressionQuality: jpegQuality)
scaledImageData = UIImageJPEGRepresentation(scaledImage, jpegQuality)
if let imageData = scaledImageData {
sizeInBytes = UInt(imageData.count)
scaleFactor = scaleFactor * scaleDecrement
@ -123,7 +123,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
let connection: YapDatabaseConnection
let internalQueue = DispatchQueue(label: "FileTransferManager Queue")
let callbackQueue = DispatchQueue.main
let sessionManager: Session
let sessionManager: SessionManager
private var servers: [HTTPServer] = []
@objc public var canUploadFiles: Bool {
@ -141,7 +141,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
self.serverCapabilities = serverCapabilities
self.httpFileUpload = XMPPHTTPFileUpload()
self.connection = connection
self.sessionManager = Alamofire.Session(configuration: sessionConfiguration)
self.sessionManager = Alamofire.SessionManager(configuration: sessionConfiguration)
super.init()
if let stream = serverCapabilities.xmppStream {
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
@objc public func resumeDownloads() {
/// https://github.com/ChatSecure/ChatSecure-iOS/issues/1034
DDLogWarn("WARN: Download resumption is disabled. See https://github.com/ChatSecure/ChatSecure-iOS/issues/1034 for more information.")
// connection.asyncRead { [weak self] (transaction) in
// let unfinished = transaction.unfinishedDownloads()
// self?.internalQueue.async {
// for mediaItem in unfinished {
// if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage,
// downloadMessage.messageError == nil {
// self?.downloadMedia(downloadMessage)
// }
// }
// }
// }
connection.asyncRead { (transaction) in
transaction.enumerateUnfinishedDownloads({ (mediaItem, stop) in
if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage, downloadMessage.messageError == nil {
self.internalQueue.async {
self.downloadMedia(downloadMessage)
}
}
})
}
}
/// This will fetch capabilities and setup XMPP transfer module if needed
@ -260,7 +256,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
var outData = data
var outKeyIv: Data? = nil
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")
self.callbackQueue.async {
completion(nil, FileTransferError.keyGenerationError)
@ -269,7 +265,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
}
outKeyIv = iv + key
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
} catch let error {
outData = Data()
@ -291,23 +287,9 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
}
return
}
// 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)
self.sessionManager.upload(outData, to: slot.putURL, method: .put)
.validate()
.response(queue: self.callbackQueue) { response in
.responseData(queue: self.callbackQueue) { response in
switch response.result {
case .success:
if let outKeyIv = outKeyIv {
@ -378,9 +360,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
}
mediaItem.parentObjectKey = message.messageKey
mediaItem.parentObjectCollection = message.messageCollection
guard let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier) else {
return
}
let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier)
self.connection.readWrite { transaction in
message.save(with: transaction)
mediaItem.save(with: transaction)
@ -494,8 +474,6 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
shouldEncrypt = true
case .invalid, .plaintext, .plaintextWithOTR:
shouldEncrypt = false
@unknown default:
fatalError("Unhandled message security value!")
}
self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in
@ -679,12 +657,7 @@ extension FileTransferManager {
// Remove placeholder media item
mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction)
mediaItem?.remove(with: transaction)
// If the file is encrypted, the server might not know its type
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 = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
mediaItem?.parentObjectKey = downloadMessage.uniqueId
mediaItem?.parentObjectCollection = downloadMessage.messageCollection
mediaItem?.save(with: transaction)
@ -813,19 +786,19 @@ extension OTRDownloadMessage {
}
}
extension OTRMessageProtocol {
public extension OTRMessageProtocol {
public var downloadableURLs: [URL] {
return self.messageText?.downloadableURLs ?? []
}
}
extension OTRBaseMessage {
public extension OTRBaseMessage {
@objc public var downloadableNSURLs: [NSURL] {
return self.downloadableURLs as [NSURL]
}
}
extension OTRXMPPRoomMessage {
public extension OTRXMPPRoomMessage {
@objc public var downloadableNSURLs: [NSURL] {
return self.downloadableURLs as [NSURL]
}
@ -904,31 +877,20 @@ extension URL {
}
var aesGcmKey: (key: Data, iv: Data)? {
guard let data = self.anchorData else { return nil }
let ivLength: Int
switch data.count {
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)
guard let data = self.anchorData, data.count == 48 else { return nil }
let iv = data.subdata(in: 0..<16)
let key = data.subdata(in: 16..<48)
return (key, iv)
}
}
extension NSString {
public extension NSString {
public var isSingleURLOnly: Bool {
return (self as String).isSingleURLOnly
}
}
extension String {
public extension String {
private var urlRanges: ([URL], [NSRange]) {
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.
@objc public static func shouldDisplayMessage(_ message: OTRMessageProtocol, transaction: YapDatabaseReadTransaction) -> Bool {
// Always show media messages

View File

@ -22,8 +22,11 @@ private class OutstandingActionInfo: Hashable, Equatable {
self.completion = completion
}
func hash(into hasher: inout Hasher) {
hasher.combine(action.yapKey())
/// Needed so we can store the struct in a dictionary
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
extension OutstandingMessageInfo: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(self.messageKey)
hasher.combine(self.messageCollection)
var hashValue: Int {
get {
return "\(self.messageKey)\(self.messageCollection)".hashValue
}
}
}
@ -213,7 +217,7 @@ public class MessageQueueHandler:NSObject {
switch message.messageSecurity {
case .plaintext:
self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion)
OTRProtocolManager.sharedInstance().send(message)
OTRProtocolManager.shared.send(message)
break
case .plaintextWithOTR:
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:
fatalError("Invalid message security. This should never happen... so let's crash!")
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)
case .OMEMO:
sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion)
@unknown default:
fatalError("Invalid group message security. This should never happen.")
}
databaseConnection.readWrite { transaction in
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
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)
return
}
@ -350,7 +350,7 @@ public class MessageQueueHandler:NSObject {
}
//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)
return
}
@ -385,7 +385,7 @@ public class MessageQueueHandler:NSObject {
}
//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)
return
}

View File

@ -11,6 +11,7 @@
@import MobileCoreServices;
@import OTRAssets;
#import "OTRUtilities.h"
#import "UIActionSheet+ChatSecure.h"
@interface OTRAttachmentPicker () <UINavigationControllerDelegate>
@ -110,7 +111,7 @@
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];
}

View File

@ -16,7 +16,7 @@
@property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem;
@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;

View File

@ -44,19 +44,18 @@
[self.currentAudioControlsView setTime:currentTime];
}
- (BOOL)playURL:(NSURL *)url error:(NSError **)error;
- (void)playURL:(NSURL *)url error:(NSError **)error;
{
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
self.duration = CMTimeGetSeconds(asset.duration);
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
error = nil;
BOOL result = [self.audioSessionManager playAudioWithURL:url error:error];
[self.audioSessionManager playAudioWithURL:url error:error];
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
[self.currentAudioControlsView setTime:0];
[self startLabelTimer];
return result;
}
- (void)startLabelTimer
@ -94,13 +93,13 @@
#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];
_currentAudioItem = audioItem;
return [self playURL:audioURL error:error];
[self playURL:audioURL error:error];
}
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView

View File

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

View File

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

View File

@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
directory:(nullable NSString*)directory
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;

View File

@ -15,6 +15,7 @@
#import "OTRConstants.h"
#import "OTRXMPPAccount.h"
#import "OTRXMPPTorAccount.h"
#import "OTRGoogleOAuthXMPPAccount.h"
#import "OTRAccount.h"
#import "OTRIncomingMessage.h"
#import "OTROutgoingMessage.h"
@ -28,7 +29,7 @@
#import "OTRSignalSession.h"
#import "OTRSettingsManager.h"
#import "OTRXMPPPresenceSubscriptionRequest.h"
#import "ChatSecureCoreCompat-Swift.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
@interface OTRDatabaseManager ()
@ -123,7 +124,6 @@
}
return keyData;
};
options.cipherCompatability = YapDatabaseCipherCompatability_Version3;
_databaseDirectory = [directory copy];
if (!_databaseDirectory) {
_databaseDirectory = [[self class] defaultYapDatabaseDirectory];
@ -134,14 +134,17 @@
}
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.
if (self.database == nil) {
return NO;
}
self.database.connectionDefaults.objectCacheLimit = 10000;
self.database.defaultObjectPolicy = YapDatabasePolicyShare;
self.database.defaultObjectCacheLimit = 10000;
[self setupConnections];
@ -193,7 +196,7 @@
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
@ -296,17 +299,15 @@
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) {
self.inMemoryPassphrase = nil;
result = [SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
[SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
} else {
[SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName];
self.inMemoryPassphrase = passphrase;
}
return result;
}
- (BOOL)hasPassphrase

View File

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

View File

@ -21,7 +21,9 @@
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
@import Foundation;
@class OTRKit, OTRDataHandler, OTRFingerprint;
@import OTRKit;
@class OTRPushTLVHandler;
extern NSString * _Nonnull const OTRMessageStateDidChangeNotification;
extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification;
@ -41,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, readonly) OTRKit *otrKit;
@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

View File

@ -39,9 +39,10 @@
#import "OTRMediaServer.h"
#import "OTRDatabaseManager.h"
#import "OTRLog.h"
#import "OTRPushTLVHandler.h"
#import "OTRXMPPManager.h"
#import "OTRYapMessageSendAction.h"
#import "ChatSecureCoreCompat-Swift.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
@import AVFoundation;
@import XMPPFramework;
@ -66,6 +67,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
_otrFingerprintCache = [[NSCache alloc] init];
_otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil];
_dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self];
_pushTLVHandler = [[OTRPushTLVHandler alloc] initWithOTRKit:self.otrKit delegate:nil];
_readConnection = OTRDatabaseManager.shared.readConnection;
NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath];
for (NSString *path in protectPaths) {
@ -246,7 +248,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
} completionBlock:^{
if (!buddy) { return; }
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
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
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;
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
@ -43,10 +43,8 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
buddyUniqueId:(NSString *)buddyUniqueId
error:(NSError* __autoreleasing *)error;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
- (void)vacuum:(dispatch_block_t)completion;
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
@property (class, nonatomic, readonly) OTRMediaFileManager *shared;

View File

@ -52,10 +52,7 @@ NSString *const kOTRRootMediaDirectory = @"media";
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
{
_ioCipher = [[IOCipher alloc] initWithPath:path password:password];
if (!_ioCipher) {
return NO;
}
return [_ioCipher setCipherCompatibility:3];
return _ioCipher != nil;
}
- (void)copyDataFromFilePath:(NSString *)filePath
@ -134,7 +131,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
//#865
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
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 {
if (!completionQueue) {
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

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")
public let signalEncryptionManager:OTRAccountSignalEncryptionManager
public let omemoStorageManager:OTROMEMOStorageManager
@objc public let accountYapKey:String
@objc public let databaseConnection:YapDatabaseConnection
open let signalEncryptionManager:OTRAccountSignalEncryptionManager
open let omemoStorageManager:OTROMEMOStorageManager
@objc open let accountYapKey:String
@objc open let databaseConnection:YapDatabaseConnection
@objc open weak var omemoModule:OMEMOModule?
@objc open weak var omemoModuleQueue:DispatchQueue?
@objc open var callbackQueue:DispatchQueue
@objc public let workQueue:DispatchQueue
@objc public let messageStorage: MessageStorage
@objc public let roomManager: OTRXMPPRoomManager
@objc open let workQueue:DispatchQueue
@objc open let messageStorage: MessageStorage
@objc open let roomManager: OTRXMPPRoomManager
private var roomStorage: RoomStorage {
return roomManager.roomStorage
@ -295,7 +295,7 @@ import SignalProtocolObjC
//Strong self work here
var buddies: [OTRXMPPBuddy] = []
self.databaseConnection.read { (transaction) in
buddies = buddyKeys.compactMap({ key in
buddies = buddyKeys.flatMap({ key in
OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction)
})
}
@ -310,7 +310,10 @@ import SignalProtocolObjC
}
do {
//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.
let encryptClosure:(OMEMODevice) -> (OMEMOKeyData?) = { device in
@ -331,7 +334,7 @@ import SignalProtocolObjC
4. Remove optional values
*/
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
@ -351,7 +354,7 @@ import SignalProtocolObjC
*/
let ourDevicesKeyData = self.omemoStorageManager.getDevicesForOurAccount(trustedOnly: true).filter({ (device) -> Bool in
return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId
}).map(encryptClosure).compactMap{ $0 }
}).map(encryptClosure).flatMap{ $0 }
// Combine teh two arrays for all key data
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)
{
groupMessage.addAttribute(withName: "type", stringValue: "groupchat")
groupMessage.addReceiptRequest()
self.omemoModule?.xmppStream?.send(groupMessage)
} else if message is OTROutgoingMessage {
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/>.
@class OTROutgoingMessage, OTRBuddy, OTRAccount;
@protocol PushControllerProtocol;
typedef NS_ENUM(int, OTRProtocolType) {
OTRProtocolTypeNone = 0,

View File

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

View File

@ -26,23 +26,24 @@
#import "OTRIncomingMessage.h"
#import "OTROutgoingMessage.h"
#import "OTRConstants.h"
#import "OTROAuthRefresher.h"
#import "OTROAuthXMPPAccount.h"
#import "OTRDatabaseManager.h"
#import "OTRPushTLVHandler.h"
#import <BBlock/NSObject+BBlock.h>
@import YapDatabase;
@import KVOController;
@import OTRAssets;
#import "OTRLog.h"
#import "ChatSecureCoreCompat-Swift.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRXMPPPresenceSubscriptionRequest.h"
@interface OTRProtocolManager ()
@property (atomic, readwrite) NSUInteger numberOfConnectedProtocols;
@property (atomic, readwrite) NSUInteger numberOfConnectingProtocols;
@property (nonatomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
@interface _OTRProtocolManager ()
//@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
@end
@implementation OTRProtocolManager
@implementation _OTRProtocolManager
-(instancetype)init
{
@ -50,53 +51,36 @@
if(self)
{
_protocolManagers = [[NSMutableDictionary alloc] init];
_numberOfConnectedProtocols = 0;
_numberOfConnectingProtocols = 0;
}
return self;
}
- (NSArray<id<OTRProtocol>> *)allProtocols {
return self.protocolManagers.allValues;
}
- (void)removeProtocolForAccount:(OTRAccount *)account
{
NSParameterAssert(account);
if (!account) { return; }
id<OTRProtocol> protocol = nil;
@synchronized (self) {
protocol = [self.protocolManagers objectForKey:account.uniqueId];
}
protocol = [self.protocolManagers objectForKey:account.uniqueId];
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
[protocol disconnect];
}
[self.KVOController unobserve:protocol];
@synchronized (self) {
[self.protocolManagers removeObjectForKey:account.uniqueId];
}
[self.protocolManagers removeObjectForKey:account.uniqueId];
}
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
{
@synchronized (self) {
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
}
[self.KVOController observe:protocol keyPath:NSStringFromSelector(@selector(loginStatus)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld action:@selector(protocolDidChange:)];
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
}
- (BOOL)existsProtocolForAccount:(OTRAccount *)account
{
NSParameterAssert(account.uniqueId);
if (!account.uniqueId) { return NO; }
@synchronized (self) {
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];
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
}
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
@ -104,14 +88,12 @@
NSParameterAssert(account);
if (!account.uniqueId) { return nil; }
id <OTRProtocol> protocol = nil;
@synchronized (self) {
protocol = [self.protocolManagers objectForKey:account.uniqueId];
if(!protocol)
{
protocol = [[[account protocolClass] alloc] initWithAccount:account];
if (protocol && account.uniqueId) {
[self addProtocol:protocol forAccount:account];
}
protocol = [self.protocolManagers objectForKey:account.uniqueId];
if(!protocol)
{
protocol = [[[account protocolClass] alloc] initWithAccount:account];
if (protocol && account.uniqueId) {
[self addProtocol:protocol forAccount:account];
}
}
return protocol;
@ -133,7 +115,22 @@
if (!account) { return; }
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
@ -149,89 +146,19 @@
}
- (void)goAwayForAllAccounts {
@synchronized (self) {
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
[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];
}
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
[xmpp goAway];
}
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 connected = NO;
id <OTRProtocol> protocol = nil;
@synchronized (self) {
protocol = [self.protocolManagers objectForKey:account.uniqueId];
}
protocol = [self.protocolManagers objectForKey:account.uniqueId];
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
@ -250,7 +177,7 @@
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
} completionBlock:^{
OTRProtocolManager * protocolManager = [OTRProtocolManager sharedInstance];
OTRProtocolManager * protocolManager = [OTRProtocolManager shared];
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
[protocol sendMessage:message];
}];
@ -264,7 +191,7 @@
if (otrFingerprint.length == 40) {
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];
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
@ -289,7 +216,7 @@
title = account.username;
}
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];

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 "OTRCertificateSetting.h"
#import "OTRUtilities.h"
#import "ChatSecureCoreCompat-Swift.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
#import "OTRUtilities.h"
@ -64,11 +64,11 @@
[settingsGroups addObject:accountsGroup];
if (OTRBranding.allowsDonation) {
NSString *donateTitle = nil;
NSString *donateTitle = DONATE_STRING();
if (TransactionObserver.hasValidReceipt) {
donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()];
} else {
donateTitle = [NSString stringWithFormat:@"%@ 🎁", DONATE_STRING()];
donateTitle = [NSString stringWithFormat:@"%@ 🆕", DONATE_STRING()];
}
OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil];
//donateSetting.imageName = @"29-heart.png";
@ -101,18 +101,16 @@
description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING()
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]];
pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]];
[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];
[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*/
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.
*/
open class OTRSignalStorageManager: NSObject {
public let accountKey:String
public let databaseConnection:YapDatabaseConnection
open let accountKey:String
open let databaseConnection:YapDatabaseConnection
open weak var delegate:OTRSignalStorageManagerDelegate?
/**
@ -232,7 +232,7 @@ open class OTRSignalStorageManager: NSObject {
}
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 {
return
}

View File

@ -7,14 +7,12 @@
//
@import Foundation;
@class CPAProxyManager;
@import CPAProxy;
NS_ASSUME_NONNULL_BEGIN
@interface OTRTorManager : NSObject
@property (nonatomic, strong, nullable) CPAProxyManager *torManager;
@property (nonatomic, strong) CPAProxyManager *torManager;
+ (instancetype) sharedInstance;
@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 {
open weak var xmppvCardTempModule:XMPPvCardTempModule?
public let photoData:Data
open let photoData:Data
fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: [])
fileprivate var waitingForVCardFetch:Bool = false

View File

@ -7,7 +7,8 @@
//
import Foundation
import YapDatabase
import YapDatabase.YapDatabaseFullTextSearch
import YapDatabase.YapDatabaseSearchResultsView
open class OTRYapExtensions:NSObject {
@ -17,7 +18,7 @@ open class OTRYapExtensions:NSObject {
let usernameColumnName = BuddyFTSColumnName.username.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 {
return
}

View File

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

View File

@ -10,13 +10,12 @@ import Foundation
import ChatSecure_Push_iOS
import YapDatabase
import UserNotifications
import OTRKit
@objc public protocol PushControllerProtocol {
func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ 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 {
@ -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.
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 write = connections?.write {
let storage = PushStorage(databaseConnection: write)
finishStorageSetup(storage: storage)
_storage = storage
}
return _storage
}
let storage: PushStorageProtocol
var apiClient : Client
var callbackQueue = OperationQueue()
var otrListener: PushOTRListener?
let timeBufffer:TimeInterval = 60*60*24
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.storage = PushStorage(databaseConnection: databaseConnection)
super.init()
}
private func finishStorageSetup(storage: PushStorageProtocol) {
// 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
let readConnection = OTRDatabaseManager.shared.database?.newConnection()
var account: Account? = nil;
connections?.read.asyncRead({ (transaction) in
account = storage.thisDevicePushAccount()
readConnection?.asyncRead({ (transaction) in
account = self.storage.thisDevicePushAccount()
}, completionBlock: {
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?) {
apiClient.unregister { (success, error) in
PushController.setPushPreference(.disabled)
self.storage?.deleteEverything(completion: completion, callbackQueue: callbackQueue)
self.storage.deleteEverything(completion: completion, callbackQueue: callbackQueue)
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
if let newAccount = account {
self?.apiClient.account = newAccount
self?.storage?.saveThisAccount(newAccount)
self?.storage.saveThisAccount(newAccount)
self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil)
})
@ -164,14 +158,14 @@ open class PushController: NSObject, PushControllerProtocol {
- 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
}
@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
if let newDevice = device {
self?.storage?.saveThisDevice(newDevice)
self?.storage.saveThisDevice(newDevice)
self?.callbackQueue.addOperation({ () -> Void in
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) {
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
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
if let newDevice = device {
self?.storage?.saveThisDevice(newDevice)
self?.storage.saveThisDevice(newDevice)
self?.callbackQueue.addOperation({ () -> Void in
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) {
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
if success {
self?.getNewPushToken(buddyKey, completion: completion)
@ -253,11 +247,11 @@ open class PushController: NSObject, PushControllerProtocol {
return
}
self?.storage?.removeUnusedToken(tokenContainer)
self?.storage.removeUnusedToken(tokenContainer)
if let buddyKey = buddyKey {
self?.storage?.associateBuddy(tokenContainer, buddyKey: buddyKey)
self?.storage.associateBuddy(tokenContainer, buddyKey: buddyKey)
} else {
self?.storage?.saveUsedToken(tokenContainer)
self?.storage.saveUsedToken(tokenContainer)
}
self?.callbackQueue.addOperation({ () -> Void in
completion(tokenContainer, nil)
@ -276,7 +270,7 @@ open class PushController: NSObject, PushControllerProtocol {
return
}
tokenContainer.pushToken = newToken
self?.storage?.saveUnusedToken(tokenContainer)
self?.storage.saveUnusedToken(tokenContainer)
self?.callbackQueue.addOperation({ () -> Void in
completion(true,nil)
})
@ -299,7 +293,7 @@ open class PushController: NSObject, PushControllerProtocol {
@objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) {
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
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
})
@ -308,11 +302,11 @@ open class PushController: NSObject, PushControllerProtocol {
var tokensToCreate:UInt = 0
guard let unusedTokens = self?.storage?.numberUnusedTokens() else {
guard let unusedTokens = self?.storage.numberUnusedTokens() else {
return;
}
guard let minimumCount = self?.storage?.unusedTokenStoreMinimum() else {
guard let minimumCount = self?.storage.unusedTokenStoreMinimum() else {
return;
}
@ -369,7 +363,7 @@ open class PushController: NSObject, PushControllerProtocol {
tokenContainer.pushToken = token
tokenContainer.endpoint = endpointURL
tokenContainer.buddyKey = buddyKey
self?.storage?.saveUsedToken(tokenContainer)
self?.storage.saveUsedToken(tokenContainer)
self?.callbackQueue.addOperation({ () -> Void in
completion(true, nil)
})
@ -412,7 +406,7 @@ open class PushController: NSObject, PushControllerProtocol {
public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) {
do {
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
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) {
DispatchQueue.global().async {[weak self] () -> Void in
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
completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil))
})
@ -464,7 +458,7 @@ open class PushController: NSObject, PushControllerProtocol {
if ((error as NSError?)?.code == 404) {
// 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
self?.sendKnock(buddyKey, completion: completion)
}
@ -547,7 +541,7 @@ open class PushController: NSObject, PushControllerProtocol {
//MARK: OTRPushTLVHandlerDelegate
@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 {
//Error fetching buddy
@ -566,7 +560,7 @@ open class PushController: NSObject, PushControllerProtocol {
}
// 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 {
return
}
@ -584,7 +578,7 @@ open class PushController: NSObject, PushControllerProtocol {
//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 {
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
if preference == .enabled {
bool = true
@ -607,79 +601,79 @@ open class PushController: NSObject, PushControllerProtocol {
//MARK: Utility
/// If callbackQueue is nil, it will complete on main queue
public func gatherPushInfo(completion: @escaping (PushInfo?) -> (), callbackQueue: DispatchQueue = DispatchQueue.main) {
guard let storage = self.storage else {
callbackQueue.async {
completion(nil)
}
return
}
public func gatherPushInfo(completion: @escaping (PushInfo) -> (), callbackQueue: DispatchQueue?) {
var pubsubEndpoint: String?
var pushPermitted = false
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
PushController.canReceivePushNotifications(completion: { (enabled) in
pushPermitted = enabled
group.leave()
})
}
pushPermitted = PushController.canReceivePushNotifications() // This will be async in a later version when we do iOS 10 refactor
group.enter()
group.leave()
getPubsubEndpoint { (endpoint, error) in
pubsubEndpoint = endpoint
group.leave()
}
group.notify(queue: .main) {
let device = storage.thisDevice()
let queue = callbackQueue ?? DispatchQueue.main
group.notify(queue: DispatchQueue.global(qos: .default)) {
let device = self.storage.thisDevice()
let newPushInfo = PushInfo(
pushAPIURL: self.apiClient.baseUrl,
hasPushAccount: storage.hasPushAccount(),
numUsedTokens: storage.numberUsedTokens(),
numUnusedTokens: storage.numberUnusedTokens(),
hasPushAccount: self.storage.hasPushAccount(),
numUsedTokens: self.storage.numberUsedTokens(),
numUnusedTokens: self.storage.numberUnusedTokens(),
pushPermitted: pushPermitted,
pubsubEndpoint: pubsubEndpoint,
device: device)
callbackQueue.async {
queue.async {
completion(newPushInfo)
}
}
}
@objc public static func registerForPushNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
DispatchQueue.main.async(execute: {
// TODO: Handle push registration error
let app = UIApplication.shared
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil)
@objc open static func registerForPushNotifications() {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
DispatchQueue.main.async(execute: {
// TODO: Handle push registration error
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 {
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 YapDatabase
@objc public protocol PushStorageProtocol: AnyObject {
@objc public protocol PushStorageProtocol: class {
func thisDevicePushAccount() -> Account?
func hasPushAccount() -> Bool
func saveThisAccount(_ account:Account)
@ -132,9 +132,11 @@ class PushStorage: NSObject, PushStorageProtocol {
func unusedToken() -> TokenContainer? {
var tokenContainer:TokenContainer? = nil
self.databaseConnection.read { (transaction) -> Void in
transaction.iterateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, tc: TokenContainer, stop) -> Void in
tokenContainer = tc
stop = true
transaction.enumerateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, object, stop) -> Void in
if let tc = object as? TokenContainer {
tokenContainer = tc
}
stop.initialize(to: true)
})
}
return tokenContainer
@ -234,16 +236,18 @@ class PushStorage: NSObject, PushStorageProtocol {
self.databaseConnection.asyncReadWrite({ (transaction) in
let collection = PushYapCollections.unusedTokenCollection.rawValue
var removeKeyArray:[String] = []
transaction.iterateKeysAndObjects(inCollection: collection, using: { (key, token: TokenContainer, stop) in
//Check that there is an expires date otherwise remove
guard let expiresDate = token.pushToken?.expires else {
removeKeyArray.append(token.uniqueId)
return
}
// Check that the date is farther in the future than currentDate + timeBuffer
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
removeKeyArray.append(token.uniqueId)
transaction.enumerateKeysAndObjects(inCollection: collection, using: { (key, object, stop) in
if let token = object as? TokenContainer {
//Check that there is an expires date otherwise remove
guard let expiresDate = token.pushToken?.expires else {
removeKeyArray.append(token.uniqueId)
return
}
// Check that the date is farther in the future than currentDate + timeBuffer
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
removeKeyArray.append(token.uniqueId)
}
}
})

View File

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

View File

@ -22,11 +22,11 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
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
}
open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
var name = SOMEONE_STRING()
if let displayName = account?.username {
name = displayName
@ -38,7 +38,7 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
}
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)])
account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in
@ -48,7 +48,7 @@ open class ShareController: NSObject {
let qrCodeActivity = OTRQRCodeActivity()
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 view = sender as? UIView {
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)
}
}

View File

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

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