Skip to content

Commit

Permalink
Simplify User Agent logic and update unit tests (#3586)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/1201037661562251/1208568177174606/f

Description:
This change updates User Agent rules as follows:
- The default UA is now DDG-branded and it's applied to all web, including duckduckgo.com.
- defaultPolicy privacy config item is no longer supported.
- 2 types of privacy-config-provided exceptions are supported: webViewDefault (using WebKit default UA)
  and defaultSites (using Safari UA).
  • Loading branch information
ayoy authored Nov 22, 2024
1 parent 5099d1c commit ed4b283
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 68 deletions.
60 changes: 14 additions & 46 deletions DuckDuckGo/UserAgent/Model/UserAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ extension UserAgent {
static let fallbackWebKitVersion = "605.1.15"
static let fallbackWebViewDefault = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)"

static let defaultPolicyConfigKey = "defaultPolicy"
static let defaultSitesConfigKey = "defaultSites"

static let brandPolicy = "brand"

// MARK: - Loaded versions

static let safariVersion: String = {
Expand Down Expand Up @@ -60,78 +55,51 @@ extension UserAgent {
"AppleWebKit/\(webKitVersion) (KHTML, like Gecko) " +
"Version/\(safariVersion) " +
"Safari/\(webKitVersion)"
static let chrome = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/91.0.4472.101 " +
"Safari/537.36"
static let `default` = UserAgent.safari
static let webViewDefault = ""
static let brandedDefault = "\(Self.default) \(ddgVersion)"

static let localUserAgentConfiguration: KeyValuePairs<RegEx, String> = [
// use safari when serving up PDFs from duckduckgo directly
regex("https://duckduckgo\\.com/[^?]*\\.pdf"): UserAgent.safari,

// use default WKWebView user agent for duckduckgo domain to remove CTA
regex("https://duckduckgo\\.com/.*"): UserAgent.webViewDefault
]
static let brandedDefault = "\(Self.safari) \(ddgVersion)"
static let `default` = UserAgent.brandedDefault

static func `for`(_ url: URL?,
privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> String {
guard let absoluteString = url?.absoluteString else {
guard let url, privacyConfig.isEnabled(featureKey: .customUserAgent) else {
return Self.default
}

if let userAgent = localUserAgentConfiguration.first(where: { (regex, _) in absoluteString.matches(regex) })?.value {
return userAgent
}

guard privacyConfig.isEnabled(featureKey: .customUserAgent) else { return Self.default }

if isURLPartOfWebviewDefaultList(url: url, privacyConfig: privacyConfig) {
return UserAgent.webViewDefault
} else if isURLPartOfDefaultSitesList(url: url) {
return Self.default
return Self.webViewDefault
}

if isBrandPolicy(forConfig: privacyConfig) {
return Self.brandedDefault
} else {
return Self.default
if isURLPartOfDefaultSitesList(url: url, privacyConfig: privacyConfig) {
return Self.safari
}

return Self.default
}

// MARK: - Remote user agent configuration

static let defaultSitesKey = "defaultSites"
static let webviewDefaultKey = "webViewDefault"
static let domainKey = "domain"

private static func isURLPartOfWebviewDefaultList(url: URL?,
private static func isURLPartOfWebviewDefaultList(url: URL,
privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> Bool {
let settings = privacyConfig.settings(for: .customUserAgent)
let webViewDefaultList = settings[webviewDefaultKey] as? [[String: String]] ?? []
let domains = webViewDefaultList.map { $0[domainKey] ?? "" }

return domains.contains(where: { domain in
url?.isPart(ofDomain: domain) ?? false
url.isPart(ofDomain: domain)
})
}

private static func isURLPartOfDefaultSitesList(url: URL?, privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> Bool {
private static func isURLPartOfDefaultSitesList(url: URL, privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> Bool {

let uaSettings = privacyConfig.settings(for: .customUserAgent)
let defaultSitesObjs = uaSettings[defaultSitesConfigKey] as? [[String: String]] ?? []
let defaultSitesObjs = uaSettings[defaultSitesKey] as? [[String: String]] ?? []
let domains = defaultSitesObjs.map { $0[domainKey] ?? "" }

return domains.contains(where: { domain in
url?.isPart(ofDomain: domain) ?? false
url.isPart(ofDomain: domain)
})
}

private static func isBrandPolicy(forConfig config: PrivacyConfiguration) -> Bool {
let uaSettings = config.settings(for: .customUserAgent)
guard let policy = uaSettings[defaultPolicyConfigKey] as? String else { return false }
return policy == brandPolicy
}

}
57 changes: 35 additions & 22 deletions UnitTests/UserAgent/Model/UserAgentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ import XCTest

final class UserAgentTests: XCTestCase {

func test_default_user_agent_is_safari() {
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "http://localhost")!))
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "http://example.com")!))
func testThatDefaultUserAgentIsBranded() {
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "http://localhost")!))
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "http://example.com")!))
}

func test_when_domain_is_google_docs_then_user_agent_is_chrome() {
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "https://google.com")!))
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "https://accounts.google.com")!))
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "https://docs.google.com")!))
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "https://docs.google.com/spreadsheets/a/document")!))
XCTAssertEqual(UserAgent.safari, UserAgent.for(URL(string: "https://a.docs.google.com")!))
func testWhenDomainIsGoogleDocsThenUserAgentIsBranded() {
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "https://google.com")!))
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "https://accounts.google.com")!))
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "https://docs.google.com")!))
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "https://docs.google.com/spreadsheets/a/document")!))
XCTAssertEqual(UserAgent.brandedDefault, UserAgent.for(URL(string: "https://a.docs.google.com")!))
}

func testWhenDomainIsDuckDuckGo_ThenUserAgentDoesntIncludeChromeOrSafari() {
XCTAssert(!UserAgent.for(URL.duckDuckGo).contains("Safari"))
XCTAssert(!UserAgent.for(URL.duckDuckGo).contains("Chrome"))
func testWhenDomainIsDuckDuckGo_ThenUserAgentDoesNotIncludeChrome() {
XCTAssertFalse(UserAgent.for(URL.duckDuckGo).contains("Chrome"))
}

func testWhenUserAgentIsDuckDuckGo_ThenUserAgentContainsExpectedParameters() {
Expand All @@ -49,7 +48,30 @@ final class UserAgentTests: XCTestCase {
XCTAssertEqual(userAgent, "ddg_mac/\(appVersion) (\(appID); macOS \(systemVersion))")
}

func testWhenURLDomainIsOnWebViewDefaultListThenWebKitDefaultUserAgentIsUsed() {
func testWhenURLDomainIsOnDefaultSitesListThenUserAgentIsSafari() {
let config = MockPrivacyConfiguration()
config.featureSettings = [
"defaultSites": [
[
"domain": "wikipedia.org",
"reason": "reason"
],
[
"domain": "google.com",
"reason": "reason"
]
]
] as! [String: Any]

XCTAssertEqual(UserAgent.for("http://wikipedia.org".url, privacyConfig: config), UserAgent.safari)
XCTAssertEqual(UserAgent.for("https://wikipedia.org".url, privacyConfig: config), UserAgent.safari)
XCTAssertEqual(UserAgent.for("https://en.wikipedia.org/wiki/Duck".url, privacyConfig: config), UserAgent.safari)
XCTAssertEqual(UserAgent.for("https://google.com".url, privacyConfig: config), UserAgent.safari)
XCTAssertEqual(UserAgent.for("https://docs.google.com".url, privacyConfig: config), UserAgent.safari)
XCTAssertNotEqual(UserAgent.for("https://duckduckgo.com".url, privacyConfig: config), UserAgent.safari)
}

func testWhenURLDomainIsOnWebViewDefaultListThenUserAgentIsWebKitDefault() {
let config = MockPrivacyConfiguration()
config.featureSettings = [
"webViewDefault": [
Expand All @@ -72,15 +94,6 @@ final class UserAgentTests: XCTestCase {
XCTAssertNotEqual(UserAgent.for("https://duckduckgo.com".url, privacyConfig: config), UserAgent.webViewDefault)
}

func testWhenDefaultPolicyIsBrandThenBrandedUserAgentIsUsed() {
let config = MockPrivacyConfiguration()
config.featureSettings = [
"defaultPolicy": "brand"
] as! [String: Any]

XCTAssertEqual(UserAgent.for("http://wikipedia.org".url, privacyConfig: config), UserAgent.brandedDefault)
}

func testThatRemoteConfigurationTakesPrecedenceOverLocalConfiguration() {
let config = MockPrivacyConfiguration()

Expand Down

0 comments on commit ed4b283

Please sign in to comment.