WKCookieWebView.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //
  2. // WKCookieWebView.swift
  3. //
  4. // Created by Jens Reynders on 30/03/2018.
  5. // Copyright (c) 2018 November Five
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in all
  15. // copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. // SOFTWARE.
  24. import Foundation
  25. import WebKit
  26. class WKCookieWebView : WKWebView {
  27. private let useRedirectCookieHandling: Bool
  28. init(frame: CGRect, configuration: WKWebViewConfiguration, useRedirectCookieHandling: Bool = false) {
  29. self.useRedirectCookieHandling = useRedirectCookieHandling
  30. super.init(frame: frame, configuration: configuration)
  31. }
  32. required init?(coder: NSCoder) {
  33. self.useRedirectCookieHandling = false
  34. super.init(coder: coder)
  35. }
  36. override func load(_ request: URLRequest) -> WKNavigation? {
  37. var request = request
  38. let language = NSLocale.preferredLanguages[0] as String
  39. request.setValue(CCUtility.getUserAgent(), forHTTPHeaderField: "User-Agent")
  40. request.addValue("true", forHTTPHeaderField: "OCS-APIRequest")
  41. request.addValue(language, forHTTPHeaderField: "Accept-Language")
  42. guard useRedirectCookieHandling else {
  43. return super.load(request)
  44. }
  45. requestWithCookieHandling(request, success: { (newRequest , response, data) in
  46. DispatchQueue.main.async {
  47. self.syncCookiesInJS()
  48. if let data = data, let response = response {
  49. let _ = self.webViewLoad(data: data, response: response)
  50. } else {
  51. self.syncCookies(newRequest, nil, { (cookieRequest) in
  52. let _ = super.load(cookieRequest)
  53. })
  54. }
  55. }
  56. }, failure: {
  57. // let WKWebView handle the network error
  58. DispatchQueue.main.async {
  59. self.syncCookies(request, nil, { (newRequest) in
  60. let _ = super.load(newRequest)
  61. })
  62. }
  63. })
  64. return nil
  65. }
  66. private func requestWithCookieHandling(_ request: URLRequest, success: @escaping (URLRequest, HTTPURLResponse?, Data?) -> Void, failure: @escaping () -> Void) {
  67. let sessionConfig = URLSessionConfiguration.default
  68. let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
  69. let task = session.dataTask(with: request) { (data, response, error) in
  70. if let _ = error {
  71. failure()
  72. } else {
  73. if let response = response as? HTTPURLResponse {
  74. let code = response.statusCode
  75. if code == 200 {
  76. // for code 200 return data to load data directly
  77. success(request, response, data)
  78. } else if code >= 300 && code < 400 {
  79. // for redirect get location in header,and make a new URLRequest
  80. guard let location = response.allHeaderFields["Location"] as? String, let redirectURL = URL(string: location) else {
  81. failure()
  82. return
  83. }
  84. let request = URLRequest(url: redirectURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5)
  85. success(request, nil, nil)
  86. } else {
  87. success(request, response, data)
  88. }
  89. }
  90. }
  91. }
  92. task.resume()
  93. }
  94. private func webViewLoad(data: Data, response: URLResponse) -> WKNavigation! {
  95. guard let url = response.url else {
  96. return nil
  97. }
  98. let encode = response.textEncodingName ?? "utf8"
  99. let mine = response.mimeType ?? "text/html"
  100. return self.load(data, mimeType: mine, characterEncodingName: encode, baseURL: url)
  101. }
  102. }
  103. extension WKCookieWebView {
  104. // sync HTTPCookieStorage cookies to URLRequest
  105. private func syncCookies(_ request: URLRequest, _ task: URLSessionTask? = nil, _ completion: @escaping (URLRequest) -> Void) {
  106. var request = request
  107. var cookiesArray = [HTTPCookie]()
  108. if let task = task {
  109. HTTPCookieStorage.shared.getCookiesFor(task, completionHandler: { (cookies) in
  110. if let cookies = cookies {
  111. cookiesArray.append(contentsOf: cookies)
  112. let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
  113. if let cookieStr = cookieDict["Cookie"] {
  114. request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
  115. }
  116. }
  117. completion(request)
  118. })
  119. } else if let url = request.url {
  120. if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
  121. cookiesArray.append(contentsOf: cookies)
  122. }
  123. let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
  124. if let cookieStr = cookieDict["Cookie"] {
  125. request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
  126. }
  127. completion(request)
  128. }
  129. }
  130. // MARK: - JS Cookie handling
  131. private func syncCookiesInJS(for request: URLRequest? = nil) {
  132. if let url = request?.url,
  133. let cookies = HTTPCookieStorage.shared.cookies(for: url) {
  134. let script = jsCookiesString(for: cookies)
  135. let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  136. self.configuration.userContentController.addUserScript(cookieScript)
  137. } else if let cookies = HTTPCookieStorage.shared.cookies {
  138. let script = jsCookiesString(for: cookies)
  139. let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  140. self.configuration.userContentController.addUserScript(cookieScript)
  141. }
  142. }
  143. private func jsCookiesString(for cookies: [HTTPCookie]) -> String {
  144. var result = ""
  145. let dateFormatter = DateFormatter()
  146. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  147. dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"
  148. for cookie in cookies {
  149. result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
  150. if let date = cookie.expiresDate {
  151. result += "expires=\(dateFormatter.string(from: date)); "
  152. }
  153. if (cookie.isSecure) {
  154. result += "secure; "
  155. }
  156. result += "'; "
  157. }
  158. return result
  159. }
  160. }
  161. extension WKCookieWebView : URLSessionTaskDelegate {
  162. func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
  163. syncCookies(request) { (newRequest) in
  164. completionHandler(newRequest)
  165. }
  166. }
  167. }