mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
aed1b73185
Added a explicit "timeout" error to make debugging a little easier Added code to prevent the AttachmentUploadJob from continuing to try to upload if it's associated interaction has been deleted Updated the getDefaultRoomsIfNeeded to make an unauthenticated sequence all to get both capabilities and rooms (so we will know if the server is blinded and retrieve the room images using blinded auth) Fixed a bug where the notification badge wouldn't get cleared when removing data from a device Fixed a bug where adding an open group could start with an invalid 'infoUpdates' value resulting in invalid data getting retrieved Fixed a bug where under certain circumstances the PagedDatabaseObserver was filtering out updates (noticeable when restoring a device, would happen if the currentCount of content was smaller than the pageSize)
178 lines
8.9 KiB
Swift
178 lines
8.9 KiB
Swift
import Foundation
|
|
import PromiseKit
|
|
|
|
public enum HTTP {
|
|
private static let seedNodeURLSession = URLSession(configuration: .ephemeral, delegate: seedNodeURLSessionDelegate, delegateQueue: nil)
|
|
private static let seedNodeURLSessionDelegate = SeedNodeURLSessionDelegateImplementation()
|
|
private static let snodeURLSession = URLSession(configuration: .ephemeral, delegate: snodeURLSessionDelegate, delegateQueue: nil)
|
|
private static let snodeURLSessionDelegate = SnodeURLSessionDelegateImplementation()
|
|
|
|
// MARK: Certificates
|
|
private static let storageSeed1Cert: SecCertificate = {
|
|
let path = Bundle.main.path(forResource: "storage-seed-1", ofType: "der")!
|
|
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
|
return SecCertificateCreateWithData(nil, data as CFData)!
|
|
}()
|
|
|
|
private static let storageSeed3Cert: SecCertificate = {
|
|
let path = Bundle.main.path(forResource: "storage-seed-3", ofType: "der")!
|
|
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
|
return SecCertificateCreateWithData(nil, data as CFData)!
|
|
}()
|
|
|
|
private static let publicLokiFoundationCert: SecCertificate = {
|
|
let path = Bundle.main.path(forResource: "public-loki-foundation", ofType: "der")!
|
|
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
|
return SecCertificateCreateWithData(nil, data as CFData)!
|
|
}()
|
|
|
|
// MARK: Settings
|
|
public static let timeout: TimeInterval = 10
|
|
|
|
// MARK: Seed Node URL Session Delegate Implementation
|
|
private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate {
|
|
|
|
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
|
guard let trust = challenge.protectionSpace.serverTrust else {
|
|
return completionHandler(.cancelAuthenticationChallenge, nil)
|
|
}
|
|
// Mark the seed node certificates as trusted
|
|
let certificates = [ storageSeed1Cert, storageSeed3Cert, publicLokiFoundationCert ]
|
|
guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else {
|
|
return completionHandler(.cancelAuthenticationChallenge, nil)
|
|
}
|
|
// Check that the presented certificate is one of the seed node certificates
|
|
var result: SecTrustResultType = .invalid
|
|
guard SecTrustEvaluate(trust, &result) == errSecSuccess else {
|
|
return completionHandler(.cancelAuthenticationChallenge, nil)
|
|
}
|
|
switch result {
|
|
case .proceed, .unspecified:
|
|
// Unspecified indicates that evaluation reached an (implicitly trusted) anchor certificate without
|
|
// any evaluation failures, but never encountered any explicitly stated user-trust preference. This
|
|
// is the most common return value. The Keychain Access utility refers to this value as the "Use System
|
|
// Policy," which is the default user setting.
|
|
return completionHandler(.useCredential, URLCredential(trust: trust))
|
|
default: return completionHandler(.cancelAuthenticationChallenge, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Snode URL Session Delegate Implementation
|
|
private final class SnodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate {
|
|
|
|
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
|
// Snode to snode communication uses self-signed certificates but clients can safely ignore this
|
|
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
|
|
}
|
|
}
|
|
|
|
// MARK: - Verb
|
|
|
|
public enum Verb: String, Codable {
|
|
case get = "GET"
|
|
case put = "PUT"
|
|
case post = "POST"
|
|
case delete = "DELETE"
|
|
}
|
|
|
|
// MARK: - Error
|
|
|
|
public enum Error: LocalizedError, Equatable {
|
|
case generic
|
|
case invalidURL
|
|
case invalidJSON
|
|
case parsingFailed
|
|
case invalidResponse
|
|
case maxFileSizeExceeded
|
|
case httpRequestFailed(statusCode: UInt, data: Data?)
|
|
case timeout
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .generic: return "An error occurred."
|
|
case .invalidURL: return "Invalid URL."
|
|
case .invalidJSON: return "Invalid JSON."
|
|
case .parsingFailed, .invalidResponse: return "Invalid response."
|
|
case .maxFileSizeExceeded: return "Maximum file size exceeded."
|
|
case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)."
|
|
case .timeout: return "The request timed out."
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Main
|
|
|
|
public static func execute(_ verb: Verb, _ url: String, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<Data> {
|
|
return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession)
|
|
}
|
|
|
|
public static func execute(_ verb: Verb, _ url: String, parameters: JSON?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<Data> {
|
|
if let parameters = parameters {
|
|
do {
|
|
guard JSONSerialization.isValidJSONObject(parameters) else { return Promise(error: Error.invalidJSON) }
|
|
let body = try JSONSerialization.data(withJSONObject: parameters, options: [ .fragmentsAllowed ])
|
|
return execute(verb, url, body: body, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession)
|
|
}
|
|
catch (let error) {
|
|
return Promise(error: error)
|
|
}
|
|
}
|
|
else {
|
|
return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession)
|
|
}
|
|
}
|
|
|
|
public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<Data> {
|
|
var request = URLRequest(url: URL(string: url)!)
|
|
request.httpMethod = verb.rawValue
|
|
request.httpBody = body
|
|
request.timeoutInterval = timeout
|
|
request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent")
|
|
request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value
|
|
request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value
|
|
let (promise, seal) = Promise<Data>.pending()
|
|
let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession
|
|
let task = urlSession.dataTask(with: request) { data, response, error in
|
|
guard let data = data, let response = response as? HTTPURLResponse else {
|
|
if let error = error {
|
|
SNLog("\(verb.rawValue) request to \(url) failed due to error: \(error).")
|
|
} else {
|
|
SNLog("\(verb.rawValue) request to \(url) failed.")
|
|
}
|
|
|
|
// Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:)
|
|
switch (error as? NSError)?.code {
|
|
case NSURLErrorTimedOut: return seal.reject(Error.timeout)
|
|
default: return seal.reject(Error.httpRequestFailed(statusCode: 0, data: nil))
|
|
}
|
|
|
|
}
|
|
if let error = error {
|
|
SNLog("\(verb.rawValue) request to \(url) failed due to error: \(error).")
|
|
// Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:)
|
|
return seal.reject(Error.httpRequestFailed(statusCode: 0, data: data))
|
|
}
|
|
let statusCode = UInt(response.statusCode)
|
|
|
|
guard 200...299 ~= statusCode else {
|
|
var json: JSON? = nil
|
|
if let processedJson: JSON = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON {
|
|
json = processedJson
|
|
}
|
|
else if let result: String = String(data: data, encoding: .utf8) {
|
|
json = [ "result": result ]
|
|
}
|
|
|
|
let jsonDescription: String = (json?.prettifiedDescription ?? "no debugging info provided")
|
|
SNLog("\(verb.rawValue) request to \(url) failed with status code: \(statusCode) (\(jsonDescription)).")
|
|
return seal.reject(Error.httpRequestFailed(statusCode: statusCode, data: data))
|
|
}
|
|
|
|
seal.fulfill(data)
|
|
}
|
|
task.resume()
|
|
return promise
|
|
}
|
|
}
|