Skip to content

Commit

Permalink
Ignore previous rules for popups in top-frame (#18)
Browse files Browse the repository at this point in the history
* Ignore previous rules for popups in top-frame

* Lint. Add test to assert new rule

* Lint
  • Loading branch information
SlayterDev authored Dec 6, 2023
1 parent ee8adbd commit a6b7ba1
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
30 changes: 24 additions & 6 deletions Sources/TrackerRadarKit/ContentBlockerRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,52 +45,70 @@ public struct ContentBlockerRule: Codable, Hashable {

}

public enum LoadContext: String, Codable {
case topFrame = "top-frame"
case childFrame = "child-frame"
}

let urlFilter: String
let unlessDomain: [String]?
let ifDomain: [String]?
let resourceType: [ResourceType]?
let loadType: [LoadType]?
let loadContext: [LoadContext]?

enum CodingKeys: String, CodingKey {
case urlFilter = "url-filter"
case unlessDomain = "unless-domain"
case ifDomain = "if-domain"
case resourceType = "resource-type"
case loadType = "load-type"
case loadContext = "load-context"
}

private init(urlFilter: String, unlessDomain: [String]?, ifDomain: [String]?, resourceType: [ResourceType]?, loadType: [LoadType]?) {
private init(urlFilter: String, unlessDomain: [String]?, ifDomain: [String]?,
resourceType: [ResourceType]?, loadType: [LoadType]?, loadContext: [LoadContext]?) {
self.urlFilter = urlFilter
self.unlessDomain = unlessDomain
self.ifDomain = ifDomain
self.resourceType = resourceType
self.loadType = loadType
self.loadContext = loadContext
}

public static func trigger(onDomain domain: String) -> Trigger {
return Trigger(urlFilter: ContentBlockerRulesBuilder.Constants.subDomainPrefix
+ domain.replacingOccurrences(of: ".", with: "\\.")
+ ContentBlockerRulesBuilder.Constants.domainMatchSuffix,
unlessDomain: nil, ifDomain: nil, resourceType: nil, loadType: nil)
unlessDomain: nil, ifDomain: nil, resourceType: nil, loadType: nil, loadContext: nil)
}

public static func trigger(urlFilter filter: String, loadTypes: [LoadType]? = [ .thirdParty ]) -> Trigger {
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: nil, resourceType: nil, loadType: loadTypes)
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: nil, resourceType: nil, loadType: loadTypes, loadContext: nil)
}

public static func trigger(urlFilter filter: String, unlessDomain urls: [String]?, loadTypes: [LoadType]? = [ .thirdParty ] ) -> Trigger {
return Trigger(urlFilter: filter, unlessDomain: urls, ifDomain: nil, resourceType: nil, loadType: loadTypes)
return Trigger(urlFilter: filter, unlessDomain: urls, ifDomain: nil, resourceType: nil, loadType: loadTypes, loadContext: nil)
}

public static func trigger(urlFilter filter: String, ifDomain domains: [String]?, resourceType types: [ResourceType]?) -> Trigger {
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: domains, resourceType: types, loadType: [ .thirdParty ])
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: domains, resourceType: types, loadType: [ .thirdParty ], loadContext: nil)
}

public static func trigger(urlFilter filter: String,
ifDomain domains: [String]?,
resourceType types: [ResourceType]?,
loadTypes: [LoadType]? = [ .thirdParty ]) -> Trigger {
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: domains, resourceType: types, loadType: loadTypes)
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: domains, resourceType: types, loadType: loadTypes, loadContext: nil)
}

public static func trigger(urlFilter filter: String,
ifDomain domains: [String]?,
resourceType types: [ResourceType]?,
loadTypes: [LoadType]? = [ .thirdParty ],
loadContext: [LoadContext]? = nil) -> Trigger {
return Trigger(urlFilter: filter, unlessDomain: nil, ifDomain: domains,
resourceType: types, loadType: loadTypes, loadContext: loadContext)
}
}

Expand Down
16 changes: 14 additions & 2 deletions Sources/TrackerRadarKit/ContentBlockerRulesBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ public struct ContentBlockerRulesBuilder {
]
} else if r.options == nil && r.exceptions == nil {
return [
block(r, withOwner: tracker.owner, loadTypes: loadTypes)
block(r, withOwner: tracker.owner, loadTypes: loadTypes),
ignorePrevious(r, resourceTypes: [.popup], loadTypes: loadTypes, loadContext: [.topFrame])
]
} else if r.exceptions != nil && r.options != nil {
return [
Expand Down Expand Up @@ -245,7 +246,18 @@ public struct ContentBlockerRulesBuilder {
loadTypes: loadTypes),
action: .ignorePreviousRules())
}


private func ignorePrevious(_ rule: KnownTracker.Rule, matching: KnownTracker.Rule.Matching? = nil,
resourceTypes: [ContentBlockerRule.Trigger.ResourceType], loadTypes: [ContentBlockerRule.Trigger.LoadType],
loadContext: [ContentBlockerRule.Trigger.LoadContext]) -> ContentBlockerRule {
return ContentBlockerRule(trigger: .trigger(urlFilter: rule.normalizedRule(),
ifDomain: matching?.domains?.prefixAll(with: "*"),
resourceType: resourceTypes,
loadTypes: loadTypes,
loadContext: loadContext),
action: .ignorePreviousRules())
}

}

fileprivate extension String {
Expand Down
17 changes: 17 additions & 0 deletions Tests/TrackerRadarKitTests/ContentBlockerRulesBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ class ContentBlockerRulesBuilderTests: XCTestCase {
}
}

func testDefaultIgnoreGeneratesPopupIgnoreRules() throws {
let rules = ContentBlockerRulesBuilder(trackerData: trackerData).buildRules(withExceptions: ["duckduckgo.com"],
andTemporaryUnprotectedDomains: [])

// swiftlint:disable:next line_length
let domainFilter = "^(https?)?(wss?)?://([a-z0-9-]+\\.)*xvideos-cdn\\.com\\/v-c19d94e7937\\/v3\\/js\\/skins\\/min\\/default\\.header\\.static\\.js"
if let idx = rules.firstIndexOfExactFilter(filter: domainFilter) {
let nextRule = rules[idx + 1]
XCTAssertNotNil(nextRule, "Missing ignore-previous popup type rule")
XCTAssert(nextRule.action == .ignorePreviousRules())
XCTAssert(nextRule.trigger.loadContext?.first == .topFrame)
XCTAssert(nextRule.trigger.resourceType?.first == .popup)
} else {
XCTFail("Missing rule for testing")
}
}

func testLoadingUnsupportedRules() throws {
let data = JSONTestDataLoader.mockTrackerData
guard let mockData = try? JSONDecoder().decode(TrackerData.self, from: data) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ extension Array where Element == ContentBlockerRule {

return nil
}

func firstIndexOfExactFilter(filter: String) -> Int? {
self.firstIndex { $0.trigger.urlFilter == filter }
}
}

0 comments on commit a6b7ba1

Please sign in to comment.