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

Commit

Permalink
Fix #6841: Make sure we don't leak S3 private data and cleanup resources
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Mar 14, 2023
1 parent 578575b commit 27af4d5
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 316 deletions.
1 change: 0 additions & 1 deletion App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
UrpLog.log("Failed to initialize user referral program")
}

DebouncingResourceDownloader.shared.startLoading()
#if canImport(BraveTalk)
BraveTalkJitsiCoordinator.sendAppLifetimeEvent(
.didFinishLaunching(options: launchOptions ?? [:])
Expand Down
5 changes: 0 additions & 5 deletions App/iOS/Delegates/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}

func sceneWillEnterForeground(_ scene: UIScene) {
// The reason we need to call this method here instead of `applicationDidBecomeActive`
// is that this method is only invoked whenever the application is entering the foreground where as
// `applicationDidBecomeActive` will get called whenever the Touch ID authentication overlay disappears.
DebouncingResourceDownloader.shared.startLoading()

if let scene = scene as? UIWindowScene {
scene.browserViewController?.windowProtection = windowProtection
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,25 @@ class AdblockDebugMenuTableViewController: TableViewController {
self.actionsSection,
self.datesSection,
self.bundledListsSection(names: listNames),
self.downloadedResourcesSection()
self.downloadedResourcesSection(
header: "Ad-Block Resources",
footer: "Files downloaded using the AdBlockResourceDownloader",
resources: AdblockResourceDownloader.handledResources
),
self.downloadedResourcesSection(
header: "Filter lists",
footer: "Files downloaded using the FilterListResourceDownloader",
resources: FilterListResourceDownloader.shared.filterLists.map({ filterList in
return filterList.makeResource(componentId: filterList.entry.componentId)
})
),
self.downloadedResourcesSection(
header: "Filter list custom URLs",
footer: "Files downloaded using the FilterListURLResourceDownloader",
resources: CustomFilterListStorage.shared.filterListsURLs.map({ filterListURL in
return filterListURL.setting.resource
})
)
]
}
}
Expand Down Expand Up @@ -137,20 +155,22 @@ class AdblockDebugMenuTableViewController: TableViewController {
return section
}

private func downloadedResourcesSection() -> Section {
func createRows(from resources: [ResourceDownloader.Resource]) -> [Row] {
private func downloadedResourcesSection<Resource: DownloadResourceInterface>(
header: Section.Extremity?, footer: Section.Extremity?, resources: [Resource]
) -> Section {
func createRows(from resources: [Resource]) -> [Row] {
resources.compactMap { createRow(from: $0) }
}

func getEtag(from resource: ResourceDownloader.Resource) -> String? {
func getEtag(from resource: Resource) -> String? {
do {
return try ResourceDownloader.etag(for: resource)
} catch {
return nil
}
}

func getFileCreation(for resource: ResourceDownloader.Resource) -> String? {
func getFileCreation(for resource: Resource) -> String? {
do {
guard let date = try ResourceDownloader.creationDate(for: resource) else { return nil }
return Self.fileDateFormatter.string(from: date)
Expand All @@ -159,7 +179,7 @@ class AdblockDebugMenuTableViewController: TableViewController {
}
}

func createRow(from resource: ResourceDownloader.Resource) -> Row? {
func createRow(from resource: Resource) -> Row? {
guard let fileURL = ResourceDownloader.downloadedFileURL(for: resource) else {
return nil
}
Expand All @@ -173,22 +193,9 @@ class AdblockDebugMenuTableViewController: TableViewController {
return Row(text: fileURL.lastPathComponent, detailText: detailText, cellClass: MultilineSubtitleCell.self)
}

var resources = FilterListResourceDownloader.shared.filterLists.map { filterList -> ResourceDownloader.Resource in
return filterList.makeResource(componentId: filterList.entry.componentId)
}

resources.append(contentsOf: CustomFilterListStorage.shared.filterListsURLs.map({ customURL in
return .customFilterListURL(uuid: customURL.setting.uuid, externalURL: customURL.setting.externalURL)
}))

resources.append(contentsOf: [
.debounceRules, .genericContentBlockingBehaviors, .genericFilterRules, .generalCosmeticFilters, .generalScriptletResources
])

return Section(
header: "Downloaded resources",
rows: createRows(from: resources),
footer: "Lists downloaded from the internet at app launch using the ResourceDownloader."
header: header, rows: createRows(from: resources),
footer: footer
)
}
}
Expand Down
45 changes: 30 additions & 15 deletions Sources/Brave/WebFilters/AdblockResourceDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public actor AdblockResourceDownloader: Sendable {
.blockTrackers, .blockCookies
]

/// All the different resources this downloader handles
static let handledResources: [BraveS3Resource] = [
.genericContentBlockingBehaviors, .generalCosmeticFilters, .debounceRules
]

/// A formatter that is used to format a version number
private let fileVersionDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
Expand All @@ -28,9 +33,9 @@ public actor AdblockResourceDownloader: Sendable {
}()

/// The resource downloader that will be used to download all our resoruces
private let resourceDownloader: ResourceDownloader
private let resourceDownloader: ResourceDownloader<BraveS3Resource>
/// All the resources that this downloader handles
private let handledResources: [ResourceDownloader.Resource] = [.genericContentBlockingBehaviors, .generalCosmeticFilters]


init(networkManager: NetworkManager = NetworkManager()) {
self.resourceDownloader = ResourceDownloader(networkManager: networkManager)
Expand All @@ -50,7 +55,7 @@ public actor AdblockResourceDownloader: Sendable {
}

// Here we load downloaded resources if we need to
await handledResources.asyncConcurrentForEach { resource in
await Self.handledResources.asyncConcurrentForEach { resource in
await self.loadCachedData(for: resource)
}
}
Expand All @@ -59,13 +64,13 @@ public actor AdblockResourceDownloader: Sendable {
public func startFetching() {
let fetchInterval = AppConstants.buildChannel.isPublic ? 6.hours : 10.minutes

for resource in handledResources {
for resource in Self.handledResources {
startFetching(resource: resource, every: fetchInterval)
}
}

/// Start fetching the given resource at regular intervals
private func startFetching(resource: ResourceDownloader.Resource, every fetchInterval: TimeInterval) {
private func startFetching(resource: BraveS3Resource, every fetchInterval: TimeInterval) {
Task { @MainActor in
for try await result in await self.resourceDownloader.downloadStream(for: resource, every: fetchInterval) {
switch result {
Expand All @@ -79,7 +84,7 @@ public actor AdblockResourceDownloader: Sendable {
}

/// Load cached data for the given resource. Ensures this is done on the MainActor
private func loadCachedData(for resource: ResourceDownloader.Resource) async {
private func loadCachedData(for resource: BraveS3Resource) async {
do {
if let downloadResult = try ResourceDownloaderStream.downloadResult(for: resource) {
await handle(downloadResult: downloadResult, for: resource)
Expand All @@ -99,7 +104,7 @@ public actor AdblockResourceDownloader: Sendable {
// But ensure that this is only triggered for handled resource
// We should have never triggered this method for resources that are outside
// of the handled resources list
assert(handledResources.contains(resource))
assert(Self.handledResources.contains(resource))
}
}
} catch {
Expand All @@ -108,17 +113,10 @@ public actor AdblockResourceDownloader: Sendable {
}

/// Handle the downloaded file url for the given resource
private func handle(downloadResult: ResourceDownloaderStream.DownloadResult, for resource: ResourceDownloader.Resource) async {
private func handle(downloadResult: ResourceDownloaderStream<BraveS3Resource>.DownloadResult, for resource: BraveS3Resource) async {
let version = fileVersionDateFormatter.string(from: downloadResult.date)

switch resource {
case .genericFilterRules:
await AdBlockEngineManager.shared.add(
resource: AdBlockEngineManager.Resource(type: .ruleList, source: .adBlock),
fileURL: downloadResult.fileURL,
version: version
)

case .generalCosmeticFilters:
await AdBlockEngineManager.shared.add(
resource: AdBlockEngineManager.Resource(type: .dat, source: .cosmeticFilters),
Expand Down Expand Up @@ -151,6 +149,23 @@ public actor AdblockResourceDownloader: Sendable {
ContentBlockerManager.log.error("Failed to compile downloaded content blocker resource: \(error.localizedDescription)")
}

case .debounceRules:
// We don't want to setup the debounce rules more than once for the same cached file
guard downloadResult.isModified || DebouncingResourceDownloader.shared.matcher == nil else {
return
}

do {
guard let data = try ResourceDownloader<BraveS3Resource>.data(for: resource) else {
assertionFailure("We just downloaded this file, how can it not be there?")
return
}

try DebouncingResourceDownloader.shared.setup(withRulesJSON: data)
} catch {
ContentBlockerManager.log.error("Failed to setup debounce rules: \(error.localizedDescription)")
}

default:
assertionFailure("Should not be handling this resource type")
}
Expand Down
85 changes: 85 additions & 0 deletions Sources/Brave/WebFilters/BraveS3Resource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2023 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import Shared

enum BraveS3Resource: Hashable, DownloadResourceInterface {
/// Rules for debouncing links
case debounceRules
/// Generic iOS only content blocking behaviours used for the iOS content blocker
case genericContentBlockingBehaviors
/// Cosmetic filter rules
case generalCosmeticFilters
/// Adblock rules for a filter list
/// iOS only content blocking behaviours used for the iOS content blocker for a given filter list
case filterListContentBlockingBehaviors(uuid: String, componentId: String)

/// The name of the info plist key that contains the service key
private static let servicesKeyName = "SERVICES_KEY"
/// The name of the header value that contains the service key
private static let servicesKeyHeaderValue = "BraveServiceKey"
/// The base s3 environment url that hosts the debouncing (and other) files.
/// Cannot be used as-is and must be combined with a path
private static var baseResourceURL: URL = {
if AppConstants.buildChannel.isPublic {
return URL(string: "https://adblock-data.s3.brave.com")!
} else {
return URL(string: "https://adblock-data-staging.s3.bravesoftware.com")!
}
}()

/// The folder name under which this data should be saved under
var cacheFolderName: String {
switch self {
case .debounceRules:
return "debounce-data"
case .filterListContentBlockingBehaviors(_, let componentId):
return ["filter-lists", componentId].joined(separator: "/")
case .genericContentBlockingBehaviors:
return "abp-data"
case .generalCosmeticFilters:
return "cmf-data"
}
}

/// Get the file name that is stored on the device
var cacheFileName: String {
switch self {
case .debounceRules:
return "ios-debouce.json"
case .filterListContentBlockingBehaviors(let uuid, _):
return "\(uuid)-latest.json"
case .genericContentBlockingBehaviors:
return "latest.json"
case .generalCosmeticFilters:
return "ios-cosmetic-filters.dat"
}
}

/// Get the external path for the given filter list and this resource type
var externalURL: URL {
switch self {
case .debounceRules:
return Self.baseResourceURL.appendingPathComponent("/ios/debounce.json")
case .filterListContentBlockingBehaviors(let uuid, _):
return Self.baseResourceURL.appendingPathComponent("/ios/\(uuid)-latest.json")
case .genericContentBlockingBehaviors:
return Self.baseResourceURL.appendingPathComponent("/ios/latest.json")
case .generalCosmeticFilters:
return Self.baseResourceURL.appendingPathComponent("/ios/ios-cosmetic-filters.dat")
}
}

var headers: [String: String] {
var headers = [String: String]()

if let servicesKeyValue = Bundle.main.getPlistString(for: Self.servicesKeyName) {
headers[Self.servicesKeyHeaderValue] = servicesKeyValue
}

return headers
}
}
Loading

0 comments on commit 27af4d5

Please sign in to comment.