NCNetworking.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. @objcMembers
  32. class NCNetworking: NSObject, NKCommonDelegate {
  33. public static let shared: NCNetworking = {
  34. let instance = NCNetworking()
  35. NotificationCenter.default.addObserver(instance, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  36. return instance
  37. }()
  38. public struct TransferInForegorund {
  39. var ocId: String
  40. var progress: Float
  41. }
  42. struct FileNameServerUrl: Hashable {
  43. var fileName: String
  44. var serverUrl: String
  45. }
  46. let utilityFileSystem = NCUtilityFileSystem()
  47. let utility = NCUtility()
  48. var lastReachability: Bool = true
  49. var networkReachability: NKCommon.TypeReachability?
  50. let downloadRequest = ThreadSafeDictionary<String, DownloadRequest>()
  51. let uploadRequest = ThreadSafeDictionary<String, UploadRequest>()
  52. let uploadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
  53. let downloadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
  54. var transferInForegorund: TransferInForegorund?
  55. let transferInError = ThreadSafeDictionary<String, Int>()
  56. func transferInError(ocId: String) {
  57. if let counter = self.transferInError[ocId] {
  58. self.transferInError[ocId] = counter + 1
  59. } else {
  60. self.transferInError[ocId] = 1
  61. }
  62. }
  63. func removeTransferInError(ocId: String) {
  64. self.transferInError.removeValue(forKey: ocId)
  65. }
  66. lazy var nkBackground: NKBackground = {
  67. let nckb = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance)
  68. return nckb
  69. }()
  70. public let sessionMaximumConnectionsPerHost = 5
  71. public let sessionDownloadBackground: String = "com.nextcloud.session.download.background"
  72. public let sessionUploadBackground: String = "com.nextcloud.session.upload.background"
  73. public let sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan"
  74. public let sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension"
  75. public lazy var sessionManagerDownloadBackground: URLSession = {
  76. let configuration = URLSessionConfiguration.background(withIdentifier: sessionDownloadBackground)
  77. configuration.allowsCellularAccess = true
  78. configuration.sessionSendsLaunchEvents = true
  79. configuration.isDiscretionary = false
  80. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  81. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  82. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  83. return session
  84. }()
  85. public lazy var sessionManagerUploadBackground: URLSession = {
  86. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackground)
  87. configuration.allowsCellularAccess = true
  88. configuration.sessionSendsLaunchEvents = true
  89. configuration.isDiscretionary = false
  90. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  91. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  92. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  93. return session
  94. }()
  95. public lazy var sessionManagerUploadBackgroundWWan: URLSession = {
  96. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackgroundWWan)
  97. configuration.allowsCellularAccess = false
  98. configuration.sessionSendsLaunchEvents = true
  99. configuration.isDiscretionary = false
  100. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  101. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  102. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  103. return session
  104. }()
  105. #if EXTENSION
  106. public lazy var sessionManagerUploadBackgroundExtension: URLSession = {
  107. let configuration = URLSessionConfiguration.background(withIdentifier: sessionUploadBackgroundExtension)
  108. configuration.allowsCellularAccess = true
  109. configuration.sessionSendsLaunchEvents = true
  110. configuration.isDiscretionary = false
  111. configuration.httpMaximumConnectionsPerHost = sessionMaximumConnectionsPerHost
  112. configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
  113. configuration.sharedContainerIdentifier = NCBrandOptions.shared.capabilitiesGroups
  114. let session = URLSession(configuration: configuration, delegate: nkBackground, delegateQueue: OperationQueue.main)
  115. return session
  116. }()
  117. #endif
  118. // REQUESTS
  119. var requestsUnifiedSearch: [DataRequest] = []
  120. // OPERATIONQUEUE
  121. let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  122. let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  123. let downloadThumbnailTrashQueue = Queuer(name: "downloadThumbnailTrashQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  124. let unifiedSearchQueue = Queuer(name: "unifiedSearchQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
  125. let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
  126. let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCBrandOptions.shared.maxConcurrentOperationDownload, qualityOfService: .default)
  127. let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  128. let convertLivePhotoQueue = Queuer(name: "convertLivePhotoQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
  129. // MARK: - init
  130. override init() {
  131. super.init()
  132. #if EXTENSION
  133. print("Start Background Upload Extension: ", sessionUploadBackgroundExtension)
  134. #else
  135. print("Start Background Download: ", sessionManagerDownloadBackground)
  136. print("Start Background Upload: ", sessionManagerUploadBackground)
  137. print("Start Background Upload WWan: ", sessionManagerUploadBackgroundWWan)
  138. #endif
  139. }
  140. // MARK: - NotificationCenter
  141. func applicationDidEnterBackground() {
  142. self.transferInError.removeAll()
  143. }
  144. // MARK: - Communication Delegate
  145. func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) {
  146. if typeReachability == NKCommon.TypeReachability.reachableCellular || typeReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
  147. if !lastReachability {
  148. #if !EXTENSION
  149. NCService().startRequestServicesServer()
  150. #endif
  151. }
  152. lastReachability = true
  153. } else {
  154. if lastReachability {
  155. let error = NKError(errorCode: NCGlobal.shared.errorNetworkNotAvailable, errorDescription: "")
  156. NCContentPresenter().messageNotification("_network_not_available_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.info)
  157. }
  158. lastReachability = false
  159. }
  160. networkReachability = typeReachability
  161. }
  162. func authenticationChallenge(_ session: URLSession,
  163. didReceive challenge: URLAuthenticationChallenge,
  164. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  165. DispatchQueue.global().async {
  166. self.checkTrustedChallenge(session, didReceive: challenge, completionHandler: completionHandler)
  167. }
  168. }
  169. func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
  170. #if !EXTENSION
  171. if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let completionHandler = appDelegate.backgroundSessionCompletionHandler {
  172. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Called urlSessionDidFinishEvents for Background URLSession")
  173. appDelegate.backgroundSessionCompletionHandler = nil
  174. completionHandler()
  175. }
  176. #endif
  177. }
  178. // MARK: -
  179. func cancelAllQueue() {
  180. downloadQueue.cancelAll()
  181. downloadThumbnailQueue.cancelAll()
  182. downloadThumbnailActivityQueue.cancelAll()
  183. downloadThumbnailTrashQueue.cancelAll()
  184. downloadAvatarQueue.cancelAll()
  185. unifiedSearchQueue.cancelAll()
  186. saveLivePhotoQueue.cancelAll()
  187. convertLivePhotoQueue.cancelAll()
  188. }
  189. func cancelAllTask() {
  190. cancelAllQueue()
  191. cancelDataTask()
  192. cancelDownloadTasks()
  193. cancelUploadTasks()
  194. cancelDownloadBackgroundTask()
  195. cancelUploadBackgroundTask()
  196. }
  197. // MARK: - Pinning check
  198. public func checkTrustedChallenge(_ session: URLSession,
  199. didReceive challenge: URLAuthenticationChallenge,
  200. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  201. let protectionSpace: URLProtectionSpace = challenge.protectionSpace
  202. let directoryCertificate = utilityFileSystem.directoryCertificates
  203. let host = challenge.protectionSpace.host
  204. let certificateSavedPath = directoryCertificate + "/" + host + ".der"
  205. var isTrusted: Bool
  206. if let serverTrust: SecTrust = protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
  207. // extarct certificate txt
  208. saveX509Certificate(certificate, host: host, directoryCertificate: directoryCertificate)
  209. let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil)
  210. let certificateCopyData = SecCertificateCopyData(certificate)
  211. let data = CFDataGetBytePtr(certificateCopyData)
  212. let size = CFDataGetLength(certificateCopyData)
  213. let certificateData = NSData(bytes: data, length: size)
  214. certificateData.write(toFile: directoryCertificate + "/" + host + ".tmp", atomically: true)
  215. if isServerTrusted {
  216. isTrusted = true
  217. } else if let certificateDataSaved = NSData(contentsOfFile: certificateSavedPath), certificateData.isEqual(to: certificateDataSaved as Data) {
  218. isTrusted = true
  219. } else {
  220. isTrusted = false
  221. }
  222. } else {
  223. isTrusted = false
  224. }
  225. if isTrusted {
  226. completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
  227. } else {
  228. #if !EXTENSION
  229. DispatchQueue.main.async { (UIApplication.shared.delegate as? AppDelegate)?.trustCertificateError(host: host) }
  230. #endif
  231. completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
  232. }
  233. }
  234. func writeCertificate(host: String) {
  235. let directoryCertificate = utilityFileSystem.directoryCertificates
  236. let certificateAtPath = directoryCertificate + "/" + host + ".tmp"
  237. let certificateToPath = directoryCertificate + "/" + host + ".der"
  238. if !utilityFileSystem.copyFile(atPath: certificateAtPath, toPath: certificateToPath) {
  239. NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Write certificare error")
  240. }
  241. }
  242. func saveX509Certificate(_ certificate: SecCertificate, host: String, directoryCertificate: String) {
  243. let certNamePathTXT = directoryCertificate + "/" + host + ".txt"
  244. let data: CFData = SecCertificateCopyData(certificate)
  245. let mem = BIO_new_mem_buf(CFDataGetBytePtr(data), Int32(CFDataGetLength(data)))
  246. let x509cert = d2i_X509_bio(mem, nil)
  247. if x509cert == nil {
  248. NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] OpenSSL couldn't parse X509 Certificate")
  249. } else {
  250. // save details
  251. if FileManager.default.fileExists(atPath: certNamePathTXT) {
  252. do {
  253. try FileManager.default.removeItem(atPath: certNamePathTXT)
  254. } catch { }
  255. }
  256. let fileCertInfo = fopen(certNamePathTXT, "w")
  257. if fileCertInfo != nil {
  258. let output = BIO_new_fp(fileCertInfo, BIO_NOCLOSE)
  259. X509_print_ex(output, x509cert, UInt(XN_FLAG_COMPAT), UInt(X509_FLAG_COMPAT))
  260. BIO_free(output)
  261. }
  262. fclose(fileCertInfo)
  263. X509_free(x509cert)
  264. }
  265. BIO_free(mem)
  266. }
  267. func checkPushNotificationServerProxyCertificateUntrusted(viewController: UIViewController?,
  268. completion: @escaping (_ error: NKError) -> Void) {
  269. guard let host = URL(string: NCBrandOptions.shared.pushNotificationServerProxy)?.host else { return }
  270. NextcloudKit.shared.checkServer(serverUrl: NCBrandOptions.shared.pushNotificationServerProxy) { error in
  271. guard error == .success else {
  272. completion(.success)
  273. return
  274. }
  275. if error == .success {
  276. NCNetworking.shared.writeCertificate(host: host)
  277. completion(error)
  278. } else if error.errorCode == NSURLErrorServerCertificateUntrusted {
  279. let alertController = UIAlertController(title: NSLocalizedString("_ssl_certificate_untrusted_", comment: ""), message: NSLocalizedString("_connect_server_anyway_", comment: ""), preferredStyle: .alert)
  280. alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in
  281. NCNetworking.shared.writeCertificate(host: host)
  282. completion(.success)
  283. }))
  284. alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in
  285. completion(error)
  286. }))
  287. alertController.addAction(UIAlertAction(title: NSLocalizedString("_certificate_details_", comment: ""), style: .default, handler: { _ in
  288. if let navigationController = UIStoryboard(name: "NCViewCertificateDetails", bundle: nil).instantiateInitialViewController() as? UINavigationController,
  289. let vcCertificateDetails = navigationController.topViewController as? NCViewCertificateDetails {
  290. vcCertificateDetails.host = host
  291. viewController?.present(navigationController, animated: true)
  292. }
  293. }))
  294. viewController?.present(alertController, animated: true)
  295. }
  296. }
  297. }
  298. }