NCNetworking.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. //
  2. // NCNetworking.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 23/10/19.
  6. // Copyright © 2019 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. import OpenSSL
  25. import NextcloudKit
  26. import Alamofire
  27. import Queuer
  28. #if EXTENSION_FILE_PROVIDER_EXTENSION || EXTENSION_WIDGET
  29. @objc protocol uploadE2EEDelegate: AnyObject { }
  30. #endif
  31. @objc protocol NCNetworkingDelegate {
  32. func downloadProgress(_ progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String, session: URLSession, task: URLSessionTask)
  33. func uploadProgress(_ progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String, session: URLSession, task: URLSessionTask)
  34. func downloadComplete(fileName: String, serverUrl: String, etag: String?, date: Date?, dateLastModified: Date?, length: Int64, task: URLSessionTask, error: NKError)
  35. func uploadComplete(fileName: String, serverUrl: String, ocId: String?, etag: String?, date: Date?, size: Int64, task: URLSessionTask, error: NKError)
  36. }
  37. @objc protocol ClientCertificateDelegate {
  38. func onIncorrectPassword()
  39. func didAskForClientCertificate()
  40. }
  41. @objcMembers
  42. class NCNetworking: NSObject, NKCommonDelegate {
  43. public static let shared: NCNetworking = {
  44. let instance = NCNetworking()
  45. NotificationCenter.default.addObserver(instance, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  46. return instance
  47. }()
  48. public struct TransferInForegorund {
  49. var ocId: String
  50. var progress: Float
  51. }
  52. struct FileNameServerUrl: Hashable {
  53. var fileName: String
  54. var serverUrl: String
  55. }
  56. let utilityFileSystem = NCUtilityFileSystem()
  57. let utility = NCUtility()
  58. var lastReachability: Bool = true
  59. var networkReachability: NKCommon.TypeReachability?
  60. let downloadRequest = ThreadSafeDictionary<String, DownloadRequest>()
  61. let uploadRequest = ThreadSafeDictionary<String, UploadRequest>()
  62. let uploadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
  63. let downloadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
  64. var transferInForegorund: TransferInForegorund?
  65. weak var delegate: NCNetworkingDelegate?
  66. weak var certificateDelegate: ClientCertificateDelegate?
  67. var p12Data: Data?
  68. var p12Password: String?
  69. let transferInError = ThreadSafeDictionary<String, Int>()
  70. func transferInError(ocId: String) {
  71. if let counter = self.transferInError[ocId] {
  72. self.transferInError[ocId] = counter + 1
  73. } else {
  74. self.transferInError[ocId] = 1
  75. }
  76. }
  77. func removeTransferInError(ocId: String) {
  78. self.transferInError.removeValue(forKey: ocId)
  79. }
  80. lazy var nkBackground: NKBackground = {
  81. let nckb = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance)
  82. return nckb
  83. }()
  84. public let sessionMaximumConnectionsPerHost = 5
  85. public let sessionDownloadBackground: String = "com.nextcloud.session.download.background"
  86. public let sessionUploadBackground: String = "com.nextcloud.session.upload.background"
  87. public let sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan"
  88. public let sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension"
  89. public lazy var sessionManagerDownloadBackground: URLSession = {
  90. let configuration = URLSessionConfiguration.background(withIdentifier: sessionDownloadBackground)
  91. configuration.allowsCellularAccess = true
  92. configuration.sessionSendsLaunchEvents = true
  93. configuration.isDiscretionary = false
  94. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  95. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  96. configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: NCBrandOptions.shared.capabilitiesGroup)
  97. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  98. return session
  99. }()
  100. public lazy var sessionManagerUploadBackground: URLSession = {
  101. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackground)
  102. configuration.allowsCellularAccess = true
  103. configuration.sessionSendsLaunchEvents = true
  104. configuration.isDiscretionary = false
  105. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  106. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  107. configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: NCBrandOptions.shared.capabilitiesGroup)
  108. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  109. return session
  110. }()
  111. public lazy var sessionManagerUploadBackgroundWWan: URLSession = {
  112. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackgroundWWan)
  113. configuration.allowsCellularAccess = false
  114. configuration.sessionSendsLaunchEvents = true
  115. configuration.isDiscretionary = false
  116. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  117. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  118. configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: NCBrandOptions.shared.capabilitiesGroup)
  119. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  120. return session
  121. }()
  122. public lazy var sessionManagerUploadBackgroundExtension: URLSession = {
  123. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackgroundExtension)
  124. configuration.allowsCellularAccess = true
  125. configuration.sessionSendsLaunchEvents = true
  126. configuration.isDiscretionary = false
  127. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  128. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  129. configuration.sharedContainerIdentifier = NCBrandOptions.shared.capabilitiesGroup
  130. configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: NCBrandOptions.shared.capabilitiesGroup)
  131. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  132. return session
  133. }()
  134. // REQUESTS
  135. var requestsUnifiedSearch: [DataRequest] = []
  136. // OPERATIONQUEUE
  137. let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  138. let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  139. let downloadThumbnailTrashQueue = Queuer(name: "downloadThumbnailTrashQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  140. let unifiedSearchQueue = Queuer(name: "unifiedSearchQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
  141. let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
  142. let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCBrandOptions.shared.maxConcurrentOperationDownload, qualityOfService: .default)
  143. let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  144. let convertLivePhotoQueue = Queuer(name: "convertLivePhotoQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  145. // MARK: - init
  146. override init() {
  147. super.init()
  148. getActiveAccountCertificate()
  149. NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil, queue: nil) { _ in
  150. self.getActiveAccountCertificate()
  151. }
  152. #if EXTENSION
  153. print("Start Background Upload Extension: ", sessionUploadBackgroundExtension)
  154. #else
  155. print("Start Background Download: ", sessionManagerDownloadBackground)
  156. print("Start Background Upload: ", sessionManagerUploadBackground)
  157. print("Start Background Upload WWan: ", sessionManagerUploadBackgroundWWan)
  158. #endif
  159. }
  160. // MARK: - NotificationCenter
  161. func applicationDidEnterBackground() {
  162. self.transferInError.removeAll()
  163. }
  164. // MARK: - Communication Delegate
  165. func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) {
  166. if typeReachability == NKCommon.TypeReachability.reachableCellular || typeReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
  167. if !lastReachability {
  168. #if !EXTENSION
  169. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  170. NCService().startRequestServicesServer(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId)
  171. }
  172. #endif
  173. }
  174. lastReachability = true
  175. } else {
  176. if lastReachability {
  177. let error = NKError(errorCode: NCGlobal.shared.errorNetworkNotAvailable, errorDescription: "")
  178. NCContentPresenter().messageNotification("_network_not_available_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.info)
  179. }
  180. lastReachability = false
  181. }
  182. networkReachability = typeReachability
  183. }
  184. func authenticationChallenge(_ session: URLSession,
  185. didReceive challenge: URLAuthenticationChallenge,
  186. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  187. if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
  188. DispatchQueue.main.async {
  189. if let p12Data = self.p12Data,
  190. let cert = (p12Data, self.p12Password) as? UserCertificate,
  191. let pkcs12 = try? PKCS12(pkcs12Data: cert.data, password: cert.password, onIncorrectPassword: {
  192. self.certificateDelegate?.onIncorrectPassword()
  193. }) {
  194. let creds = PKCS12.urlCredential(for: pkcs12)
  195. completionHandler(URLSession.AuthChallengeDisposition.useCredential, creds)
  196. } else {
  197. self.certificateDelegate?.didAskForClientCertificate()
  198. completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
  199. }
  200. }
  201. } else {
  202. DispatchQueue.global().async {
  203. self.checkTrustedChallenge(session, didReceive: challenge, completionHandler: completionHandler)
  204. }
  205. }
  206. }
  207. func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
  208. #if !EXTENSION
  209. if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let completionHandler = appDelegate.backgroundSessionCompletionHandler {
  210. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Called urlSessionDidFinishEvents for Background URLSession")
  211. appDelegate.backgroundSessionCompletionHandler = nil
  212. completionHandler()
  213. }
  214. #endif
  215. }
  216. // MARK: -
  217. func cancelAllQueue() {
  218. downloadQueue.cancelAll()
  219. downloadThumbnailQueue.cancelAll()
  220. downloadThumbnailActivityQueue.cancelAll()
  221. downloadThumbnailTrashQueue.cancelAll()
  222. downloadAvatarQueue.cancelAll()
  223. unifiedSearchQueue.cancelAll()
  224. saveLivePhotoQueue.cancelAll()
  225. convertLivePhotoQueue.cancelAll()
  226. }
  227. func cancelAllTask() {
  228. cancelAllQueue()
  229. cancelDataTask()
  230. cancelDownloadTasks()
  231. cancelUploadTasks()
  232. cancelDownloadBackgroundTask()
  233. cancelUploadBackgroundTask()
  234. }
  235. // MARK: - Pinning check
  236. public func checkTrustedChallenge(_ session: URLSession,
  237. didReceive challenge: URLAuthenticationChallenge,
  238. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  239. let protectionSpace: URLProtectionSpace = challenge.protectionSpace
  240. let directoryCertificate = utilityFileSystem.directoryCertificates
  241. let host = challenge.protectionSpace.host
  242. let certificateSavedPath = directoryCertificate + "/" + host + ".der"
  243. var isTrusted: Bool
  244. if let trust: SecTrust = protectionSpace.serverTrust,
  245. let certificates = (SecTrustCopyCertificateChain(trust) as? [SecCertificate]),
  246. let certificate = certificates.first {
  247. // extarct certificate txt
  248. saveX509Certificate(certificate, host: host, directoryCertificate: directoryCertificate)
  249. let isServerTrusted = SecTrustEvaluateWithError(trust, nil)
  250. let certificateCopyData = SecCertificateCopyData(certificate)
  251. let data = CFDataGetBytePtr(certificateCopyData)
  252. let size = CFDataGetLength(certificateCopyData)
  253. let certificateData = NSData(bytes: data, length: size)
  254. certificateData.write(toFile: directoryCertificate + "/" + host + ".tmp", atomically: true)
  255. if isServerTrusted {
  256. isTrusted = true
  257. } else if let certificateDataSaved = NSData(contentsOfFile: certificateSavedPath), certificateData.isEqual(to: certificateDataSaved as Data) {
  258. isTrusted = true
  259. } else {
  260. isTrusted = false
  261. }
  262. } else {
  263. isTrusted = false
  264. }
  265. if isTrusted {
  266. completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
  267. } else {
  268. #if !EXTENSION
  269. DispatchQueue.main.async { (UIApplication.shared.delegate as? AppDelegate)?.trustCertificateError(host: host) }
  270. #endif
  271. completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
  272. }
  273. }
  274. func writeCertificate(host: String) {
  275. let directoryCertificate = utilityFileSystem.directoryCertificates
  276. let certificateAtPath = directoryCertificate + "/" + host + ".tmp"
  277. let certificateToPath = directoryCertificate + "/" + host + ".der"
  278. if !utilityFileSystem.copyFile(atPath: certificateAtPath, toPath: certificateToPath) {
  279. NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Write certificare error")
  280. }
  281. }
  282. func saveX509Certificate(_ certificate: SecCertificate, host: String, directoryCertificate: String) {
  283. let certNamePathTXT = directoryCertificate + "/" + host + ".txt"
  284. let data: CFData = SecCertificateCopyData(certificate)
  285. let mem = BIO_new_mem_buf(CFDataGetBytePtr(data), Int32(CFDataGetLength(data)))
  286. let x509cert = d2i_X509_bio(mem, nil)
  287. if x509cert == nil {
  288. NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] OpenSSL couldn't parse X509 Certificate")
  289. } else {
  290. // save details
  291. if FileManager.default.fileExists(atPath: certNamePathTXT) {
  292. do {
  293. try FileManager.default.removeItem(atPath: certNamePathTXT)
  294. } catch { }
  295. }
  296. let fileCertInfo = fopen(certNamePathTXT, "w")
  297. if fileCertInfo != nil {
  298. let output = BIO_new_fp(fileCertInfo, BIO_NOCLOSE)
  299. X509_print_ex(output, x509cert, UInt(XN_FLAG_COMPAT), UInt(X509_FLAG_COMPAT))
  300. BIO_free(output)
  301. }
  302. fclose(fileCertInfo)
  303. X509_free(x509cert)
  304. }
  305. BIO_free(mem)
  306. }
  307. func checkPushNotificationServerProxyCertificateUntrusted(viewController: UIViewController?,
  308. completion: @escaping (_ error: NKError) -> Void) {
  309. guard let host = URL(string: NCBrandOptions.shared.pushNotificationServerProxy)?.host else { return }
  310. NextcloudKit.shared.checkServer(serverUrl: NCBrandOptions.shared.pushNotificationServerProxy) { error in
  311. guard error == .success else {
  312. completion(.success)
  313. return
  314. }
  315. if error == .success {
  316. NCNetworking.shared.writeCertificate(host: host)
  317. completion(error)
  318. } else if error.errorCode == NSURLErrorServerCertificateUntrusted {
  319. let alertController = UIAlertController(title: NSLocalizedString("_ssl_certificate_untrusted_", comment: ""), message: NSLocalizedString("_connect_server_anyway_", comment: ""), preferredStyle: .alert)
  320. alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in
  321. NCNetworking.shared.writeCertificate(host: host)
  322. completion(.success)
  323. }))
  324. alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in
  325. completion(error)
  326. }))
  327. alertController.addAction(UIAlertAction(title: NSLocalizedString("_certificate_details_", comment: ""), style: .default, handler: { _ in
  328. if let navigationController = UIStoryboard(name: "NCViewCertificateDetails", bundle: nil).instantiateInitialViewController() as? UINavigationController,
  329. let vcCertificateDetails = navigationController.topViewController as? NCViewCertificateDetails {
  330. vcCertificateDetails.host = host
  331. viewController?.present(navigationController, animated: true)
  332. }
  333. }))
  334. viewController?.present(alertController, animated: true)
  335. }
  336. }
  337. }
  338. private func getActiveAccountCertificate() {
  339. if let account = NCManageDatabase.shared.getActiveAccount()?.account {
  340. (self.p12Data, self.p12Password) = NCKeychain().getClientCertificate(account: account)
  341. }
  342. }
  343. }