WKCookieWebView.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. guard useRedirectCookieHandling else {
  41. return super.load(request)
  42. }
  43. requestWithCookieHandling(request, success: { (newRequest , response, data) in
  44. DispatchQueue.main.async {
  45. self.syncCookiesInJS()
  46. if let data = data, let response = response {
  47. let _ = self.webViewLoad(data: data, response: response)
  48. } else {
  49. self.syncCookies(newRequest, nil, { (cookieRequest) in
  50. let _ = super.load(cookieRequest)
  51. })
  52. }
  53. }
  54. }, failure: {
  55. // let WKWebView handle the network error
  56. DispatchQueue.main.async {
  57. self.syncCookies(request, nil, { (newRequest) in
  58. let _ = super.load(newRequest)
  59. })
  60. }
  61. })
  62. return nil
  63. }
  64. private func requestWithCookieHandling(_ request: URLRequest, success: @escaping (URLRequest, HTTPURLResponse?, Data?) -> Void, failure: @escaping () -> Void) {
  65. let sessionConfig = URLSessionConfiguration.default
  66. let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
  67. let task = session.dataTask(with: request) { (data, response, error) in
  68. if let _ = error {
  69. failure()
  70. } else {
  71. if let response = response as? HTTPURLResponse {
  72. let code = response.statusCode
  73. if code == 200 {
  74. // for code 200 return data to load data directly
  75. success(request, response, data)
  76. } else if code >= 300 && code < 400 {
  77. // for redirect get location in header,and make a new URLRequest
  78. guard let location = response.allHeaderFields["Location"] as? String, let redirectURL = URL(string: location) else {
  79. failure()
  80. return
  81. }
  82. let request = URLRequest(url: redirectURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5)
  83. success(request, nil, nil)
  84. } else {
  85. success(request, response, data)
  86. }
  87. }
  88. }
  89. }
  90. task.resume()
  91. }
  92. private func webViewLoad(data: Data, response: URLResponse) -> WKNavigation! {
  93. guard let url = response.url else {
  94. return nil
  95. }
  96. let encode = response.textEncodingName ?? "utf8"
  97. let mine = response.mimeType ?? "text/html"
  98. return self.load(data, mimeType: mine, characterEncodingName: encode, baseURL: url)
  99. }
  100. }
  101. extension WKCookieWebView {
  102. // sync HTTPCookieStorage cookies to URLRequest
  103. private func syncCookies(_ request: URLRequest, _ task: URLSessionTask? = nil, _ completion: @escaping (URLRequest) -> Void) {
  104. var request = request
  105. var cookiesArray = [HTTPCookie]()
  106. if let task = task {
  107. HTTPCookieStorage.shared.getCookiesFor(task, completionHandler: { (cookies) in
  108. if let cookies = cookies {
  109. cookiesArray.append(contentsOf: cookies)
  110. let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
  111. if let cookieStr = cookieDict["Cookie"] {
  112. request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
  113. }
  114. }
  115. completion(request)
  116. })
  117. } else if let url = request.url {
  118. if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
  119. cookiesArray.append(contentsOf: cookies)
  120. }
  121. let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
  122. if let cookieStr = cookieDict["Cookie"] {
  123. request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
  124. }
  125. completion(request)
  126. }
  127. }
  128. // MARK: - JS Cookie handling
  129. private func syncCookiesInJS(for request: URLRequest? = nil) {
  130. if let url = request?.url,
  131. let cookies = HTTPCookieStorage.shared.cookies(for: url) {
  132. let script = jsCookiesString(for: cookies)
  133. let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  134. self.configuration.userContentController.addUserScript(cookieScript)
  135. } else if let cookies = HTTPCookieStorage.shared.cookies {
  136. let script = jsCookiesString(for: cookies)
  137. let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  138. self.configuration.userContentController.addUserScript(cookieScript)
  139. }
  140. }
  141. private func jsCookiesString(for cookies: [HTTPCookie]) -> String {
  142. var result = ""
  143. let dateFormatter = DateFormatter()
  144. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  145. dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"
  146. for cookie in cookies {
  147. result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
  148. if let date = cookie.expiresDate {
  149. result += "expires=\(dateFormatter.string(from: date)); "
  150. }
  151. if (cookie.isSecure) {
  152. result += "secure; "
  153. }
  154. result += "'; "
  155. }
  156. return result
  157. }
  158. }
  159. extension WKCookieWebView : URLSessionTaskDelegate {
  160. func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
  161. syncCookies(request) { (newRequest) in
  162. completionHandler(newRequest)
  163. }
  164. }
  165. }