WKCookieWebView.swift 7.7 KB

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