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

Commit

Permalink
Fix #3700: Inject "farbling" protection scripts per domain
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Apr 6, 2022
1 parent 664bdd2 commit edd7504
Show file tree
Hide file tree
Showing 24 changed files with 1,156 additions and 249 deletions.
72 changes: 64 additions & 8 deletions Client.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Client/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ class BrowserViewController: UIViewController, BrowserViewControllerDelegate {

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
ScriptFactory.shared.clearCaches()

for tab in tabManager.tabsForCurrentMode where tab.id != tabManager.selectedTab?.id {
tab.newTabPageViewController = nil
Expand Down Expand Up @@ -2047,8 +2048,6 @@ extension BrowserViewController: TabDelegate {

tab.addContentScript(FocusHelper(tab: tab), name: FocusHelper.name(), contentWorld: .defaultClient)

tab.addContentScript(FingerprintingProtection(tab: tab), name: FingerprintingProtection.name(), contentWorld: .page)

tab.addContentScript(BraveGetUA(tab: tab), name: BraveGetUA.name(), contentWorld: .page)
tab.addContentScript(
BraveSearchScriptHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,12 @@ extension BrowserViewController: WKNavigationDelegate {
}

let isPrivateBrowsing = PrivateBrowsingManager.shared.isPrivateBrowsing

// Check if custom user scripts must be added to the web view.
let tab = tabManager[webView]
tab?.userScriptManager?.handleDomainUserScript(for: url)

// Check if custom user scripts must be added to or removed from the web view.
tab?.userScriptManager?.userScriptTypes = UserScriptHelper.getUserScriptTypes(
for: navigationAction, options: isPrivateBrowsing ? .privateBrowsing : .default
)

// Brave Search logic.

Expand Down Expand Up @@ -268,17 +270,8 @@ extension BrowserViewController: WKNavigationDelegate {
on.compactMap { $0.rule }.forEach(controller.add)
off.compactMap { $0.rule }.forEach(controller.remove)

if let tab = tabManager[webView] {
tab.userScriptManager?.isFingerprintingProtectionEnabled =
domainForShields.isShieldExpected(.FpProtection, considerAllShieldsOption: true)
}

let isScriptsEnabled = !domainForShields.isShieldExpected(.NoScript, considerAllShieldsOption: true)
if #available(iOS 14.0, *) {
preferences.allowsContentJavaScript = isScriptsEnabled
} else {
webView.configuration.preferences.javaScriptEnabled = isScriptsEnabled
}
}

// Cookie Blocking code below
Expand Down Expand Up @@ -518,3 +511,18 @@ extension BrowserViewController: WKNavigationDelegate {
tab.redirectURLs.append(url)
}
}

extension WKNavigationType: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .linkActivated: return "linkActivated"
case .formResubmitted: return "formResubmitted"
case .backForward: return "backForward"
case .formSubmitted: return "formSubmitted"
case .other: return "other"
case .reload: return "reload"
@unknown default:
return "Unknown(\(rawValue))"
}
}
}
144 changes: 29 additions & 115 deletions Client/Frontend/Browser/DomainUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,139 +11,53 @@ import WebKit
private let log = Logger.browserLogger

enum DomainUserScript: CaseIterable {
case youtube
case youtubeAdBlock
case archive
case braveSearch
case braveTalk

static func get(for url: URL) -> Self? {
var found: DomainUserScript?

// First we look for exact domain match, if no matches we look for base domain matches.
guard let host = url.host else { return nil }
allCases.forEach {
if $0.associatedDomains.contains(host) {
found = $0
return
}
case braveSearchHelper
case braveTalkHelper

/// Initialize this script with a URL
init?(for url: URL) {
// First we look for an exact domain match
if let host = url.host, let found = Self.allCases.first(where: { $0.associatedDomains.contains(host) }) {
self = found
return
}

if found != nil { return found }

guard let baseDomain = url.baseDomain else { return nil }
allCases.forEach {
if $0.associatedDomains.contains(baseDomain) {
found = $0
return
}
// If no matches, we look for a baseDomain (eTLD+1) match.
if let baseDomain = url.baseDomain, let found = Self.allCases.first(where: { $0.associatedDomains.contains(baseDomain) }) {
self = found
return
}

return found
return nil
}

/// Returns a shield type for a given user script domain.
/// Returns nil if the domain's user script can't be turned off via a shield toggle.
/// Returns nil if the domain's user script can't be turned off via a shield toggle. (i.e. it's always enabled)
var shieldType: BraveShield? {
switch self {
case .youtube:
case .youtubeAdBlock:
return .AdblockAndTp
case .archive, .braveSearch, .braveTalk:
case .archive, .braveSearchHelper, .braveTalkHelper:
return nil
}
}

/// The domains associated with this script.
var associatedDomains: Set<String> {
switch self {
case .youtube:
return .init(arrayLiteral: "youtube.com")
case .archive:
return .init(arrayLiteral: "archive.is", "archive.today", "archive.vn", "archive.fo")
case .braveSearch:
return .init(arrayLiteral: "search.brave.com", "search-dev.brave.com")
case .braveTalk:
return .init(
arrayLiteral: "talk.brave.com", "beta.talk.brave.com",
"talk.bravesoftware.com", "beta.talk.bravesoftware.com",
"dev.talk.brave.software", "beta.talk.brave.software",
"talk.brave.software")
}
}

private var scriptName: String {
switch self {
case .youtube:
return "YoutubeAdblock"
case .archive:
return "ArchiveIsCompat"
case .braveSearch:
return "BraveSearchHelper"
case .braveTalk:
return "BraveTalkHelper"
}
}

var script: WKUserScript? {
guard let source = sourceFile else { return nil }

switch self {
case .youtube:
// Verify that the application itself is making a call to the JS script instead of other scripts on the page.
// This variable will be unique amongst scripts loaded in the page.
// When the script is called, the token is provided in order to access the script variable.
var alteredSource = source
let token = UserScriptManager.securityTokenString
alteredSource = alteredSource.replacingOccurrences(
of: "$<prunePaths>", with: "ABSPP\(token)",
options: .literal)
alteredSource = alteredSource.replacingOccurrences(
of: "$<findOwner>", with: "ABSFO\(token)",
options: .literal)
alteredSource = alteredSource.replacingOccurrences(
of: "$<setJS>", with: "ABSSJ\(token)",
options: .literal)

return WKUserScript.create(source: alteredSource, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: .page)
case .youtubeAdBlock:
return Set(arrayLiteral: "youtube.com")
case .archive:
return WKUserScript.create(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: .page)
case .braveSearch:
var alteredSource = source

let securityToken = UserScriptManager.securityTokenString
alteredSource =
alteredSource
.replacingOccurrences(
of: "$<brave-search-helper>",
with: "BSH\(UserScriptManager.messageHandlerTokenString)",
options: .literal
)
.replacingOccurrences(of: "$<security_token>", with: securityToken)

return WKUserScript.create(source: alteredSource, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: .page)
case .braveTalk:
var alteredSource = source

let securityToken = UserScriptManager.securityTokenString
alteredSource =
alteredSource
.replacingOccurrences(
of: "$<brave-talk-helper>",
with: "BT\(UserScriptManager.messageHandlerTokenString)",
options: .literal
)
.replacingOccurrences(of: "$<security_token>", with: securityToken)

return WKUserScript.create(source: alteredSource, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: .page)
}
}

private var sourceFile: String? {
guard let path = Bundle.main.path(forResource: scriptName, ofType: "js"),
let source = try? String(contentsOfFile: path)
else {
log.error("Failed to load \(scriptName).js")
return nil
return Set(arrayLiteral: "archive.is", "archive.today", "archive.vn", "archive.fo")
case .braveSearchHelper:
return Set(arrayLiteral: "search.brave.com", "search-dev.brave.com")
case .braveTalkHelper:
return Set(arrayLiteral: "talk.brave.com", "beta.talk.brave.com",
"talk.bravesoftware.com", "beta.talk.bravesoftware.com",
"dev.talk.brave.software", "beta.talk.brave.software",
"talk.brave.software")
}

return source
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class BraveSearchScriptHandler: TabContentScript {
replyHandler: (Any?, String?) -> Void
) {
defer { replyHandler(nil, nil) }
let allowedHosts = DomainUserScript.braveSearch.associatedDomains
let allowedHosts = DomainUserScript.braveSearchHelper.associatedDomains

guard let requestHost = message.frameInfo.request.url?.host,
allowedHosts.contains(requestHost),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class BraveTalkScriptHandler: TabContentScript {
replyHandler: (Any?, String?) -> Void
) {
defer { replyHandler(nil, nil) }
let allowedHosts = DomainUserScript.braveTalk.associatedDomains
let allowedHosts = DomainUserScript.braveTalkHelper.associatedDomains

guard let requestHost = message.frameInfo.request.url?.host,
allowedHosts.contains(requestHost),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,9 @@ extension PlaylistWebLoader: WKNavigationDelegate {
return
}

tab.userScriptManager?.handleDomainUserScript(for: url)
tab.userScriptManager?.userScriptTypes = UserScriptHelper.getUserScriptTypes(
for: navigationAction, options: .playlistCacheLoader
)

// For Playlist automatic detection since the above `handleDomainUserScript` removes ALL scripts!
if let script = playlistDetectorScript {
Expand Down Expand Up @@ -675,9 +677,6 @@ extension PlaylistWebLoader: WKNavigationDelegate {
on.compactMap { $0.rule }.forEach(controller.add)
off.compactMap { $0.rule }.forEach(controller.remove)

tab.userScriptManager?.isFingerprintingProtectionEnabled =
domainForShields.isShieldExpected(.FpProtection, considerAllShieldsOption: true)

webView.configuration.preferences.javaScriptEnabled = !domainForShields.isShieldExpected(.NoScript, considerAllShieldsOption: true)
}

Expand Down
1 change: 0 additions & 1 deletion Client/Frontend/Browser/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ class Tab: NSObject {
self.webView?.addObserver(self, forKeyPath: KVOConstants.URL.rawValue, options: .new, context: nil)
self.userScriptManager = UserScriptManager(
tab: self,
isFingerprintingProtectionEnabled: Preferences.Shields.fingerprintingProtection.value,
isCookieBlockingEnabled: Preferences.Privacy.blockAllCookies.value,
isPaymentRequestEnabled: webView.hasOnlySecureContent,
isWebCompatibilityMediaSourceAPIEnabled: Preferences.Playlist.webMediaSourceCompatibility.value,
Expand Down
Loading

0 comments on commit edd7504

Please sign in to comment.