WKCookieWebView.swift 7.6 KB

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