Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fixed Opening Authenticated Documents in private and non-private mode. #1218

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions Client/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1879,12 +1879,14 @@ extension BrowserViewController: TabDelegate {
}

func tab(_ tab: Tab, willDeleteWebView webView: WKWebView) {
tab.cancelQueuedAlerts()
KVOs.forEach { webView.removeObserver(self, forKeyPath: $0.rawValue) }
webView.scrollView.removeObserver(self.scrollController, forKeyPath: KVOConstants.contentSize.rawValue)
webView.uiDelegate = nil
webView.scrollView.delegate = nil
webView.removeFromSuperview()
ensureMainThread {
tab.cancelQueuedAlerts()
KVOs.forEach { webView.removeObserver(self, forKeyPath: $0.rawValue) }
webView.scrollView.removeObserver(self.scrollController, forKeyPath: KVOConstants.contentSize.rawValue)
webView.uiDelegate = nil
webView.scrollView.delegate = nil
webView.removeFromSuperview()
}
}

fileprivate func findSnackbar(_ barToFind: SnackBar) -> Int? {
Expand Down Expand Up @@ -2128,7 +2130,19 @@ extension BrowserViewController: WKUIDelegate {
if let currentTab = tabManager.selectedTab {
screenshotHelper.takeScreenshot(currentTab)
}


// Copy the parent's cookies to the child tab
// Firefox desktop and chrome desktop work the same way.
// Cookies do NOT persist and do NOT get shared across individual tabs.
// Cookies are only shared from parent to child (same as Desktop).
// Cookies are domain specific and do NOT leak when requested by a different domain.
// - Brandon T.
parentTab.webView?.configuration.websiteDataStore.httpCookieStore.getAllCookies({
$0.forEach({
configuration.websiteDataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})
})

// If the page uses `window.open()` or `[target="_blank"]`, open the page in a new tab.
// IMPORTANT!!: WebKit will perform the `URLRequest` automatically!! Attempting to do
// the request here manually leads to incorrect results!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ extension BrowserViewController: WKNavigationDelegate {
// If none of our helpers are responsible for handling this response,
// just let the webview handle it as normal.
decisionHandler(.allow)

flushCookiesToTabStorage(for: webView)
}

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
Expand Down Expand Up @@ -337,5 +339,31 @@ extension BrowserViewController: WKNavigationDelegate {
}
tabsBar.reloadDataAndRestoreSelectedTab()
}

flushCookiesToTabStorage(for: webView)
}

private func flushCookiesToTabStorage(for webView: WKWebView) {
if let tab = tabManager[webView] {
let group = DispatchGroup()
let tabs = sequence(first: tab, next: { $0.parent }).reversed()
var cookies = [[HTTPCookie]](repeating: [], count: tabs.count)

for it in tabs.enumerated() {
group.enter()
it.element.webView?.configuration.websiteDataStore.httpCookieStore.getAllCookies({
cookies[it.offset] = $0
group.leave()
})
}

group.notify(queue: .main) {
cookies.flatMap({ $0 }).forEach({
tab.cookieStorage.setCookie($0)
})
tab.temporaryDocument?.setCookies(tab.cookieStorage.cookies)
tab.cookieStorage.cookies?.forEach({ tab.cookieStorage.deleteCookie($0) })
}
}
}
}
1 change: 1 addition & 0 deletions Client/Frontend/Browser/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Tab: NSObject {
var url: URL?
var mimeType: String?
var isEditing: Bool = false
let cookieStorage = BATEphemeralCookieStorage()

// When viewing a non-HTML content type in the webview (like a PDF document), this URL will
// point to a tempfile containing the content so it can be shared to external applications.
Expand Down
151 changes: 148 additions & 3 deletions Client/Frontend/Browser/TemporaryDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,145 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import Foundation
import WebKit
import Deferred
import Shared

private let temporaryDocumentOperationQueue = OperationQueue()

class BATEphemeralCookieStorage: HTTPCookieStorage {
private var _allCookies: [String: HTTPCookie]
private let queue = DispatchQueue(label: "org.bat.cookie.storage.queue")

private var allCookies: [String: HTTPCookie] {
get {
dispatchPrecondition(condition: DispatchPredicate.onQueue(self.queue))
return self._allCookies
}
set {
dispatchPrecondition(condition: DispatchPredicate.onQueue(self.queue))
self._allCookies = newValue
}
}

override init() {
_allCookies = [:]
super.init()
cookieAcceptPolicy = .always
}

override var cookies: [HTTPCookie]? {
return Array(self.queue.sync { self.allCookies.values })
}

override func setCookie(_ cookie: HTTPCookie) {
self.queue.sync {
guard cookieAcceptPolicy != .never else { return }

let key = cookie.domain + cookie.path + cookie.name
if allCookies.index(forKey: key) != nil {
allCookies.updateValue(cookie, forKey: key)
} else {
allCookies[key] = cookie
}

let expired = allCookies.filter { (_, value) in
value.expiresDate != nil && value.expiresDate!.timeIntervalSinceNow < 0
}

for key in expired.keys {
self.allCookies.removeValue(forKey: key)
}
}
}

override func deleteCookie(_ cookie: HTTPCookie) {
self.queue.sync {
let key = cookie.domain + cookie.path + cookie.name
self.allCookies.removeValue(forKey: key)
}
}

override func removeCookies(since date: Date) {
self.queue.sync {
let cookiesSinceDate = self.allCookies.values.filter {
if let creationDate = $0.properties?[HTTPCookiePropertyKey(rawValue: "created")] as? Double {
return creationDate > date.timeIntervalSinceReferenceDate
}
return false
}

for cookie in cookiesSinceDate {
let key = cookie.domain + cookie.path + cookie.name
self.allCookies.removeValue(forKey: key)
}
}
}

override func cookies(for url: URL) -> [HTTPCookie]? {
guard let host = url.host?.lowercased() else { return nil }
return Array(self.queue.sync(execute: { allCookies }).values.filter {
guard $0.domain.hasPrefix(".") else { return host == $0.domain }
return host == $0.domain.dropFirst() || host.hasSuffix($0.domain)
})
}

override func setCookies(_ cookies: [HTTPCookie], for url: URL?, mainDocumentURL: URL?) {
guard cookieAcceptPolicy != .never else { return }
guard let urlHost = url?.host?.lowercased() else { return }

if mainDocumentURL != nil && cookieAcceptPolicy == .onlyFromMainDocumentDomain {
guard let mainDocumentHost = mainDocumentURL?.host?.lowercased() else { return }
guard mainDocumentHost.hasSuffix(urlHost) else { return }
}

let validCookies = cookies.filter {
guard $0.domain.hasPrefix(".") else { return urlHost == $0.domain }
return urlHost == $0.domain.dropFirst() || urlHost.hasSuffix($0.domain)
}

for cookie in validCookies {
setCookie(cookie)
}
}

override var cookieAcceptPolicy: HTTPCookie.AcceptPolicy {
get {
return .always
}

set {

}
}

override func sortedCookies(using sortOrder: [NSSortDescriptor]) -> [HTTPCookie] {
return queue.sync {
let cookies = Array(allCookies.values) as NSArray
return cookies.sortedArray(using: sortOrder) as? [HTTPCookie] ?? []
}
}

override var description: String {
return "Ephemeral <BATMemoryCookieStorage cookies count:\(cookies?.count ?? 0)>"
}

func apply(to request: URLRequest) -> URLRequest {
if let url = request.url, let cookies = self.cookies(for: url) {
var headers = request.allHTTPHeaderFields ?? [:]
HTTPCookie.requestHeaderFields(with: cookies).forEach({
headers.updateValue($0.value, forKey: $0.key)
})

var result = request
result.allHTTPHeaderFields = headers
return result
}

return request
}
}

class TemporaryDocument: NSObject {
fileprivate let request: URLRequest
fileprivate let filename: String
Expand All @@ -17,14 +151,19 @@ class TemporaryDocument: NSObject {
fileprivate var downloadTask: URLSessionDownloadTask?
fileprivate var localFileURL: URL?
fileprivate var pendingResult: Deferred<URL>?
fileprivate let cookieStorage = BATEphemeralCookieStorage()

init(preflightResponse: URLResponse, request: URLRequest) {
self.request = request
self.filename = preflightResponse.suggestedFilename ?? "unknown"

super.init()

self.session = URLSession(configuration: .default, delegate: self, delegateQueue: temporaryDocumentOperationQueue)
self.session = URLSession(configuration: .default, delegate: self, delegateQueue: temporaryDocumentOperationQueue).then {
$0.configuration.httpCookieStorage = self.cookieStorage
$0.configuration.httpCookieAcceptPolicy = .always
$0.configuration.httpShouldSetCookies = true
}
}

deinit {
Expand All @@ -33,6 +172,12 @@ class TemporaryDocument: NSObject {
try? FileManager.default.removeItem(at: url)
}
}

func setCookies(_ cookies: [HTTPCookie]?) {
cookies?.forEach({
self.cookieStorage.setCookie($0)
})
}

func getURL() -> Deferred<URL> {
if let url = localFileURL {
Expand All @@ -47,8 +192,8 @@ class TemporaryDocument: NSObject {

let result = Deferred<URL>()
pendingResult = result

downloadTask = session?.downloadTask(with: request)
downloadTask = session?.downloadTask(with: self.cookieStorage.apply(to: self.request))
downloadTask?.resume()

UIApplication.shared.isNetworkActivityIndicatorVisible = true
Expand Down